Skip to main content

nestforge_http/
factory.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use anyhow::Result;
5use axum::Router;
6use axum::middleware::from_fn;
7use nestforge_core::{
8    execute_pipeline, framework_log, initialize_module_graph, Container, Guard, Interceptor,
9    ModuleDefinition,
10};
11use tower_http::trace::TraceLayer;
12
13/*
14NestForgeFactory = app bootstrapper.
15
16This is the NestFactory.create(AppModule) vibe.
17
18Now it:
19- builds DI container
20- asks the module to register providers
21- asks the module for controllers
22- merges controller routers into one app router
23*/
24pub struct NestForgeFactory<M: ModuleDefinition> {
25    _marker: std::marker::PhantomData<M>,
26    container: Container,
27    controllers: Vec<Router<Container>>,
28    global_prefix: Option<String>,
29    version: Option<String>,
30    global_guards: Vec<Arc<dyn Guard>>,
31    global_interceptors: Vec<Arc<dyn Interceptor>>,
32}
33
34impl<M: ModuleDefinition> NestForgeFactory<M> {
35    pub fn create() -> Result<Self> {
36        let container = Container::new();
37        let controllers = initialize_module_graph::<M>(&container)?;
38
39        Ok(Self {
40            _marker: std::marker::PhantomData,
41            container,
42            controllers,
43            global_prefix: None,
44            version: None,
45            global_guards: Vec::new(),
46            global_interceptors: Vec::new(),
47        })
48    }
49
50    pub fn with_global_prefix(mut self, prefix: impl Into<String>) -> Self {
51        let prefix = prefix.into().trim().trim_matches('/').to_string();
52        if !prefix.is_empty() {
53            framework_log(format!("Using global route prefix '{}'.", prefix));
54            self.global_prefix = Some(prefix);
55        }
56        self
57    }
58
59    pub fn with_version(mut self, version: impl Into<String>) -> Self {
60        let version = version.into().trim().trim_matches('/').to_string();
61        if !version.is_empty() {
62            framework_log(format!("Using api version '{}'.", version));
63            self.version = Some(version);
64        }
65        self
66    }
67
68    pub fn use_guard<G>(mut self) -> Self
69    where
70        G: Guard + Default,
71    {
72        framework_log(format!(
73            "Registering guard {}.",
74            std::any::type_name::<G>()
75        ));
76        self.global_guards.push(Arc::new(G::default()));
77        self
78    }
79
80    pub fn use_interceptor<I>(mut self) -> Self
81    where
82        I: Interceptor + Default,
83    {
84        framework_log(format!(
85            "Registering interceptor {}.",
86            std::any::type_name::<I>()
87        ));
88        self.global_interceptors.push(Arc::new(I::default()));
89        self
90    }
91
92    pub async fn listen(self, port: u16) -> Result<()> {
93        /*
94        Build a router that EXPECTS Container state.
95        We don't attach the actual state yet.
96        */
97        let mut app: Router<Container> = Router::new();
98
99        /*
100        Mount all controller routers (they are also Router<Container>)
101        */
102        for controller_router in self.controllers {
103            app = app.merge(controller_router);
104        }
105
106        if let Some(version) = &self.version {
107            app = Router::new().nest(&format!("/{}", version), app);
108        }
109
110        if let Some(prefix) = &self.global_prefix {
111            app = Router::new().nest(&format!("/{}", prefix), app);
112        }
113
114        let global_guards = Arc::new(self.global_guards);
115        let global_interceptors = Arc::new(self.global_interceptors);
116
117        let app = app.route_layer(from_fn(move |req, next| {
118            let guards = Arc::clone(&global_guards);
119            let interceptors = Arc::clone(&global_interceptors);
120            async move { execute_pipeline(req, next, guards, interceptors).await }
121        }));
122
123        let app = app
124            .with_state(self.container.clone())
125            .layer(TraceLayer::new_for_http());
126
127        let addr = SocketAddr::from(([127, 0, 0, 1], port));
128        let listener = tokio::net::TcpListener::bind(addr).await?;
129
130        framework_log(format!("Listening on {}.", addr));
131
132        axum::serve(listener, app).await?;
133        Ok(())
134    }
135}