1#![warn(missing_docs)]
6
7use std::{collections::HashMap, fmt, sync::Arc};
8
9use axum::{
10 body::Body,
11 extract::FromRequestParts,
12 http::{StatusCode, request::Parts},
13 response::{IntoResponse, Response},
14};
15
16#[derive(Default)]
20pub struct Dependencies {
21 services: HashMap<String, Box<dyn std::any::Any + Send + Sync>>,
22}
23
24impl Dependencies {
25 pub fn new() -> Self {
27 Self { services: HashMap::new() }
28 }
29
30 pub fn register<T: Send + Sync + 'static>(&mut self, name: &str, service: T) {
32 self.services.insert(name.to_string(), Box::new(service));
33 }
34
35 pub fn get<T: Clone + Send + Sync + 'static>(&self, name: &str) -> Result<T, EffectError> {
37 self.services
38 .get(name)
39 .and_then(|s| s.downcast_ref::<T>())
40 .cloned()
41 .ok_or_else(|| EffectError::NotFound(name.to_string()))
42 }
43}
44
45pub struct Effectful {
49 deps: Arc<Dependencies>,
50 parts: Parts,
51}
52
53impl Effectful {
54 pub fn new(deps: Arc<Dependencies>, parts: Parts) -> Self {
56 Self { deps, parts }
57 }
58
59 pub fn get<T: Clone + Send + Sync + 'static>(&self, name: &str) -> Result<T, EffectError> {
61 self.deps.get(name)
62 }
63
64 pub fn header(&self, name: &str) -> Option<&str> {
66 self.parts.headers.get(name).and_then(|v| v.to_str().ok())
67 }
68}
69
70impl<S> FromRequestParts<S> for Effectful
72where
73 S: Send + Sync,
74{
75 type Rejection = Response<Body>;
76
77 fn from_request_parts(
78 parts: &mut Parts,
79 _state: &S,
80 ) -> impl std::future::Future<Output = Result<Self, Self::Rejection>> + Send {
81 let deps = Arc::new(Dependencies::new());
82 async move { Ok(Effectful::new(deps, parts.clone())) }
83 }
84}
85
86pub struct AlgebraicEffect {
90 deps: Dependencies,
91}
92
93impl Default for AlgebraicEffect {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99impl AlgebraicEffect {
100 pub fn new() -> Self {
102 Self { deps: Dependencies::new() }
103 }
104
105 pub fn with<T: Send + Sync + 'static>(mut self, name: &str, service: T) -> Self {
107 self.deps.register(name, service);
108 self
109 }
110
111 pub fn build(self) -> Arc<Dependencies> {
113 Arc::new(self.deps)
114 }
115}
116
117#[derive(Debug)]
119pub enum EffectError {
120 NotFound(String),
122
123 TypeMismatch {
125 name: String,
127 expected: String,
129 actual: String,
131 },
132
133 ConfigError(wae_config::ConfigError),
135
136 DatabaseError(String),
138}
139
140impl fmt::Display for EffectError {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 EffectError::NotFound(msg) => write!(f, "Dependency not found: {}", msg),
144 EffectError::TypeMismatch { name, expected, actual } => {
145 write!(f, "Type mismatch for {}: expected {}, got {}", name, expected, actual)
146 }
147 EffectError::ConfigError(err) => write!(f, "Config error: {}", err),
148 EffectError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
149 }
150 }
151}
152
153impl std::error::Error for EffectError {}
154
155impl From<wae_config::ConfigError> for EffectError {
156 fn from(err: wae_config::ConfigError) -> Self {
157 EffectError::ConfigError(err)
158 }
159}
160
161impl IntoResponse for EffectError {
162 fn into_response(self) -> Response {
163 let (status, message) = match &self {
164 EffectError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
165 EffectError::TypeMismatch { .. } => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
166 EffectError::ConfigError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
167 EffectError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
168 };
169 (status, message).into_response()
170 }
171}