extract api-types crate, adopt thiserror for all errors
api-types: standalone crate with DTOs (widget, data source, layout, preset) extracted from http-api. Shared between http-api and future SPA. thiserror: replaced all manual Display impls with derive macros across 8 crates (config-sqlite, config-memory, tcp-server, tcp-client, http-json, rss, media, application).
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -8,11 +8,20 @@ version = "0.2.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "api-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"domain",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "application"
|
name = "application"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"domain",
|
"domain",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -228,6 +237,7 @@ name = "config-memory"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"domain",
|
"domain",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -238,6 +248,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -701,6 +712,7 @@ dependencies = [
|
|||||||
name = "http-api"
|
name = "http-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"api-types",
|
||||||
"application",
|
"application",
|
||||||
"axum",
|
"axum",
|
||||||
"config-memory",
|
"config-memory",
|
||||||
@@ -744,6 +756,7 @@ dependencies = [
|
|||||||
"domain",
|
"domain",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1068,6 +1081,7 @@ dependencies = [
|
|||||||
"domain",
|
"domain",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1493,6 +1507,7 @@ dependencies = [
|
|||||||
"quick-xml",
|
"quick-xml",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2012,6 +2027,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"client-domain",
|
"client-domain",
|
||||||
"protocol",
|
"protocol",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2021,6 +2037,7 @@ dependencies = [
|
|||||||
"domain",
|
"domain",
|
||||||
"postcard",
|
"postcard",
|
||||||
"protocol",
|
"protocol",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ members = [
|
|||||||
"crates/adapters/http-json",
|
"crates/adapters/http-json",
|
||||||
"crates/adapters/rss",
|
"crates/adapters/rss",
|
||||||
"crates/adapters/media",
|
"crates/adapters/media",
|
||||||
|
"crates/api-types",
|
||||||
"crates/bootstrap",
|
"crates/bootstrap",
|
||||||
"crates/client-desktop",
|
"crates/client-desktop",
|
||||||
]
|
]
|
||||||
@@ -36,6 +37,9 @@ config-sqlite = { path = "crates/adapters/config-sqlite" }
|
|||||||
http-api = { path = "crates/adapters/http-api" }
|
http-api = { path = "crates/adapters/http-api" }
|
||||||
axum = { version = "0.8", features = ["macros"] }
|
axum = { version = "0.8", features = ["macros"] }
|
||||||
tower-http = { version = "0.6", features = ["cors"] }
|
tower-http = { version = "0.6", features = ["cors"] }
|
||||||
|
api-types = { path = "crates/api-types" }
|
||||||
|
thiserror = "2.0"
|
||||||
|
anyhow = "1.0"
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
|
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain.workspace = true
|
domain.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|||||||
@@ -6,19 +6,12 @@ use domain::{
|
|||||||
WidgetConfig, WidgetId,
|
WidgetConfig, WidgetId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum MemoryConfigError {
|
pub enum MemoryConfigError {
|
||||||
|
#[error("lock poisoned")]
|
||||||
LockPoisoned,
|
LockPoisoned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for MemoryConfigError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
MemoryConfigError::LockPoisoned => write!(f, "lock poisoned"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MemoryConfigStore {
|
pub struct MemoryConfigStore {
|
||||||
widgets: RwLock<HashMap<WidgetId, WidgetConfig>>,
|
widgets: RwLock<HashMap<WidgetId, WidgetConfig>>,
|
||||||
data_sources: RwLock<HashMap<DataSourceId, DataSource>>,
|
data_sources: RwLock<HashMap<DataSourceId, DataSource>>,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ domain.workspace = true
|
|||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum SqliteConfigError {
|
pub enum SqliteConfigError {
|
||||||
Sql(sqlx::Error),
|
#[error("sql: {0}")]
|
||||||
|
Sql(#[from] sqlx::Error),
|
||||||
|
#[error("serialization: {0}")]
|
||||||
Serialization(String),
|
Serialization(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SqliteConfigError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
SqliteConfigError::Sql(e) => write!(f, "sql: {e}"),
|
|
||||||
SqliteConfigError::Serialization(e) => write!(f, "serialization: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
domain.workspace = true
|
domain.workspace = true
|
||||||
application.workspace = true
|
application.workspace = true
|
||||||
|
api-types.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
mod dto;
|
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use domain::{ConfigRepository, EventPublisher};
|
use domain::{ConfigRepository, EventPublisher};
|
||||||
use application::ConfigService;
|
use application::ConfigService;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::dto::DataSourceDto;
|
use api_types::DataSourceDto;
|
||||||
|
|
||||||
type S<C, E> = State<AppState<C, E>>;
|
type S<C, E> = State<AppState<C, E>>;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use domain::{ConfigRepository, EventPublisher};
|
use domain::{ConfigRepository, EventPublisher};
|
||||||
use application::ConfigService;
|
use application::ConfigService;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::dto::LayoutDto;
|
use api_types::LayoutDto;
|
||||||
|
|
||||||
type S<C, E> = State<AppState<C, E>>;
|
type S<C, E> = State<AppState<C, E>>;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use domain::{ConfigRepository, EventPublisher};
|
use domain::{ConfigRepository, EventPublisher};
|
||||||
use application::ConfigService;
|
use application::ConfigService;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::dto::{PresetDto, CreatePresetDto};
|
use api_types::{PresetDto, CreatePresetDto};
|
||||||
|
|
||||||
type S<C, E> = State<AppState<C, E>>;
|
type S<C, E> = State<AppState<C, E>>;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use domain::{ConfigRepository, EventPublisher};
|
use domain::{ConfigRepository, EventPublisher};
|
||||||
use application::ConfigService;
|
use application::ConfigService;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::dto::{WidgetDto, CreateWidgetDto};
|
use api_types::{WidgetDto, CreateWidgetDto};
|
||||||
|
|
||||||
type S<C, E> = State<AppState<C, E>>;
|
type S<C, E> = State<AppState<C, E>>;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
domain.workspace = true
|
domain.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -4,23 +4,16 @@ pub struct HttpJsonAdapter {
|
|||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum HttpJsonError {
|
pub enum HttpJsonError {
|
||||||
Request(reqwest::Error),
|
#[error("request: {0}")]
|
||||||
|
Request(#[from] reqwest::Error),
|
||||||
|
#[error("no url configured")]
|
||||||
NoUrl,
|
NoUrl,
|
||||||
|
#[error("parse: {0}")]
|
||||||
Parse(String),
|
Parse(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for HttpJsonError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
HttpJsonError::Request(e) => write!(f, "request: {e}"),
|
|
||||||
HttpJsonError::NoUrl => write!(f, "no url configured"),
|
|
||||||
HttpJsonError::Parse(e) => write!(f, "parse: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpJsonAdapter {
|
impl HttpJsonAdapter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
domain.workspace = true
|
domain.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum MediaError {
|
pub enum MediaError {
|
||||||
Request(reqwest::Error),
|
#[error("request: {0}")]
|
||||||
|
Request(#[from] reqwest::Error),
|
||||||
|
#[error("no url configured")]
|
||||||
NoUrl,
|
NoUrl,
|
||||||
|
#[error("parse: {0}")]
|
||||||
Parse(String),
|
Parse(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for MediaError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
MediaError::Request(e) => write!(f, "request: {e}"),
|
|
||||||
MediaError::NoUrl => write!(f, "no url configured"),
|
|
||||||
MediaError::Parse(e) => write!(f, "parse: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ domain.workspace = true
|
|||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
quick-xml = { version = "0.37", features = ["serialize"] }
|
quick-xml = { version = "0.37", features = ["serialize"] }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum RssError {
|
pub enum RssError {
|
||||||
Request(reqwest::Error),
|
#[error("request: {0}")]
|
||||||
|
Request(#[from] reqwest::Error),
|
||||||
|
#[error("no url configured")]
|
||||||
NoUrl,
|
NoUrl,
|
||||||
|
#[error("parse: {0}")]
|
||||||
Parse(String),
|
Parse(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for RssError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
RssError::Request(e) => write!(f, "request: {e}"),
|
|
||||||
RssError::NoUrl => write!(f, "no url configured"),
|
|
||||||
RssError::Parse(e) => write!(f, "parse: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
client-domain.workspace = true
|
client-domain.workspace = true
|
||||||
protocol.workspace = true
|
protocol.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|||||||
@@ -4,23 +4,16 @@ use std::time::Duration;
|
|||||||
use client_domain::NetworkPort;
|
use client_domain::NetworkPort;
|
||||||
use protocol::MAX_FRAME_SIZE;
|
use protocol::MAX_FRAME_SIZE;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TcpClientError {
|
pub enum TcpClientError {
|
||||||
Io(std::io::Error),
|
#[error("io: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("not connected")]
|
||||||
NotConnected,
|
NotConnected,
|
||||||
|
#[error("frame too large: {0}")]
|
||||||
FrameTooLarge(usize),
|
FrameTooLarge(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for TcpClientError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
TcpClientError::Io(e) => write!(f, "io: {e}"),
|
|
||||||
TcpClientError::NotConnected => write!(f, "not connected"),
|
|
||||||
TcpClientError::FrameTooLarge(n) => write!(f, "frame too large: {n}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StdTcpClient {
|
pub struct StdTcpClient {
|
||||||
stream: Option<TcpStream>,
|
stream: Option<TcpStream>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ domain.workspace = true
|
|||||||
protocol.workspace = true
|
protocol.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
postcard.workspace = true
|
postcard.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TcpServerError {
|
pub enum TcpServerError {
|
||||||
Io(std::io::Error),
|
#[error("io: {0}")]
|
||||||
Encode(postcard::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
#[error("encode: {0}")]
|
||||||
|
Encode(#[from] postcard::Error),
|
||||||
impl std::fmt::Display for TcpServerError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
TcpServerError::Io(e) => write!(f, "io: {e}"),
|
|
||||||
TcpServerError::Encode(e) => write!(f, "encode: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
crates/api-types/Cargo.toml
Normal file
8
crates/api-types/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "api-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
domain.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use domain::*;
|
use domain::*;
|
||||||
use super::layout::LayoutDto;
|
use crate::layout::LayoutDto;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PresetDto {
|
pub struct PresetDto {
|
||||||
@@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain.workspace = true
|
domain.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|||||||
@@ -11,25 +11,18 @@ pub struct ConfigService<'a, C, E> {
|
|||||||
events: &'a E,
|
events: &'a E,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ConfigError<C: fmt::Debug, E: fmt::Debug> {
|
pub enum ConfigError<C: fmt::Debug, E: fmt::Debug> {
|
||||||
|
#[error("repository error: {0:?}")]
|
||||||
Repository(C),
|
Repository(C),
|
||||||
|
#[error("event error: {0:?}")]
|
||||||
Event(E),
|
Event(E),
|
||||||
|
#[error("validation errors: {0:?}")]
|
||||||
Validation(Vec<DataSourceValidationError>),
|
Validation(Vec<DataSourceValidationError>),
|
||||||
|
#[error("not found")]
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: fmt::Debug, E: fmt::Debug> fmt::Display for ConfigError<C, E> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ConfigError::Repository(e) => write!(f, "repository error: {:?}", e),
|
|
||||||
ConfigError::Event(e) => write!(f, "event error: {:?}", e),
|
|
||||||
ConfigError::Validation(errors) => write!(f, "validation errors: {:?}", errors),
|
|
||||||
ConfigError::NotFound => write!(f, "not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, C, E> ConfigService<'a, C, E>
|
impl<'a, C, E> ConfigService<'a, C, E>
|
||||||
where
|
where
|
||||||
C: ConfigRepository,
|
C: ConfigRepository,
|
||||||
|
|||||||
Reference in New Issue
Block a user