diff --git a/crates/adapters/postgres-search/src/lib.rs b/crates/adapters/postgres-search/src/lib.rs index 22983fe..0a90607 100644 --- a/crates/adapters/postgres-search/src/lib.rs +++ b/crates/adapters/postgres-search/src/lib.rs @@ -67,13 +67,13 @@ const FEED_SELECT: &str = " (SELECT COUNT(*) FROM thoughts r WHERE r.in_reply_to_id=t.id) AS reply_count FROM thoughts t JOIN users u ON u.id=t.user_id"; -fn row_to_entry(r: FeedRow) -> FeedEntry { +fn row_to_entry(r: FeedRow) -> Result { let thought = Thought { id: ThoughtId::from_uuid(r.thought_id), user_id: UserId::from_uuid(r.t_user_id), content: Content::new_remote(r.content), in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), - visibility: Visibility::from_db_str(&r.visibility), + visibility: Visibility::from_db_str(&r.visibility)?, content_warning: r.content_warning, sensitive: r.sensitive, local: r.t_local, @@ -94,7 +94,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { created_at: r.author_created_at, updated_at: r.author_updated_at, }; - FeedEntry { + Ok(FeedEntry { thought, author, like_count: r.like_count, @@ -102,7 +102,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { reply_count: r.reply_count, liked_by_viewer: false, boosted_by_viewer: false, - } + }) } #[async_trait] @@ -137,7 +137,10 @@ impl SearchPort for PgSearchRepository { .map_err(|e| DomainError::Internal(e.to_string()))?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, diff --git a/crates/adapters/postgres/src/feed.rs b/crates/adapters/postgres/src/feed.rs index 871f82b..6aacb67 100644 --- a/crates/adapters/postgres/src/feed.rs +++ b/crates/adapters/postgres/src/feed.rs @@ -106,13 +106,13 @@ fn feed_select(viewer: Option) -> String { ) } -fn row_to_entry(r: FeedRow) -> FeedEntry { +fn row_to_entry(r: FeedRow) -> Result { let thought = Thought { id: ThoughtId::from_uuid(r.thought_id), user_id: UserId::from_uuid(r.t_user_id), content: Content::new_remote(r.content), in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), - visibility: Visibility::from_db_str(&r.visibility), + visibility: Visibility::from_db_str(&r.visibility)?, content_warning: r.content_warning, sensitive: r.sensitive, local: r.t_local, @@ -133,7 +133,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { created_at: r.author_created_at, updated_at: r.author_updated_at, }; - FeedEntry { + Ok(FeedEntry { thought, author, like_count: r.like_count, @@ -141,7 +141,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { reply_count: r.reply_count, liked_by_viewer: r.liked_by_viewer, boosted_by_viewer: r.boosted_by_viewer, - } + }) } #[async_trait] @@ -176,7 +176,10 @@ impl FeedRepository for PgFeedRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, @@ -206,7 +209,10 @@ impl FeedRepository for PgFeedRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, @@ -239,7 +245,10 @@ impl FeedRepository for PgFeedRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, @@ -281,7 +290,10 @@ impl FeedRepository for PgFeedRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, @@ -321,7 +333,10 @@ impl FeedRepository for PgFeedRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(row_to_entry).collect(), + items: rows + .into_iter() + .map(row_to_entry) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, diff --git a/crates/adapters/postgres/src/follow.rs b/crates/adapters/postgres/src/follow.rs index 0aeba1b..6bd2286 100644 --- a/crates/adapters/postgres/src/follow.rs +++ b/crates/adapters/postgres/src/follow.rs @@ -76,13 +76,18 @@ impl FollowRepository for PgFollowRepository { .fetch_optional(&self.pool) .await .into_domain() - .map(|o| o.map(|r| Follow { - follower_id: UserId::from_uuid(r.follower_id), - following_id: UserId::from_uuid(r.following_id), - state: FollowState::from_db_str(&r.state), - ap_id: r.ap_id, - created_at: r.created_at, - })) + .and_then(|o| { + o.map(|r| { + Ok(Follow { + follower_id: UserId::from_uuid(r.follower_id), + following_id: UserId::from_uuid(r.following_id), + state: FollowState::from_db_str(&r.state)?, + ap_id: r.ap_id, + created_at: r.created_at, + }) + }) + .transpose() + }) } async fn update_state( diff --git a/crates/adapters/postgres/src/tag.rs b/crates/adapters/postgres/src/tag.rs index a39c027..ab692a9 100644 --- a/crates/adapters/postgres/src/tag.rs +++ b/crates/adapters/postgres/src/tag.rs @@ -105,7 +105,10 @@ impl TagRepository for PgTagRepository { .fetch_all(&self.pool).await.into_domain()?; Ok(Paginated { - items: rows.into_iter().map(Thought::from).collect(), + items: rows + .into_iter() + .map(Thought::try_from) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, diff --git a/crates/adapters/postgres/src/thought.rs b/crates/adapters/postgres/src/thought.rs index be78c7b..3a0cf7d 100644 --- a/crates/adapters/postgres/src/thought.rs +++ b/crates/adapters/postgres/src/thought.rs @@ -36,20 +36,21 @@ pub(crate) struct ThoughtRow { pub updated_at: Option>, } -impl From for Thought { - fn from(r: ThoughtRow) -> Self { - Thought { +impl TryFrom for Thought { + type Error = DomainError; + fn try_from(r: ThoughtRow) -> Result { + Ok(Thought { id: ThoughtId::from_uuid(r.id), user_id: UserId::from_uuid(r.user_id), content: Content::new_remote(r.content), in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), - visibility: Visibility::from_db_str(&r.visibility), + visibility: Visibility::from_db_str(&r.visibility)?, content_warning: r.content_warning, sensitive: r.sensitive, local: r.local, created_at: r.created_at, updated_at: r.updated_at, - } + }) } } @@ -85,7 +86,7 @@ impl ThoughtRepository for PgThoughtRepository { .fetch_optional(&self.pool) .await .into_domain() - .map(|o| o.map(Thought::from)) + .and_then(|o| o.map(Thought::try_from).transpose()) } async fn delete(&self, id: &ThoughtId, user_id: &UserId) -> Result<(), DomainError> { @@ -129,7 +130,7 @@ impl ThoughtRepository for PgThoughtRepository { .fetch_all(&self.pool) .await .into_domain() - .map(|rows| rows.into_iter().map(Thought::from).collect()) + .and_then(|rows| rows.into_iter().map(Thought::try_from).collect()) } async fn list_by_user( @@ -155,7 +156,10 @@ impl ThoughtRepository for PgThoughtRepository { .into_domain()?; Ok(Paginated { - items: rows.into_iter().map(Thought::from).collect(), + items: rows + .into_iter() + .map(Thought::try_from) + .collect::, _>>()?, total, page: page.page, per_page: page.per_page, diff --git a/crates/domain/src/models/social.rs b/crates/domain/src/models/social.rs index 8ac4df0..bca0c34 100644 --- a/crates/domain/src/models/social.rs +++ b/crates/domain/src/models/social.rs @@ -35,11 +35,14 @@ impl FollowState { } } - pub fn from_db_str(s: &str) -> Self { + pub fn from_db_str(s: &str) -> Result { match s { - "pending" => Self::Pending, - "rejected" => Self::Rejected, - _ => Self::Accepted, + "pending" => Ok(Self::Pending), + "accepted" => Ok(Self::Accepted), + "rejected" => Ok(Self::Rejected), + other => Err(crate::errors::DomainError::Internal(format!( + "unknown follow_state: '{other}'" + ))), } } } diff --git a/crates/domain/src/models/thought.rs b/crates/domain/src/models/thought.rs index 2e99773..72c1953 100644 --- a/crates/domain/src/models/thought.rs +++ b/crates/domain/src/models/thought.rs @@ -33,12 +33,15 @@ impl Visibility { } } - pub fn from_db_str(s: &str) -> Self { + pub fn from_db_str(s: &str) -> Result { match s { - "followers" => Self::Followers, - "unlisted" => Self::Unlisted, - "direct" => Self::Direct, - _ => Self::Public, + "public" => Ok(Self::Public), + "followers" => Ok(Self::Followers), + "unlisted" => Ok(Self::Unlisted), + "direct" => Ok(Self::Direct), + other => Err(crate::errors::DomainError::Internal(format!( + "unknown visibility: '{other}'" + ))), } } }