Skip to main content

smos_application/errors/
use_case_error.rs

1//! Umbrella use-case error.
2//!
3//! Use cases depend on multiple ports simultaneously; rather than forcing each
4//! call site to enumerate every error leaf, `UseCaseError` aggregates the
5//! three port-specific errors (plus domain errors) via `#[from]` conversions.
6//! The variants preserve the original error for inspection via `downcast_ref`
7//! or pattern matching.
8
9use thiserror::Error;
10
11use crate::errors::{ProviderError, RepoError, UpstreamError};
12use crate::helpers::person_router::RouteError;
13
14/// Top-level error returned by use cases in later slices.
15#[derive(Debug, Error)]
16pub enum UseCaseError {
17    #[error(transparent)]
18    Repo(#[from] RepoError),
19
20    #[error(transparent)]
21    Provider(#[from] ProviderError),
22
23    #[error(transparent)]
24    Upstream(#[from] UpstreamError),
25
26    #[error(transparent)]
27    Domain(#[from] smos_domain::DomainError),
28
29    /// Routing-layer error returned by `route_request`. Mapped to 400 by
30    /// the HTTP layer when the requested person is unknown or carries an
31    /// unsafe name, and to 502 when the person references a provider that
32    /// is missing from the `[[providers]]` array (a config-level mistake).
33    #[error(transparent)]
34    Route(#[from] RouteError),
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::helpers::person_router::PersonEntry;
41    use std::collections::HashMap;
42
43    #[test]
44    fn repo_error_converts_via_from() {
45        let repo_err = RepoError::QueryFailed("boom".into());
46        let use_case: UseCaseError = repo_err.into();
47        assert!(matches!(
48            use_case,
49            UseCaseError::Repo(RepoError::QueryFailed(_))
50        ));
51    }
52
53    #[test]
54    fn provider_error_converts_via_from() {
55        let provider_err = ProviderError::Unavailable("down".into());
56        let use_case: UseCaseError = provider_err.into();
57        assert!(matches!(
58            use_case,
59            UseCaseError::Provider(ProviderError::Unavailable(_))
60        ));
61    }
62
63    #[test]
64    fn upstream_error_converts_via_from() {
65        let upstream_err = UpstreamError::ConnectFailed("refused".into());
66        let use_case: UseCaseError = upstream_err.into();
67        assert!(matches!(
68            use_case,
69            UseCaseError::Upstream(UpstreamError::ConnectFailed(_))
70        ));
71    }
72
73    #[test]
74    fn route_error_converts_via_from() {
75        let route_err = RouteError::UnknownPerson("ghost".into());
76        let use_case: UseCaseError = route_err.into();
77        assert!(matches!(
78            use_case,
79            UseCaseError::Route(RouteError::UnknownPerson(_))
80        ));
81    }
82
83    /// Compile-time guard: the routing plumbing (`HandleChatCompletion`)
84    /// expects the existence of an empty `HashMap<String, PersonEntry>`
85    /// constructor path so it can build mock persons without depending on
86    /// the adapter config crate.
87    #[test]
88    fn person_entry_map_can_be_constructed_empty() {
89        let _map: HashMap<String, PersonEntry> = HashMap::new();
90    }
91}