diff --git a/Cargo.lock b/Cargo.lock index 76e82bc..31df059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,7 +571,7 @@ dependencies = [ "pin-project", "serde", "server_fn", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -676,7 +676,7 @@ dependencies = [ "http", "lru", "rustc-hash", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -718,7 +718,7 @@ dependencies = [ "serde", "serde_json", "slab", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -1079,7 +1079,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1510,6 +1510,7 @@ dependencies = [ "dioxus-cli-config", "dioxus-logger", "dotenvy", + "thiserror 2.0.11", "tokio", ] @@ -2046,7 +2047,7 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2096,7 +2097,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror", + "thiserror 1.0.69", "tower 0.4.13", "tower-layer", "url", @@ -2293,7 +2294,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -2307,6 +2317,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -2557,7 +2578,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -2575,7 +2596,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] diff --git a/Cargo.toml b/Cargo.toml index 3304734..7b673dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ dioxus = { version = "0.6", features = ["fullstack"] } dioxus-cli-config = "0.6" dioxus-logger = "0.6" dotenvy = { version = "0.15", optional = true } +thiserror = "2.0.11" tokio = { version = "1", features = ["full"], optional = true } [features] diff --git a/src/domain/entities/site.rs b/src/domain/entities/site.rs index 2ee7b18..a6b7188 100644 --- a/src/domain/entities/site.rs +++ b/src/domain/entities/site.rs @@ -12,3 +12,19 @@ pub struct SiteInfo { pub info: SiteMetadata, pub pages: Vec<PageInfo>, } +pub struct Page { + pub info: PageInfo, + pub content: PageContent, +} + +pub enum PageContent { + Single { content: Post }, +} + +pub struct Post { + pub blocks: Vec<Block>, +} + +pub enum Block { + Text { text: String }, +} diff --git a/src/outbound/repository/adapters/static_data.rs b/src/outbound/repository/adapters/static_data.rs index 523d5a6..33556ac 100644 --- a/src/outbound/repository/adapters/static_data.rs +++ b/src/outbound/repository/adapters/static_data.rs @@ -1,20 +1,20 @@ use crate::{ - domain::entities::site::{PageInfo, SiteMetadata}, - outbound::repository::site::SiteRepository, + domain::entities::site::{Block, Page, PageContent, PageInfo, Post, SiteMetadata}, + outbound::repository::site::{Error, Result, SiteRepository}, }; pub struct StaticData {} impl SiteRepository for StaticData { - async fn get_site_by_domain(&self, domain: &str) -> SiteMetadata { - SiteMetadata { + async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> { + Ok(SiteMetadata { domain: domain.to_string(), title: "Test site".to_string(), - } + }) } - async fn get_pages_for_site(&self, _: &str) -> Vec<PageInfo> { - vec![ + async fn get_pages_for_site(&self, _: &str) -> Result<Vec<PageInfo>> { + Ok(vec![ PageInfo { title: "Home".to_string(), name: "/".to_string(), @@ -23,6 +23,38 @@ impl SiteRepository for StaticData { title: "Cool page".to_string(), name: "cool".to_string(), }, - ] + ]) + } + + async fn get_page(&self, _: &str, name: &str) -> Result<Page> { + match name { + "/" => Ok(Page { + info: PageInfo { + title: "Home".to_string(), + name: name.to_string(), + }, + content: PageContent::Single { + content: Post { + blocks: vec![Block::Text { + text: "Hello, world!".to_string(), + }], + }, + }, + }), + "cool" => Ok(Page { + info: PageInfo { + title: "Cool page".to_string(), + name: name.to_string(), + }, + content: PageContent::Single { + content: Post { + blocks: vec![Block::Text { + text: "Hello, world!".to_string(), + }], + }, + }, + }), + _ => Err(Error::NotFound), + } } } diff --git a/src/outbound/repository/site.rs b/src/outbound/repository/site.rs index 38408b7..201eba7 100644 --- a/src/outbound/repository/site.rs +++ b/src/outbound/repository/site.rs @@ -1,6 +1,20 @@ -use crate::domain::entities::site::{PageInfo, SiteMetadata}; +use thiserror::Error; + +use crate::domain::entities::site::{Page, PageInfo, SiteMetadata}; pub trait SiteRepository { - async fn get_site_by_domain(&self, domain: &str) -> SiteMetadata; - async fn get_pages_for_site(&self, domain: &str) -> Vec<PageInfo>; + async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>; + async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>; + async fn get_page(&self, domain: &str, name: &str) -> Result<Page>; +} + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Error, Debug)] +pub enum Error { + #[error("resource not found")] + NotFound, + + #[error("the server encountered an error: {0}")] + ServerError(String), } diff --git a/src/outbound/services/site.rs b/src/outbound/services/site.rs index e928e77..bae1112 100644 --- a/src/outbound/services/site.rs +++ b/src/outbound/services/site.rs @@ -1,4 +1,9 @@ -use crate::{domain::entities::site::SiteInfo, outbound::repository::site::SiteRepository}; +use thiserror::Error; + +use crate::{ + domain::entities::site::{Page, SiteInfo}, + outbound::repository::{self, site::SiteRepository}, +}; pub struct SiteService<SiteRepo: SiteRepository> { site_repository: SiteRepo, @@ -9,24 +14,80 @@ impl<SiteRepo: SiteRepository> SiteService<SiteRepo> { Self { site_repository } } - pub async fn get_site(&self, domain: &str) -> SiteInfo { - let info = self.site_repository.get_site_by_domain(domain).await; - let pages = self.site_repository.get_pages_for_site(&info.domain).await; - SiteInfo { info, pages } + pub async fn get_site(&self, domain: &str) -> Result<SiteInfo> { + let info = self + .site_repository + .get_site_by_domain(domain) + .await + .map_err(|e| match e { + repository::site::Error::NotFound => Error::NotFound, + _ => Error::RepositoryError(e), + })?; + let pages = self + .site_repository + .get_pages_for_site(&info.domain) + .await?; + + Ok(SiteInfo { info, pages }) } + + pub async fn get_page(&self, domain: &str, name: &str) -> Result<Page> { + let info = self + .site_repository + .get_site_by_domain(domain) + .await + .map_err(|e| match e { + repository::site::Error::NotFound => Error::NotFound, + _ => Error::RepositoryError(e), + })?; + let page = self.site_repository.get_page(&info.domain, name).await?; + + Ok(page) + } +} + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Error, Debug)] +pub enum Error { + #[error("resource not found")] + NotFound, + + #[error("the server encountered an error: {0}")] + RepositoryError(#[from] repository::site::Error), } #[cfg(test)] mod tests { use super::SiteService; - use crate::outbound::repository::adapters::static_data::StaticData; + use crate::{ + domain::entities::site::{Block, PageContent}, + outbound::repository::adapters::static_data::StaticData, + }; #[tokio::test] async fn gets_site_info() { let service = SiteService::new(StaticData {}); - let info = service.get_site("example.com").await; + let info = service.get_site("example.com").await.unwrap(); assert_eq!(info.info.domain, "example.com"); assert_eq!(info.info.title, "Test site"); assert_eq!(info.pages.len(), 2); } + + #[tokio::test] + async fn gets_page_data() { + let service = SiteService::new(StaticData {}); + let page = service.get_page("example.com", "/").await.unwrap(); + + assert_eq!(page.info.title, "Home"); + assert_eq!(page.info.name, "/"); + + // page content must be a single text block + let PageContent::Single { + content: page_content, + } = page.content; + assert_eq!(page_content.blocks.len(), 1); + let Block::Text { text } = &page_content.blocks[0]; + assert_eq!(text, "Hello, world!"); + } }