ddd_toolkit_core/application/
usecase.rs1#[trait_variant::make(Send)]
4pub trait UseCase: Send + Sync {
5 type Input: Send;
7 type Output: Send;
9 type Error: std::error::Error + Send + Sync + 'static;
11
12 async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error>;
14}
15
16#[cfg(test)]
17mod test {
18 use std::fmt::Display;
19
20 use crate::testing::block_on;
21
22 use super::*;
23
24 #[derive(Debug, PartialEq)]
25 struct FooError {
26 message: String,
27 }
28
29 impl Display for FooError {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 write!(f, "{}", self.message)
32 }
33 }
34
35 impl std::error::Error for FooError {}
36
37 struct FooUseCase;
38
39 impl UseCase for FooUseCase {
40 type Input = String;
41 type Output = String;
42 type Error = FooError;
43
44 async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
45 Ok(format!("hello, {input}"))
46 }
47 }
48
49 struct CountingUseCase {
50 calls: std::sync::Mutex<usize>,
51 }
52
53 impl UseCase for CountingUseCase {
54 type Input = ();
55 type Output = usize;
56 type Error = FooError;
57
58 async fn execute(&self, _input: Self::Input) -> Result<Self::Output, Self::Error> {
59 let mut calls = self.calls.lock().expect("lock should not be poisoned");
60 *calls += 1;
61 Ok(*calls)
62 }
63 }
64
65 struct FailingUseCase;
66
67 impl UseCase for FailingUseCase {
68 type Input = String;
69 type Output = String;
70 type Error = FooError;
71
72 async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
73 Err(FooError {
74 message: format!("failed to execute with {input}"),
75 })
76 }
77 }
78
79 struct UnitUseCase;
80
81 impl UseCase for UnitUseCase {
82 type Input = ();
83 type Output = ();
84 type Error = FooError;
85
86 async fn execute(&self, _input: Self::Input) -> Result<Self::Output, Self::Error> {
87 Ok(())
88 }
89 }
90
91 #[test]
92 fn execute_returns_ok_output() {
93 let use_case = FooUseCase;
94
95 let output =
96 block_on(use_case.execute("world".to_string())).expect("execute should succeed");
97
98 assert_eq!(output, "hello, world");
99 }
100
101 #[test]
102 fn execute_with_shared_reference_allows_multiple_calls() {
103 let use_case = CountingUseCase {
104 calls: std::sync::Mutex::new(0),
105 };
106
107 let first = block_on(use_case.execute(())).expect("first call should succeed");
108 let second = block_on(use_case.execute(())).expect("second call should succeed");
109
110 assert_eq!(first, 1);
111 assert_eq!(second, 2);
112 }
113
114 #[test]
117 fn use_case_is_usable_through_generic_bound() {
118 fn run<U: UseCase>(use_case: &U, input: U::Input) -> Result<U::Output, U::Error> {
119 block_on(use_case.execute(input))
120 }
121
122 let output =
123 run(&FooUseCase, "generic".to_string()).expect("generic execute should succeed");
124
125 assert_eq!(output, "hello, generic");
126 }
127
128 #[test]
132 fn execute_future_is_send() {
133 fn assert_send(_: impl Send) {}
134
135 fn check<U: UseCase>(use_case: &U, input: U::Input) {
136 assert_send(use_case.execute(input));
137 }
138
139 check(&FooUseCase, "send".to_string());
140 }
141
142 #[test]
143 fn execute_returns_err_from_failing_use_case() {
144 let use_case = FailingUseCase;
145
146 let error = block_on(use_case.execute("bad".to_string())).expect_err("execute should fail");
147
148 assert_eq!(
149 error,
150 FooError {
151 message: "failed to execute with bad".to_string()
152 }
153 );
154 assert_eq!(error.to_string(), "failed to execute with bad");
155 }
156
157 #[test]
160 fn error_can_be_boxed_as_dyn_error() {
161 fn boxed<U: UseCase>(error: U::Error) -> Box<dyn std::error::Error + Send + Sync> {
162 Box::new(error)
163 }
164
165 let error =
166 block_on(FailingUseCase.execute("bad".to_string())).expect_err("execute should fail");
167
168 let boxed = boxed::<FailingUseCase>(error);
169
170 assert_eq!(boxed.to_string(), "failed to execute with bad");
171 }
172
173 #[test]
174 fn execute_works_with_unit_input_and_output() {
175 let () = block_on(UnitUseCase.execute(())).expect("execute should succeed");
176 }
177}