Skip to main content

a3s_boot/routing/
controller.rs

1use super::handler::RouteHandler;
2use super::host::validate_host_pattern;
3use super::path::normalize_prefix;
4use super::route::RouteDefinition;
5use crate::pipeline::PipelineComponents;
6use crate::{
7    BootErrorKind, BootRequest, ExceptionFilter, ExecutionInterceptor, Guard, Interceptor,
8    Middleware, ModuleRef, Pipe, Result, RouteVersioning, SerializationOptions, SseEvent, Validate,
9};
10use futures_core::Stream;
11use serde::de::DeserializeOwned;
12use serde::Serialize;
13use serde_json::Value;
14use std::collections::BTreeMap;
15use std::future::Future;
16
17/// Group routes under a common HTTP prefix, similar to a Nest controller.
18#[derive(Clone)]
19pub struct ControllerDefinition {
20    prefix: String,
21    host: Option<String>,
22    routes: Vec<RouteDefinition>,
23    pipeline: PipelineComponents,
24    openapi_tags: Vec<String>,
25    versioning: RouteVersioning,
26    serialization: Option<SerializationOptions>,
27    metadata: BTreeMap<String, Value>,
28}
29
30impl ControllerDefinition {
31    pub fn new(prefix: impl Into<String>) -> Result<Self> {
32        let prefix = normalize_prefix(&prefix.into())?;
33        Ok(Self {
34            prefix,
35            host: None,
36            routes: Vec::new(),
37            pipeline: PipelineComponents::default(),
38            openapi_tags: Vec::new(),
39            versioning: RouteVersioning::default(),
40            serialization: None,
41            metadata: BTreeMap::new(),
42        })
43    }
44
45    pub fn route(mut self, route: RouteDefinition) -> Result<Self> {
46        let mut route = route;
47        route = route.with_host_default(self.host.as_deref())?;
48        if route.versioning().is_unspecified() && !self.versioning.is_unspecified() {
49            route = match &self.versioning {
50                RouteVersioning::Unspecified => route,
51                RouteVersioning::Versions(versions) => route.with_versions(versions.clone()),
52                RouteVersioning::Neutral => route.version_neutral(),
53            };
54        }
55        if route.serialization().is_empty() {
56            if let Some(serialization) = &self.serialization {
57                route = route.with_serialization(serialization.clone());
58            }
59        }
60        for tag in &self.openapi_tags {
61            route = route.with_tag(tag.clone());
62        }
63        route = route.with_metadata_defaults(&self.metadata);
64        self.routes.push(
65            route
66                .with_prefix(&self.prefix)?
67                .with_pipeline_prefix(&self.pipeline),
68        );
69        Ok(self)
70    }
71
72    pub fn with_pipe<P>(mut self, pipe: P) -> Self
73    where
74        P: Pipe,
75    {
76        self.pipeline.push_pipe(pipe);
77        self
78    }
79
80    pub fn with_middleware<M>(mut self, middleware: M) -> Self
81    where
82        M: Middleware,
83    {
84        self.pipeline.push_middleware(middleware);
85        self
86    }
87
88    pub fn with_guard<G>(mut self, guard: G) -> Self
89    where
90        G: Guard,
91    {
92        self.pipeline.push_guard(guard);
93        self
94    }
95
96    pub fn with_interceptor<I>(mut self, interceptor: I) -> Self
97    where
98        I: Interceptor,
99    {
100        self.pipeline.push_interceptor(interceptor);
101        self
102    }
103
104    pub fn with_execution_interceptor<I>(mut self, interceptor: I) -> Self
105    where
106        I: ExecutionInterceptor,
107    {
108        self.pipeline.push_execution_interceptor(interceptor);
109        self
110    }
111
112    pub fn with_filter<F>(mut self, filter: F) -> Self
113    where
114        F: ExceptionFilter,
115    {
116        self.pipeline.push_filter(filter);
117        self
118    }
119
120    pub fn with_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
121    where
122        I: IntoIterator<Item = BootErrorKind>,
123        F: ExceptionFilter,
124    {
125        self.pipeline.push_catch_filter(kinds, filter);
126        self
127    }
128
129    pub fn with_validation(mut self) -> Self {
130        self.pipeline.enable_validation();
131        self
132    }
133
134    pub fn with_host(mut self, pattern: impl Into<String>) -> Result<Self> {
135        let pattern = pattern.into();
136        validate_host_pattern(&pattern)?;
137        self.host = Some(pattern.clone());
138        self.routes = self
139            .routes
140            .into_iter()
141            .map(|route| route.with_host_default(Some(&pattern)))
142            .collect::<Result<Vec<_>>>()?;
143        Ok(self)
144    }
145
146    pub fn without_host(mut self) -> Self {
147        self.host = None;
148        self.routes = self
149            .routes
150            .into_iter()
151            .map(RouteDefinition::without_host)
152            .collect();
153        self
154    }
155
156    pub fn host(&self) -> Option<&str> {
157        self.host.as_deref()
158    }
159
160    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
161        let tag = tag.into();
162        if !self.openapi_tags.contains(&tag) {
163            self.openapi_tags.push(tag.clone());
164        }
165        self.routes = self
166            .routes
167            .into_iter()
168            .map(|route| route.with_tag(tag.clone()))
169            .collect();
170        self
171    }
172
173    pub fn with_version(mut self, version: impl Into<String>) -> Self {
174        self.versioning = RouteVersioning::version(version);
175        self
176    }
177
178    pub fn with_versions<I, V>(mut self, versions: I) -> Self
179    where
180        I: IntoIterator<Item = V>,
181        V: Into<String>,
182    {
183        self.versioning = RouteVersioning::versions(versions);
184        self
185    }
186
187    pub fn version_neutral(mut self) -> Self {
188        self.versioning = RouteVersioning::neutral();
189        self
190    }
191
192    pub fn with_serialization(mut self, options: SerializationOptions) -> Self {
193        self.serialization = Some(options);
194        self
195    }
196
197    pub fn with_metadata<V>(self, key: impl Into<String>, value: V) -> Result<Self>
198    where
199        V: Serialize,
200    {
201        let key = key.into();
202        let value = serde_json::to_value(value).map_err(|error| {
203            crate::BootError::Internal(format!(
204                "failed to serialize controller metadata `{key}`: {error}"
205            ))
206        })?;
207        Ok(self.with_metadata_value(key, value))
208    }
209
210    pub fn with_metadata_value(mut self, key: impl Into<String>, value: Value) -> Self {
211        let key = key.into();
212        self.metadata.insert(key.clone(), value.clone());
213        self.routes = self
214            .routes
215            .into_iter()
216            .map(|route| route.with_metadata_default_value(key.clone(), value.clone()))
217            .collect();
218        self
219    }
220
221    pub fn metadata(&self) -> &BTreeMap<String, Value> {
222        &self.metadata
223    }
224
225    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
226        self.metadata.get(key)
227    }
228
229    pub fn all<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
230    where
231        H: RouteHandler,
232    {
233        self.route(RouteDefinition::all(path, handler)?)
234    }
235
236    pub fn all_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
237    where
238        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
239        H: RouteHandler,
240    {
241        self.route(RouteDefinition::all_scoped(path, factory)?)
242    }
243
244    pub fn all_json<H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
245    where
246        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
247        Fut: Future<Output = Result<R>> + Send + 'static,
248        R: Serialize + Send + 'static,
249    {
250        self.all_json_with_status(path, 200, handler)
251    }
252
253    pub fn all_json_with_status<H, Fut, R>(
254        self,
255        path: impl Into<String>,
256        status: u16,
257        handler: H,
258    ) -> Result<Self>
259    where
260        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
261        Fut: Future<Output = Result<R>> + Send + 'static,
262        R: Serialize + Send + 'static,
263    {
264        self.route(RouteDefinition::all_json_with_status(
265            path, status, handler,
266        )?)
267    }
268
269    pub fn get<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
270    where
271        H: RouteHandler,
272    {
273        self.route(RouteDefinition::get(path, handler)?)
274    }
275
276    pub fn get_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
277    where
278        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
279        H: RouteHandler,
280    {
281        self.route(RouteDefinition::get_scoped(path, factory)?)
282    }
283
284    pub fn get_json<H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
285    where
286        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
287        Fut: Future<Output = Result<R>> + Send + 'static,
288        R: Serialize + Send + 'static,
289    {
290        self.get_json_with_status(path, 200, handler)
291    }
292
293    pub fn get_json_with_status<H, Fut, R>(
294        self,
295        path: impl Into<String>,
296        status: u16,
297        handler: H,
298    ) -> Result<Self>
299    where
300        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
301        Fut: Future<Output = Result<R>> + Send + 'static,
302        R: Serialize + Send + 'static,
303    {
304        self.route(RouteDefinition::get_json_with_status(
305            path, status, handler,
306        )?)
307    }
308
309    pub fn sse<H, Fut, S>(self, path: impl Into<String>, handler: H) -> Result<Self>
310    where
311        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
312        Fut: Future<Output = Result<S>> + Send + 'static,
313        S: Stream<Item = Result<SseEvent>> + Send + 'static,
314    {
315        self.route(RouteDefinition::sse(path, handler)?)
316    }
317
318    pub fn post<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
319    where
320        H: RouteHandler,
321    {
322        self.route(RouteDefinition::post(path, handler)?)
323    }
324
325    pub fn post_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
326    where
327        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
328        H: RouteHandler,
329    {
330        self.route(RouteDefinition::post_scoped(path, factory)?)
331    }
332
333    pub fn post_json<T, H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
334    where
335        T: DeserializeOwned + Send + 'static,
336        H: Fn(T) -> Fut + Send + Sync + 'static,
337        Fut: Future<Output = Result<R>> + Send + 'static,
338        R: Serialize + Send + 'static,
339    {
340        self.post_json_with_status(path, 200, handler)
341    }
342
343    pub fn post_json_with_status<T, H, Fut, R>(
344        self,
345        path: impl Into<String>,
346        status: u16,
347        handler: H,
348    ) -> Result<Self>
349    where
350        T: DeserializeOwned + Send + 'static,
351        H: Fn(T) -> Fut + Send + Sync + 'static,
352        Fut: Future<Output = Result<R>> + Send + 'static,
353        R: Serialize + Send + 'static,
354    {
355        self.route(RouteDefinition::post_json_with_status(
356            path, status, handler,
357        )?)
358    }
359
360    pub fn post_validated_json<T, H, Fut, R>(
361        self,
362        path: impl Into<String>,
363        handler: H,
364    ) -> Result<Self>
365    where
366        T: DeserializeOwned + Validate + Send + 'static,
367        H: Fn(T) -> Fut + Send + Sync + 'static,
368        Fut: Future<Output = Result<R>> + Send + 'static,
369        R: Serialize + Send + 'static,
370    {
371        self.post_validated_json_with_status(path, 200, handler)
372    }
373
374    pub fn post_validated_json_with_status<T, H, Fut, R>(
375        self,
376        path: impl Into<String>,
377        status: u16,
378        handler: H,
379    ) -> Result<Self>
380    where
381        T: DeserializeOwned + Validate + Send + 'static,
382        H: Fn(T) -> Fut + Send + Sync + 'static,
383        Fut: Future<Output = Result<R>> + Send + 'static,
384        R: Serialize + Send + 'static,
385    {
386        self.route(RouteDefinition::post_validated_json_with_status(
387            path, status, handler,
388        )?)
389    }
390
391    pub fn put<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
392    where
393        H: RouteHandler,
394    {
395        self.route(RouteDefinition::put(path, handler)?)
396    }
397
398    pub fn put_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
399    where
400        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
401        H: RouteHandler,
402    {
403        self.route(RouteDefinition::put_scoped(path, factory)?)
404    }
405
406    pub fn put_json<T, H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
407    where
408        T: DeserializeOwned + Send + 'static,
409        H: Fn(T) -> Fut + Send + Sync + 'static,
410        Fut: Future<Output = Result<R>> + Send + 'static,
411        R: Serialize + Send + 'static,
412    {
413        self.put_json_with_status(path, 200, handler)
414    }
415
416    pub fn put_json_with_status<T, H, Fut, R>(
417        self,
418        path: impl Into<String>,
419        status: u16,
420        handler: H,
421    ) -> Result<Self>
422    where
423        T: DeserializeOwned + Send + 'static,
424        H: Fn(T) -> Fut + Send + Sync + 'static,
425        Fut: Future<Output = Result<R>> + Send + 'static,
426        R: Serialize + Send + 'static,
427    {
428        self.route(RouteDefinition::put_json_with_status(
429            path, status, handler,
430        )?)
431    }
432
433    pub fn put_validated_json<T, H, Fut, R>(
434        self,
435        path: impl Into<String>,
436        handler: H,
437    ) -> Result<Self>
438    where
439        T: DeserializeOwned + Validate + Send + 'static,
440        H: Fn(T) -> Fut + Send + Sync + 'static,
441        Fut: Future<Output = Result<R>> + Send + 'static,
442        R: Serialize + Send + 'static,
443    {
444        self.put_validated_json_with_status(path, 200, handler)
445    }
446
447    pub fn put_validated_json_with_status<T, H, Fut, R>(
448        self,
449        path: impl Into<String>,
450        status: u16,
451        handler: H,
452    ) -> Result<Self>
453    where
454        T: DeserializeOwned + Validate + Send + 'static,
455        H: Fn(T) -> Fut + Send + Sync + 'static,
456        Fut: Future<Output = Result<R>> + Send + 'static,
457        R: Serialize + Send + 'static,
458    {
459        self.route(RouteDefinition::put_validated_json_with_status(
460            path, status, handler,
461        )?)
462    }
463
464    pub fn patch_json<T, H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
465    where
466        T: DeserializeOwned + Send + 'static,
467        H: Fn(T) -> Fut + Send + Sync + 'static,
468        Fut: Future<Output = Result<R>> + Send + 'static,
469        R: Serialize + Send + 'static,
470    {
471        self.patch_json_with_status(path, 200, handler)
472    }
473
474    pub fn patch_json_with_status<T, H, Fut, R>(
475        self,
476        path: impl Into<String>,
477        status: u16,
478        handler: H,
479    ) -> Result<Self>
480    where
481        T: DeserializeOwned + Send + 'static,
482        H: Fn(T) -> Fut + Send + Sync + 'static,
483        Fut: Future<Output = Result<R>> + Send + 'static,
484        R: Serialize + Send + 'static,
485    {
486        self.route(RouteDefinition::patch_json_with_status(
487            path, status, handler,
488        )?)
489    }
490
491    pub fn patch_validated_json<T, H, Fut, R>(
492        self,
493        path: impl Into<String>,
494        handler: H,
495    ) -> Result<Self>
496    where
497        T: DeserializeOwned + Validate + Send + 'static,
498        H: Fn(T) -> Fut + Send + Sync + 'static,
499        Fut: Future<Output = Result<R>> + Send + 'static,
500        R: Serialize + Send + 'static,
501    {
502        self.patch_validated_json_with_status(path, 200, handler)
503    }
504
505    pub fn patch_validated_json_with_status<T, H, Fut, R>(
506        self,
507        path: impl Into<String>,
508        status: u16,
509        handler: H,
510    ) -> Result<Self>
511    where
512        T: DeserializeOwned + Validate + Send + 'static,
513        H: Fn(T) -> Fut + Send + Sync + 'static,
514        Fut: Future<Output = Result<R>> + Send + 'static,
515        R: Serialize + Send + 'static,
516    {
517        self.route(RouteDefinition::patch_validated_json_with_status(
518            path, status, handler,
519        )?)
520    }
521
522    pub fn patch<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
523    where
524        H: RouteHandler,
525    {
526        self.route(RouteDefinition::patch(path, handler)?)
527    }
528
529    pub fn patch_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
530    where
531        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
532        H: RouteHandler,
533    {
534        self.route(RouteDefinition::patch_scoped(path, factory)?)
535    }
536
537    pub fn delete<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
538    where
539        H: RouteHandler,
540    {
541        self.route(RouteDefinition::delete(path, handler)?)
542    }
543
544    pub fn delete_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
545    where
546        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
547        H: RouteHandler,
548    {
549        self.route(RouteDefinition::delete_scoped(path, factory)?)
550    }
551
552    pub fn delete_json<H, Fut, R>(self, path: impl Into<String>, handler: H) -> Result<Self>
553    where
554        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
555        Fut: Future<Output = Result<R>> + Send + 'static,
556        R: Serialize + Send + 'static,
557    {
558        self.delete_json_with_status(path, 200, handler)
559    }
560
561    pub fn delete_json_with_status<H, Fut, R>(
562        self,
563        path: impl Into<String>,
564        status: u16,
565        handler: H,
566    ) -> Result<Self>
567    where
568        H: Fn(BootRequest) -> Fut + Send + Sync + 'static,
569        Fut: Future<Output = Result<R>> + Send + 'static,
570        R: Serialize + Send + 'static,
571    {
572        self.route(RouteDefinition::delete_json_with_status(
573            path, status, handler,
574        )?)
575    }
576
577    pub fn options<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
578    where
579        H: RouteHandler,
580    {
581        self.route(RouteDefinition::options(path, handler)?)
582    }
583
584    pub fn options_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
585    where
586        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
587        H: RouteHandler,
588    {
589        self.route(RouteDefinition::options_scoped(path, factory)?)
590    }
591
592    pub fn head<H>(self, path: impl Into<String>, handler: H) -> Result<Self>
593    where
594        H: RouteHandler,
595    {
596        self.route(RouteDefinition::head(path, handler)?)
597    }
598
599    pub fn head_scoped<F, H>(self, path: impl Into<String>, factory: F) -> Result<Self>
600    where
601        F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
602        H: RouteHandler,
603    {
604        self.route(RouteDefinition::head_scoped(path, factory)?)
605    }
606
607    pub fn prefix(&self) -> &str {
608        &self.prefix
609    }
610
611    pub fn routes(&self) -> &[RouteDefinition] {
612        &self.routes
613    }
614
615    pub(crate) fn into_routes(self) -> Vec<RouteDefinition> {
616        self.routes
617    }
618}