Parakeet is a Rust-based Bluesky AppServer aiming to implement most of the functionality required to support the Bluesky client
appview atproto bluesky rust appserver

refactor: clean vec<option<T>> mess

+66 -33
+54 -13
parakeet-db/src/models.rs
··· 137 137 138 138 pub content: String, 139 139 pub facets: Option<serde_json::Value>, 140 - pub languages: Vec<Option<String>>, 141 - pub tags: Vec<Option<String>>, 140 + pub languages: not_null_vec::TextArray, 141 + pub tags: not_null_vec::TextArray, 142 142 143 143 pub parent_uri: Option<String>, 144 144 pub parent_cid: Option<String>, ··· 148 148 pub embed: Option<String>, 149 149 pub embed_subtype: Option<String>, 150 150 151 - pub mentions: Option<Vec<Option<String>>>, 151 + pub mentions: Option<not_null_vec::TextArray>, 152 152 pub violates_threadgate: bool, 153 153 154 154 pub created_at: DateTime<Utc>, ··· 236 236 pub cid: String, 237 237 pub post_uri: String, 238 238 239 - pub detached: Vec<Option<String>>, 240 - pub rules: Vec<Option<String>>, 239 + pub detached: not_null_vec::TextArray, 240 + pub rules: not_null_vec::TextArray, 241 241 242 242 pub created_at: DateTime<Utc>, 243 243 pub indexed_at: NaiveDateTime, ··· 252 252 pub cid: String, 253 253 pub post_uri: String, 254 254 255 - pub hidden_replies: Vec<Option<String>>, 256 - pub allow: Option<Vec<Option<String>>>, 257 - pub allowed_lists: Option<Vec<Option<String>>>, 255 + pub hidden_replies: not_null_vec::TextArray, 256 + pub allow: Option<not_null_vec::TextArray>, 257 + pub allowed_lists: Option<not_null_vec::TextArray>, 258 258 259 259 pub record: serde_json::Value, 260 260 ··· 276 276 pub description: Option<String>, 277 277 pub description_facets: Option<serde_json::Value>, 278 278 pub list: String, 279 - pub feeds: Option<Vec<Option<String>>>, 279 + pub feeds: Option<not_null_vec::TextArray>, 280 280 281 281 pub created_at: DateTime<Utc>, 282 282 pub indexed_at: NaiveDateTime, ··· 290 290 pub did: String, 291 291 pub cid: String, 292 292 293 - pub reasons: Option<Vec<Option<String>>>, 294 - pub subject_types: Option<Vec<Option<String>>>, 295 - pub subject_collections: Option<Vec<Option<String>>>, 293 + pub reasons: Option<not_null_vec::TextArray>, 294 + pub subject_types: Option<not_null_vec::TextArray>, 295 + pub subject_collections: Option<not_null_vec::TextArray>, 296 296 297 297 pub created_at: NaiveDateTime, 298 298 pub indexed_at: NaiveDateTime, ··· 402 402 pub subject: String, 403 403 pub subject_cid: Option<String>, 404 404 pub subject_type: String, 405 - pub tags: Vec<Option<String>>, 405 + pub tags: not_null_vec::TextArray, 406 406 pub created_at: DateTime<Utc>, 407 407 } 408 408 ··· 430 430 pub typ: String, 431 431 pub sort_at: DateTime<Utc>, 432 432 } 433 + 434 + mod not_null_vec { 435 + use diesel::deserialize::FromSql; 436 + use diesel::pg::Pg; 437 + use diesel::sql_types::{Array, Nullable, Text}; 438 + use diesel::{deserialize, FromSqlRow}; 439 + use serde::{Deserialize, Serialize}; 440 + use std::ops::{Deref, DerefMut}; 441 + 442 + #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] 443 + #[diesel(sql_type = Array<Nullable<Text>>)] 444 + pub struct TextArray(pub Vec<String>); 445 + 446 + impl FromSql<Array<Nullable<Text>>, Pg> for TextArray { 447 + fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result<Self> { 448 + let vec_with_nulls = 449 + <Vec<Option<String>> as FromSql<Array<Nullable<Text>>, Pg>>::from_sql(bytes)?; 450 + Ok(TextArray(vec_with_nulls.into_iter().flatten().collect())) 451 + } 452 + } 453 + 454 + impl Deref for TextArray { 455 + type Target = Vec<String>; 456 + 457 + fn deref(&self) -> &Self::Target { 458 + &self.0 459 + } 460 + } 461 + 462 + impl DerefMut for TextArray { 463 + fn deref_mut(&mut self) -> &mut Self::Target { 464 + &mut self.0 465 + } 466 + } 467 + 468 + impl From<TextArray> for Vec<String> { 469 + fn from(v: TextArray) -> Vec<String> { 470 + v.0 471 + } 472 + } 473 + }
+5 -9
parakeet/src/hydration/labeler.rs
··· 42 42 likes: Option<i32>, 43 43 ) -> LabelerViewDetailed { 44 44 let reason_types = labeler.reasons.map(|v| { 45 - v.into_iter() 46 - .flatten() 47 - .filter_map(|v| ReasonType::from_str(&v).ok()) 45 + v.iter() 46 + .filter_map(|v| ReasonType::from_str(v).ok()) 48 47 .collect() 49 48 }); 50 49 ··· 74 73 }) 75 74 .collect(); 76 75 let subject_types = labeler.subject_types.map(|v| { 77 - v.into_iter() 78 - .flatten() 79 - .filter_map(|v| SubjectType::from_str(&v).ok()) 76 + v.iter() 77 + .filter_map(|v| SubjectType::from_str(v).ok()) 80 78 .collect() 81 79 }); 82 - let subject_collections = labeler 83 - .subject_collections 84 - .map(|v| v.into_iter().flatten().collect()); 80 + let subject_collections = labeler.subject_collections.map(Vec::from); 85 81 86 82 LabelerViewDetailed { 87 83 uri: format!("at://{}/app.bsky.labeler.service/self", labeler.did),
+3 -3
parakeet/src/hydration/posts.rs
··· 89 89 let threadgate = threadgate?; 90 90 91 91 let lists = match threadgate.allowed_lists.as_ref() { 92 - Some(allowed_lists) => allowed_lists.iter().flatten().cloned().collect(), 92 + Some(allowed_lists) => allowed_lists.clone().into(), 93 93 None => Vec::new(), 94 94 }; 95 95 let lists = self.hydrate_lists_basic(lists).await; ··· 106 106 ) -> HashMap<String, ThreadgateView> { 107 107 let lists = threadgates.iter().fold(Vec::new(), |mut acc, c| { 108 108 if let Some(lists) = &c.allowed_lists { 109 - acc.extend(lists.iter().flatten().cloned()); 109 + acc.extend(lists.clone().0); 110 110 } 111 111 acc 112 112 }); ··· 118 118 let this_lists = match &threadgate.allowed_lists { 119 119 Some(allowed_lists) => allowed_lists 120 120 .iter() 121 - .filter_map(|v| v.clone().and_then(|v| lists.get(&v).cloned())) 121 + .filter_map(|v| lists.get(v).cloned()) 122 122 .collect(), 123 123 None => Vec::new(), 124 124 };
+3 -7
parakeet/src/hydration/starter_packs.rs
··· 96 96 let feeds = sp 97 97 .feeds 98 98 .clone() 99 - .unwrap_or_default() 100 - .into_iter() 101 - .flatten() 102 - .collect(); 103 - let feeds = self.hydrate_feedgens(feeds).await.into_values().collect(); 99 + .unwrap_or_default(); 100 + let feeds = self.hydrate_feedgens(feeds.into()).await.into_values().collect(); 104 101 105 102 Some(build_spview(sp, creator, labels, list, feeds)) 106 103 } ··· 119 116 let feeds = packs 120 117 .values() 121 118 .filter_map(|pack| pack.feeds.clone()) 122 - .flat_map(|feeds| feeds.into_iter().flatten()) 119 + .flat_map(Vec::from) 123 120 .collect(); 124 121 125 122 let creators = self.hydrate_profiles_basic(creators).await; ··· 133 130 let list = lists.get(&pack.list).cloned(); 134 131 let feeds = pack.feeds.as_ref().map(|v| { 135 132 v.iter() 136 - .flatten() 137 133 .filter_map(|feed| feeds.get(feed).cloned()) 138 134 .collect() 139 135 });
+1 -1
parakeet/src/xrpc/community_lexicon/bookmarks.rs
··· 60 60 .into_iter() 61 61 .map(|bookmark| Bookmark { 62 62 subject: bookmark.subject, 63 - tags: bookmark.tags.into_iter().flatten().collect(), 63 + tags: bookmark.tags.into(), 64 64 created_at: bookmark.created_at, 65 65 }) 66 66 .collect();