1use async_trait::async_trait;
4use std::any::TypeId;
5
6pub use dependency_injector::{Injectable, Provider};
8
9#[async_trait]
11pub trait Controller: Send + Sync + 'static {
12 fn base_path(&self) -> &'static str;
14
15 fn routes(&self) -> Vec<RouteDefinition>;
17}
18
19pub trait Module: Send + Sync + 'static {
21 fn module_type_id(&self) -> std::any::TypeId {
36 std::any::TypeId::of::<Self>()
37 }
38
39 fn module_type_name(&self) -> &'static str {
47 std::any::type_name::<Self>()
48 }
49
50 fn providers(&self) -> Vec<ProviderRegistration>;
52
53 fn controllers(&self) -> Vec<ControllerRegistration>;
55
56 fn guards(&self) -> Vec<crate::module::GuardRegistration> {
58 vec![]
59 }
60
61 fn imports(&self) -> Vec<Box<dyn Module>>;
63
64 fn exports(&self) -> Vec<TypeId>;
66
67 fn re_exports(&self) -> Vec<Box<dyn Module>> {
72 vec![]
73 }
74}
75
76#[async_trait]
78pub trait RequestHandler: Send + Sync {
79 async fn handle(
81 &self,
82 request: crate::HttpRequest,
83 ) -> Result<crate::HttpResponse, crate::Error>;
84}
85
86pub trait Validator: Send + Sync {
88 fn validate(&self, value: &str) -> Result<(), String>;
90}
91
92#[derive(Clone, Debug)]
94pub struct RouteDefinition {
95 pub method: HttpMethod,
96 pub path: String,
97 pub handler_name: String,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102#[non_exhaustive]
103pub enum HttpMethod {
104 GET,
105 POST,
106 PUT,
107 DELETE,
108 PATCH,
109 HEAD,
110 OPTIONS,
111 QUERY,
114}
115
116impl HttpMethod {
117 #[allow(clippy::should_implement_trait)]
118 pub fn from_str(s: &str) -> Option<Self> {
119 match s.to_uppercase().as_str() {
120 "GET" => Some(HttpMethod::GET),
121 "POST" => Some(HttpMethod::POST),
122 "PUT" => Some(HttpMethod::PUT),
123 "DELETE" => Some(HttpMethod::DELETE),
124 "PATCH" => Some(HttpMethod::PATCH),
125 "HEAD" => Some(HttpMethod::HEAD),
126 "OPTIONS" => Some(HttpMethod::OPTIONS),
127 "QUERY" => Some(HttpMethod::QUERY),
128 _ => None,
129 }
130 }
131
132 pub fn as_str(&self) -> &'static str {
133 match self {
134 HttpMethod::GET => "GET",
135 HttpMethod::POST => "POST",
136 HttpMethod::PUT => "PUT",
137 HttpMethod::DELETE => "DELETE",
138 HttpMethod::PATCH => "PATCH",
139 HttpMethod::HEAD => "HEAD",
140 HttpMethod::OPTIONS => "OPTIONS",
141 HttpMethod::QUERY => "QUERY",
142 }
143 }
144}
145
146#[derive(Clone)]
148pub struct ProviderRegistration {
149 pub type_id: TypeId,
150 pub type_name: &'static str,
151 pub register_fn: fn(&crate::container::Container),
154}
155
156impl std::fmt::Debug for ProviderRegistration {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 f.debug_struct("ProviderRegistration")
159 .field("type_id", &self.type_id)
160 .field("type_name", &self.type_name)
161 .finish()
162 }
163}
164
165#[derive(Clone)]
167pub struct ControllerRegistration {
168 pub type_id: TypeId,
169 pub type_name: &'static str,
170 pub base_path: &'static str,
171 pub factory:
172 fn(&crate::Container) -> Result<Box<dyn std::any::Any + Send + Sync>, crate::Error>,
173 #[allow(clippy::type_complexity)]
174 pub route_registrar: fn(
175 &crate::Container,
176 &mut crate::Router,
177 Box<dyn std::any::Any + Send + Sync>,
178 ) -> Result<(), crate::Error>,
179}
180
181impl std::fmt::Debug for ControllerRegistration {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.debug_struct("ControllerRegistration")
184 .field("type_id", &self.type_id)
185 .field("type_name", &self.type_name)
186 .field("base_path", &self.base_path)
187 .finish()
188 }
189}
190
191impl From<HttpMethod> for crate::Method {
192 #[inline]
193 fn from(m: HttpMethod) -> Self {
194 match m {
195 HttpMethod::GET => crate::Method::Get,
196 HttpMethod::POST => crate::Method::Post,
197 HttpMethod::PUT => crate::Method::Put,
198 HttpMethod::DELETE => crate::Method::Delete,
199 HttpMethod::PATCH => crate::Method::Patch,
200 HttpMethod::HEAD => crate::Method::Head,
201 HttpMethod::OPTIONS => crate::Method::Options,
202 HttpMethod::QUERY => crate::Method::Query,
203 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct UnroutableMethod(pub String);
214
215impl std::fmt::Display for UnroutableMethod {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 write!(f, "method `{}` has no HttpMethod counterpart", self.0)
218 }
219}
220
221impl std::error::Error for UnroutableMethod {}
222
223impl TryFrom<&crate::Method> for HttpMethod {
224 type Error = UnroutableMethod;
225
226 #[inline]
227 fn try_from(m: &crate::Method) -> Result<Self, Self::Error> {
228 match m {
229 crate::Method::Get => Ok(HttpMethod::GET),
230 crate::Method::Post => Ok(HttpMethod::POST),
231 crate::Method::Put => Ok(HttpMethod::PUT),
232 crate::Method::Delete => Ok(HttpMethod::DELETE),
233 crate::Method::Patch => Ok(HttpMethod::PATCH),
234 crate::Method::Head => Ok(HttpMethod::HEAD),
235 crate::Method::Options => Ok(HttpMethod::OPTIONS),
236 crate::Method::Query => Ok(HttpMethod::QUERY),
237 crate::Method::Connect => Err(UnroutableMethod("CONNECT".into())),
238 crate::Method::Trace => Err(UnroutableMethod("TRACE".into())),
239 crate::Method::Other(t) => Err(UnroutableMethod(t.into_owned())),
240 }
241 }
242}
243
244#[cfg(test)]
245mod method_conversion_tests {
246 use super::HttpMethod;
247 use crate::Method;
248
249 #[test]
250 fn every_http_method_round_trips_through_method() {
251 for m in [
252 HttpMethod::GET,
253 HttpMethod::POST,
254 HttpMethod::PUT,
255 HttpMethod::DELETE,
256 HttpMethod::PATCH,
257 HttpMethod::HEAD,
258 HttpMethod::OPTIONS,
259 HttpMethod::QUERY,
260 ] {
261 let converted = Method::from(m.clone());
262 assert_eq!(
263 HttpMethod::try_from(&converted).ok(),
264 Some(m.clone()),
265 "{m:?} did not round-trip"
266 );
267 }
268 }
269
270 #[test]
271 fn methods_with_no_http_method_counterpart_fail_conversion() {
272 assert!(HttpMethod::try_from(&Method::Connect).is_err());
276 assert!(HttpMethod::try_from(&Method::Trace).is_err());
277 assert!(HttpMethod::try_from(&Method::from("PURGE")).is_err());
278 }
279}