Skip to main content

a3s_boot/app/
builder.rs

1use super::application::BootApplication;
2use super::registration::register_module;
3use crate::pipeline::PipelineComponents;
4use crate::{
5    BootError, ExceptionFilter, Guard, Interceptor, Module, ModuleRef, Pipe, Result,
6    RouteDefinition,
7};
8use std::collections::BTreeSet;
9use std::sync::Arc;
10
11/// Builder for a [`BootApplication`].
12#[derive(Default)]
13pub struct BootApplicationBuilder {
14    modules: Vec<Arc<dyn Module>>,
15    routes: Vec<RouteDefinition>,
16    global_pipeline: PipelineComponents,
17    global_prefix: Option<String>,
18}
19
20impl BootApplicationBuilder {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Import a root module.
26    pub fn import<M>(mut self, module: M) -> Self
27    where
28        M: Module,
29    {
30        self.modules.push(Arc::new(module));
31        self
32    }
33
34    /// Import a shared root module.
35    pub fn import_arc(mut self, module: Arc<dyn Module>) -> Self {
36        self.modules.push(module);
37        self
38    }
39
40    /// Add a framework-neutral route directly to the application shell.
41    pub fn route(mut self, route: RouteDefinition) -> Self {
42        self.routes.push(route);
43        self
44    }
45
46    /// Prefix every route in the built application, for example `/api/v1`.
47    pub fn global_prefix(mut self, prefix: impl Into<String>) -> Self {
48        self.global_prefix = Some(prefix.into());
49        self
50    }
51
52    /// Add an application-wide pipe, similar to Nest's global pipes.
53    pub fn use_global_pipe<P>(mut self, pipe: P) -> Self
54    where
55        P: Pipe,
56    {
57        self.global_pipeline.push_pipe(pipe);
58        self
59    }
60
61    /// Add an application-wide guard, similar to Nest's global guards.
62    pub fn use_global_guard<G>(mut self, guard: G) -> Self
63    where
64        G: Guard,
65    {
66        self.global_pipeline.push_guard(guard);
67        self
68    }
69
70    /// Add an application-wide interceptor, similar to Nest's global interceptors.
71    pub fn use_global_interceptor<I>(mut self, interceptor: I) -> Self
72    where
73        I: Interceptor,
74    {
75        self.global_pipeline.push_interceptor(interceptor);
76        self
77    }
78
79    /// Add an application-wide exception filter, similar to Nest's global filters.
80    pub fn use_global_filter<F>(mut self, filter: F) -> Self
81    where
82        F: ExceptionFilter,
83    {
84        self.global_pipeline.push_filter(filter);
85        self
86    }
87
88    /// Resolve module imports, providers, controllers, and routes.
89    pub fn build(self) -> Result<BootApplication> {
90        let module_ref = ModuleRef::new();
91        let mut seen = BTreeSet::new();
92        let mut modules = Vec::new();
93        let mut module_instances = Vec::new();
94        let mut routes = self
95            .routes
96            .into_iter()
97            .map(|route| route.with_pipeline_prefix(&self.global_pipeline))
98            .collect::<Vec<_>>();
99
100        for module in &self.modules {
101            register_module(
102                Arc::clone(module),
103                &module_ref,
104                &self.global_pipeline,
105                &mut seen,
106                &mut modules,
107                &mut module_instances,
108                &mut routes,
109            )?;
110        }
111
112        let routes = apply_global_prefix(routes, self.global_prefix.as_deref())?;
113        validate_unique_routes(&routes)?;
114
115        Ok(BootApplication {
116            routes,
117            modules,
118            module_ref,
119            module_instances,
120        })
121    }
122}
123
124fn apply_global_prefix(
125    routes: Vec<RouteDefinition>,
126    prefix: Option<&str>,
127) -> Result<Vec<RouteDefinition>> {
128    match prefix {
129        Some(prefix) => routes
130            .into_iter()
131            .map(|route| route.with_path_prefix(prefix))
132            .collect(),
133        None => Ok(routes),
134    }
135}
136
137fn validate_unique_routes(routes: &[RouteDefinition]) -> Result<()> {
138    let mut seen = BTreeSet::new();
139
140    for route in routes {
141        let key = (route.method(), route.path_shape_key());
142        if !seen.insert(key) {
143            return Err(BootError::DuplicateRoute(format!(
144                "{} {}",
145                route.method().as_str(),
146                route.path()
147            )));
148        }
149    }
150
151    Ok(())
152}