hey it's tdd

This commit is contained in:
Hamcha 2025-01-25 13:23:02 +01:00
parent 32a02d4964
commit 9249ac7717
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
6 changed files with 172 additions and 27 deletions
Cargo.lockCargo.toml
src
domain/entities
outbound
repository
services

39
Cargo.lock generated
View file

@ -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",
]

View file

@ -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]

View file

@ -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 },
}

View file

@ -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),
}
}
}

View file

@ -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),
}

View file

@ -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!");
}
}