ddd_toolkit_core/application/
usecase.rs1#[trait_variant::make(Send)]
53pub trait UseCase: Send + Sync {
54 type Input: Send;
56 type Output: Send;
58 type Error: std::error::Error + Send + Sync + 'static;
60
61 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 #[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 #[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 #[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}