hey it's tdd
This commit is contained in:
parent
32a02d4964
commit
9249ac7717
6 changed files with 172 additions and 27 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -571,7 +571,7 @@ dependencies = [
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
"server_fn",
|
"server_fn",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -676,7 +676,7 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"lru",
|
"lru",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -718,7 +718,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1079,7 +1079,7 @@ dependencies = [
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
@ -1510,6 +1510,7 @@ dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-logger",
|
"dioxus-logger",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"thiserror 2.0.11",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2046,7 +2047,7 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2096,7 +2097,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_qs",
|
"serde_qs",
|
||||||
"server_fn_macro_default",
|
"server_fn_macro_default",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"url",
|
"url",
|
||||||
|
@ -2293,7 +2294,16 @@ version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
|
@ -2307,6 +2317,17 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.8"
|
version = "1.1.8"
|
||||||
|
@ -2557,7 +2578,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2575,7 +2596,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ dioxus = { version = "0.6", features = ["fullstack"] }
|
||||||
dioxus-cli-config = "0.6"
|
dioxus-cli-config = "0.6"
|
||||||
dioxus-logger = "0.6"
|
dioxus-logger = "0.6"
|
||||||
dotenvy = { version = "0.15", optional = true }
|
dotenvy = { version = "0.15", optional = true }
|
||||||
|
thiserror = "2.0.11"
|
||||||
tokio = { version = "1", features = ["full"], optional = true }
|
tokio = { version = "1", features = ["full"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -12,3 +12,19 @@ pub struct SiteInfo {
|
||||||
pub info: SiteMetadata,
|
pub info: SiteMetadata,
|
||||||
pub pages: Vec<PageInfo>,
|
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 },
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::entities::site::{PageInfo, SiteMetadata},
|
domain::entities::site::{Block, Page, PageContent, PageInfo, Post, SiteMetadata},
|
||||||
outbound::repository::site::SiteRepository,
|
outbound::repository::site::{Error, Result, SiteRepository},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct StaticData {}
|
pub struct StaticData {}
|
||||||
|
|
||||||
impl SiteRepository for StaticData {
|
impl SiteRepository for StaticData {
|
||||||
async fn get_site_by_domain(&self, domain: &str) -> SiteMetadata {
|
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> {
|
||||||
SiteMetadata {
|
Ok(SiteMetadata {
|
||||||
domain: domain.to_string(),
|
domain: domain.to_string(),
|
||||||
title: "Test site".to_string(),
|
title: "Test site".to_string(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_pages_for_site(&self, _: &str) -> Vec<PageInfo> {
|
async fn get_pages_for_site(&self, _: &str) -> Result<Vec<PageInfo>> {
|
||||||
vec![
|
Ok(vec![
|
||||||
PageInfo {
|
PageInfo {
|
||||||
title: "Home".to_string(),
|
title: "Home".to_string(),
|
||||||
name: "/".to_string(),
|
name: "/".to_string(),
|
||||||
|
@ -23,6 +23,38 @@ impl SiteRepository for StaticData {
|
||||||
title: "Cool page".to_string(),
|
title: "Cool page".to_string(),
|
||||||
name: "cool".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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
pub trait SiteRepository {
|
||||||
async fn get_site_by_domain(&self, domain: &str) -> SiteMetadata;
|
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>;
|
||||||
async fn get_pages_for_site(&self, domain: &str) -> Vec<PageInfo>;
|
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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
pub struct SiteService<SiteRepo: SiteRepository> {
|
||||||
site_repository: SiteRepo,
|
site_repository: SiteRepo,
|
||||||
|
@ -9,24 +14,80 @@ impl<SiteRepo: SiteRepository> SiteService<SiteRepo> {
|
||||||
Self { site_repository }
|
Self { site_repository }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_site(&self, domain: &str) -> SiteInfo {
|
pub async fn get_site(&self, domain: &str) -> Result<SiteInfo> {
|
||||||
let info = self.site_repository.get_site_by_domain(domain).await;
|
let info = self
|
||||||
let pages = self.site_repository.get_pages_for_site(&info.domain).await;
|
.site_repository
|
||||||
SiteInfo { info, pages }
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::SiteService;
|
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]
|
#[tokio::test]
|
||||||
async fn gets_site_info() {
|
async fn gets_site_info() {
|
||||||
let service = SiteService::new(StaticData {});
|
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.domain, "example.com");
|
||||||
assert_eq!(info.info.title, "Test site");
|
assert_eq!(info.info.title, "Test site");
|
||||||
assert_eq!(info.pages.len(), 2);
|
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!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue