Skip to main content

ddd_toolkit_core/application/
usecase.rs

1/// A single, named application operation that orchestrates domain objects
2/// and ports to do one thing.
3///
4/// # Examples
5///
6/// ```
7/// use ddd_toolkit_core::application::usecase::UseCase;
8/// use std::fmt::Display;
9///
10/// # fn block_on<F: std::future::Future>(future: F) -> F::Output {
11/// #     let mut future = std::pin::pin!(future);
12/// #     let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
13/// #     loop {
14/// #         if let std::task::Poll::Ready(output) = future.as_mut().poll(&mut cx) {
15/// #             return output;
16/// #         }
17/// #     }
18/// # }
19/// #
20/// #[derive(Debug)]
21/// struct GreetError;
22///
23/// impl Display for GreetError {
24///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25///         write!(f, "name must not be empty")
26///     }
27/// }
28///
29/// impl std::error::Error for GreetError {}
30///
31/// struct Greet;
32///
33/// impl UseCase for Greet {
34///     type Input = String;
35///     type Output = String;
36///     type Error = GreetError;
37///
38///     async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
39///         if input.trim().is_empty() {
40///             return Err(GreetError);
41///         }
42///         Ok(format!("hello, {input}"))
43///     }
44/// }
45///
46/// let greeting = block_on(Greet.execute("world".to_string()))?;
47/// assert_eq!(greeting, "hello, world");
48///
49/// assert!(block_on(Greet.execute(String::new())).is_err());
50/// # Ok::<(), GreetError>(())
51/// ```
52#[trait_variant::make(Send)]
53pub trait UseCase: Send + Sync {
54    /// The input required to run this use case.
55    type Input: Send;
56    /// What this use case produces on success.
57    type Output: Send;
58    /// What this use case produces on failure.
59    type Error: std::error::Error + Send + Sync + 'static;
60
61    /// Runs the use case.
62    async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error>;
63}
64
65#[cfg(test)]
66mod test {
67    use std::fmt::Display;
68
69    use crate::testing::block_on;
70
71    use super::*;
72
73    #[derive(Debug, PartialEq)]
74    struct FooError {
75        message: String,
76    }
77
78    impl Display for FooError {
79        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80            write!(f, "{}", self.message)
81        }
82    }
83
84    impl std::error::Error for FooError {}
85
86    struct FooUseCase;
87
88    impl UseCase for FooUseCase {
89        type Input = String;
90        type Output = String;
91        type Error = FooError;
92
93        async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
94            Ok(format!("hello, {input}"))
95        }
96    }
97
98    struct CountingUseCase {
99        calls: std::sync::Mutex<usize>,
100    }
101
102    impl UseCase for CountingUseCase {
103        type Input = ();
104        type Output = usize;
105        type Error = FooError;
106
107        async fn execute(&self, _input: Self::Input) -> Result<Self::Output, Self::Error> {
108            let mut calls = self.calls.lock().expect("lock should not be poisoned");
109            *calls += 1;
110            Ok(*calls)
111        }
112    }
113
114    struct FailingUseCase;
115
116    impl UseCase for FailingUseCase {
117        type Input = String;
118        type Output = String;
119        type Error = FooError;
120
121        async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
122            Err(FooError {
123                message: format!("failed to execute with {input}"),
124            })
125        }
126    }
127
128    struct UnitUseCase;
129
130    impl UseCase for UnitUseCase {
131        type Input = ();
132        type Output = ();
133        type Error = FooError;
134
135        async fn execute(&self, _input: Self::Input) -> Result<Self::Output, Self::Error> {
136            Ok(())
137        }
138    }
139
140    #[test]
141    fn execute_returns_ok_output() {
142        let use_case = FooUseCase;
143
144        let output =
145            block_on(use_case.execute("world".to_string())).expect("execute should succeed");
146
147        assert_eq!(output, "hello, world");
148    }
149
150    #[test]
151    fn execute_with_shared_reference_allows_multiple_calls() {
152        let use_case = CountingUseCase {
153            calls: std::sync::Mutex::new(0),
154        };
155
156        let first = block_on(use_case.execute(())).expect("first call should succeed");
157        let second = block_on(use_case.execute(())).expect("second call should succeed");
158
159        assert_eq!(first, 1);
160        assert_eq!(second, 2);
161    }
162
163    // Verifies the trait bounds are usable from generic code; the runtime
164    // assertion is secondary to the fact that this compiles.
165    #[test]
166    fn use_case_is_usable_through_generic_bound() {
167        fn run<U: UseCase>(use_case: &U, input: U::Input) -> Result<U::Output, U::Error> {
168            block_on(use_case.execute(input))
169        }
170
171        let output =
172            run(&FooUseCase, "generic".to_string()).expect("generic execute should succeed");
173
174        assert_eq!(output, "hello, generic");
175    }
176
177    // Compile-time only (no runtime assertion): the generic bound accepts the
178    // future as `Send` purely because `#[trait_variant::make(Send)]` promises
179    // it at the trait level, not because of the concrete impl.
180    #[test]
181    fn execute_future_is_send() {
182        fn assert_send(_: impl Send) {}
183
184        fn check<U: UseCase>(use_case: &U, input: U::Input) {
185            assert_send(use_case.execute(input));
186        }
187
188        check(&FooUseCase, "send".to_string());
189    }
190
191    #[test]
192    fn execute_returns_err_from_failing_use_case() {
193        let use_case = FailingUseCase;
194
195        let error = block_on(use_case.execute("bad".to_string())).expect_err("execute should fail");
196
197        assert_eq!(
198            error,
199            FooError {
200                message: "failed to execute with bad".to_string()
201            }
202        );
203        assert_eq!(error.to_string(), "failed to execute with bad");
204    }
205
206    // Verifies the `Error: std::error::Error + Send + Sync + 'static` bound is
207    // strong enough for the usual boxed-error upcast.
208    #[test]
209    fn error_can_be_boxed_as_dyn_error() {
210        fn boxed<U: UseCase>(error: U::Error) -> Box<dyn std::error::Error + Send + Sync> {
211            Box::new(error)
212        }
213
214        let error =
215            block_on(FailingUseCase.execute("bad".to_string())).expect_err("execute should fail");
216
217        let boxed = boxed::<FailingUseCase>(error);
218
219        assert_eq!(boxed.to_string(), "failed to execute with bad");
220    }
221
222    #[test]
223    fn execute_works_with_unit_input_and_output() {
224        let () = block_on(UnitUseCase.execute(())).expect("execute should succeed");
225    }
226}