1use async_trait::async_trait;
22use std::marker::PhantomData;
23use std::sync::Arc;
24
25use crate::validation::ValidationErrors;
26
27#[derive(Debug)]
31pub enum UseCaseError {
32 Validation(ValidationErrors),
33 NotFound(String),
34 Forbidden(String),
35 Conflict(String),
36 Internal(String),
37}
38
39impl std::fmt::Display for UseCaseError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 UseCaseError::Validation(errs) => write!(f, "validation failed: {errs}"),
43 UseCaseError::NotFound(msg) => write!(f, "not found: {msg}"),
44 UseCaseError::Forbidden(msg) => write!(f, "forbidden: {msg}"),
45 UseCaseError::Conflict(msg) => write!(f, "conflict: {msg}"),
46 UseCaseError::Internal(msg) => write!(f, "internal error: {msg}"),
47 }
48 }
49}
50
51impl std::error::Error for UseCaseError {}
52
53impl From<ValidationErrors> for UseCaseError {
54 fn from(errs: ValidationErrors) -> Self {
55 UseCaseError::Validation(errs)
56 }
57}
58
59pub type UseCaseResult<T> = Result<T, UseCaseError>;
60
61#[async_trait]
68pub trait UseCaseHooks<E: Send + Sync + 'static, DTO: Send + Sync + 'static>: Send + Sync {
69 async fn before_create(&self, dto: &mut DTO) -> UseCaseResult<()> {
71 let _ = dto;
72 Ok(())
73 }
74
75 async fn after_build(&self, entity: &mut E) -> UseCaseResult<()> {
77 let _ = entity;
78 Ok(())
79 }
80
81 async fn after_create(&self, entity: &E) -> UseCaseResult<()> {
83 let _ = entity;
84 Ok(())
85 }
86
87 async fn before_update(&self, entity: &E, dto: &mut DTO) -> UseCaseResult<()> {
89 let _ = (entity, dto);
90 Ok(())
91 }
92
93 async fn after_update(&self, entity: &E) -> UseCaseResult<()> {
95 let _ = entity;
96 Ok(())
97 }
98
99 async fn before_delete(&self, entity: &E) -> UseCaseResult<()> {
101 let _ = entity;
102 Ok(())
103 }
104
105 async fn after_delete(&self, id: &str) -> UseCaseResult<()> {
107 let _ = id;
108 Ok(())
109 }
110}
111
112pub struct DefaultHooks<E, DTO> {
114 _phantom: PhantomData<(E, DTO)>,
115}
116
117impl<E, DTO> DefaultHooks<E, DTO> {
118 pub fn new() -> Self {
119 Self {
120 _phantom: PhantomData,
121 }
122 }
123}
124
125impl<E, DTO> Default for DefaultHooks<E, DTO> {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[async_trait]
132impl<E: Send + Sync + 'static, DTO: Send + Sync + 'static> UseCaseHooks<E, DTO>
133 for DefaultHooks<E, DTO>
134{
135 }
137
138#[async_trait]
145pub trait UseCaseService<E: Send + Sync + 'static>: Send + Sync {
146 async fn find_by_id(&self, id: &str) -> UseCaseResult<Option<E>>;
147 async fn persist(&self, entity: E) -> UseCaseResult<E>;
148 async fn remove(&self, id: &str) -> UseCaseResult<()>;
149}
150
151pub trait EntityFactory<E, DTO>: Send + Sync {
158 fn build(&self, dto: DTO) -> UseCaseResult<E>;
159}
160
161pub trait EntityUpdater<E, DTO>: Send + Sync {
163 fn apply(&self, entity: E, dto: DTO) -> UseCaseResult<E>;
164}
165
166pub struct CreateUseCase<E, DTO, S> {
174 service: Arc<S>,
175 factory: Arc<dyn EntityFactory<E, DTO>>,
176 hooks: Arc<dyn UseCaseHooks<E, DTO>>,
177 _phantom: PhantomData<(E, DTO)>,
178}
179
180impl<E, DTO, S> CreateUseCase<E, DTO, S>
181where
182 E: Send + Sync + Clone + 'static,
183 DTO: Send + Sync + 'static,
184 S: UseCaseService<E>,
185{
186 pub fn new(
187 service: Arc<S>,
188 factory: Arc<dyn EntityFactory<E, DTO>>,
189 hooks: Arc<dyn UseCaseHooks<E, DTO>>,
190 ) -> Self {
191 Self {
192 service,
193 factory,
194 hooks,
195 _phantom: PhantomData,
196 }
197 }
198
199 pub fn with_default_hooks(
200 service: Arc<S>,
201 factory: Arc<dyn EntityFactory<E, DTO>>,
202 ) -> Self
203 where
204 DTO: 'static,
205 {
206 Self::new(service, factory, Arc::new(DefaultHooks::new()))
207 }
208
209 pub async fn execute(&self, mut dto: DTO) -> UseCaseResult<E> {
210 self.hooks.before_create(&mut dto).await?;
211 let mut entity = self.factory.build(dto)?;
212 self.hooks.after_build(&mut entity).await?;
213 let entity = self.service.persist(entity).await?;
214 self.hooks.after_create(&entity).await?;
215 Ok(entity)
216 }
217}
218
219pub struct UpdateUseCase<E, DTO, S> {
223 service: Arc<S>,
224 updater: Arc<dyn EntityUpdater<E, DTO>>,
225 hooks: Arc<dyn UseCaseHooks<E, DTO>>,
226 _phantom: PhantomData<(E, DTO)>,
227}
228
229impl<E, DTO, S> UpdateUseCase<E, DTO, S>
230where
231 E: Send + Sync + Clone + 'static,
232 DTO: Send + Sync + Clone + 'static,
233 S: UseCaseService<E>,
234{
235 pub fn new(
236 service: Arc<S>,
237 updater: Arc<dyn EntityUpdater<E, DTO>>,
238 hooks: Arc<dyn UseCaseHooks<E, DTO>>,
239 ) -> Self {
240 Self {
241 service,
242 updater,
243 hooks,
244 _phantom: PhantomData,
245 }
246 }
247
248 pub async fn execute(&self, id: &str, mut dto: DTO) -> UseCaseResult<E> {
249 let entity = self
250 .service
251 .find_by_id(id)
252 .await?
253 .ok_or_else(|| UseCaseError::NotFound(id.to_string()))?;
254
255 self.hooks.before_update(&entity, &mut dto).await?;
256 let updated = self.updater.apply(entity, dto)?;
257 let persisted = self.service.persist(updated).await?;
258 self.hooks.after_update(&persisted).await?;
259 Ok(persisted)
260 }
261}
262
263pub struct GetUseCase<E, S> {
267 service: Arc<S>,
268 _phantom: PhantomData<E>,
269}
270
271impl<E, S> GetUseCase<E, S>
272where
273 E: Send + Sync + 'static,
274 S: UseCaseService<E>,
275{
276 pub fn new(service: Arc<S>) -> Self {
277 Self {
278 service,
279 _phantom: PhantomData,
280 }
281 }
282
283 pub async fn execute(&self, id: &str) -> UseCaseResult<E> {
284 self.service
285 .find_by_id(id)
286 .await?
287 .ok_or_else(|| UseCaseError::NotFound(id.to_string()))
288 }
289}
290
291pub struct DeleteUseCase<E, S> {
295 service: Arc<S>,
296 hooks: Arc<dyn UseCaseHooks<E, ()>>,
297 _phantom: PhantomData<E>,
298}
299
300impl<E, S> DeleteUseCase<E, S>
301where
302 E: Send + Sync + Clone + 'static,
303 S: UseCaseService<E>,
304{
305 pub fn new(service: Arc<S>, hooks: Arc<dyn UseCaseHooks<E, ()>>) -> Self {
306 Self {
307 service,
308 hooks,
309 _phantom: PhantomData,
310 }
311 }
312
313 pub fn with_default_hooks(service: Arc<S>) -> Self {
314 Self::new(service, Arc::new(DefaultHooks::new()))
315 }
316
317 pub async fn execute(&self, id: &str) -> UseCaseResult<()> {
318 let entity = self
319 .service
320 .find_by_id(id)
321 .await?
322 .ok_or_else(|| UseCaseError::NotFound(id.to_string()))?;
323
324 self.hooks.before_delete(&entity).await?;
325 self.service.remove(id).await?;
326 self.hooks.after_delete(id).await?;
327 Ok(())
328 }
329}
330
331#[derive(Debug, Clone)]
335pub struct ListParams {
336 pub page: u64,
337 pub limit: u64,
338 pub filters: std::collections::HashMap<String, String>,
339}
340
341impl ListParams {
342 pub fn new(page: u64, limit: u64) -> Self {
343 Self {
344 page,
345 limit,
346 filters: Default::default(),
347 }
348 }
349
350 pub fn with_filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
351 self.filters.insert(key.into(), value.into());
352 self
353 }
354}
355
356impl Default for ListParams {
357 fn default() -> Self {
358 Self::new(1, 20)
359 }
360}
361
362#[derive(Debug, Clone)]
364pub struct ListResult<E> {
365 pub items: Vec<E>,
366 pub total: u64,
367 pub page: u64,
368 pub limit: u64,
369}
370
371impl<E> ListResult<E> {
372 pub fn new(items: Vec<E>, total: u64, page: u64, limit: u64) -> Self {
373 Self {
374 items,
375 total,
376 page,
377 limit,
378 }
379 }
380
381 pub fn total_pages(&self) -> u64 {
382 if self.limit == 0 {
383 return 0;
384 }
385 (self.total + self.limit - 1) / self.limit
386 }
387}
388
389#[async_trait]
391pub trait ListService<E: Send + Sync + 'static>: Send + Sync {
392 async fn list(&self, params: ListParams) -> UseCaseResult<ListResult<E>>;
393}
394
395pub struct ListUseCase<E, S> {
397 service: Arc<S>,
398 _phantom: PhantomData<E>,
399}
400
401impl<E, S> ListUseCase<E, S>
402where
403 E: Send + Sync + 'static,
404 S: ListService<E>,
405{
406 pub fn new(service: Arc<S>) -> Self {
407 Self {
408 service,
409 _phantom: PhantomData,
410 }
411 }
412
413 pub async fn execute(&self, params: ListParams) -> UseCaseResult<ListResult<E>> {
414 self.service.list(params).await
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[derive(Debug, Clone, PartialEq)]
423 struct Foo {
424 id: String,
425 name: String,
426 }
427
428 #[derive(Debug, Clone)]
429 struct CreateFooDto {
430 name: String,
431 }
432
433 struct FooFactory;
434 impl EntityFactory<Foo, CreateFooDto> for FooFactory {
435 fn build(&self, dto: CreateFooDto) -> UseCaseResult<Foo> {
436 Ok(Foo {
437 id: "new-id".into(),
438 name: dto.name,
439 })
440 }
441 }
442
443 struct FooService {
444 store: tokio::sync::Mutex<Vec<Foo>>,
445 }
446
447 impl FooService {
448 fn new() -> Self {
449 Self {
450 store: tokio::sync::Mutex::new(Vec::new()),
451 }
452 }
453 }
454
455 #[async_trait]
456 impl UseCaseService<Foo> for FooService {
457 async fn find_by_id(&self, id: &str) -> UseCaseResult<Option<Foo>> {
458 let store = self.store.lock().await;
459 Ok(store.iter().find(|f| f.id == id).cloned())
460 }
461
462 async fn persist(&self, entity: Foo) -> UseCaseResult<Foo> {
463 let mut store = self.store.lock().await;
464 store.retain(|f| f.id != entity.id);
465 store.push(entity.clone());
466 Ok(entity)
467 }
468
469 async fn remove(&self, id: &str) -> UseCaseResult<()> {
470 let mut store = self.store.lock().await;
471 store.retain(|f| f.id != id);
472 Ok(())
473 }
474 }
475
476 #[tokio::test]
477 async fn create_use_case_persists_entity() {
478 let service = Arc::new(FooService::new());
479 let use_case =
480 CreateUseCase::with_default_hooks(service.clone(), Arc::new(FooFactory));
481
482 let result = use_case
483 .execute(CreateFooDto {
484 name: "hello".into(),
485 })
486 .await
487 .unwrap();
488
489 assert_eq!(result.name, "hello");
490
491 let found = service.find_by_id("new-id").await.unwrap();
492 assert!(found.is_some());
493 }
494
495 #[tokio::test]
496 async fn get_use_case_returns_not_found() {
497 let service = Arc::new(FooService::new());
498 let use_case = GetUseCase::new(service);
499 let err = use_case.execute("missing").await.unwrap_err();
500 assert!(matches!(err, UseCaseError::NotFound(_)));
501 }
502
503 #[tokio::test]
504 async fn delete_use_case_removes_entity() {
505 let service = Arc::new(FooService::new());
506 service
507 .persist(Foo {
508 id: "x".into(),
509 name: "X".into(),
510 })
511 .await
512 .unwrap();
513
514 let use_case = DeleteUseCase::with_default_hooks(service.clone());
515 use_case.execute("x").await.unwrap();
516
517 assert!(service.find_by_id("x").await.unwrap().is_none());
518 }
519}