Skip to main content

nestforge_core/
inject.rs

1use std::{ops::Deref, sync::Arc};
2
3use anyhow::Result;
4use axum::{
5    extract::{FromRequestParts, State},
6    http::request::Parts,
7};
8
9use crate::{framework_log_event, Container, HttpException};
10
11/*
12Inject<T> = DI extractor wrapper
13
14Now users can write:
15users: Inject<UsersService>
16
17and NestForge resolves it from the shared DI container automatically.
18*/
19pub struct Inject<T>(Arc<T>);
20
21impl<T> Inject<T>
22where
23    T: Send + Sync + 'static,
24{
25    /*
26    Manual resolve helper (still useful internally/tests)
27    */
28    pub fn from(container: &Container) -> Result<Self> {
29        framework_log_event(
30            "dependency_injected",
31            &[("type", std::any::type_name::<T>().to_string())],
32        );
33        let inner = container.resolve::<T>()?;
34        Ok(Self(inner))
35    }
36
37    pub fn into_inner(self) -> Arc<T> {
38        self.0
39    }
40}
41
42impl<T> Deref for Inject<T> {
43    type Target = T;
44
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50/*
51Magic extractor:
52Axum gives us Container state -> we resolve T from DI -> handler gets Inject<T>
53*/
54impl<T> FromRequestParts<Container> for Inject<T>
55where
56    T: Send + Sync + 'static,
57{
58    type Rejection = HttpException;
59
60    async fn from_request_parts(
61        parts: &mut Parts,
62        state: &Container,
63    ) -> Result<Self, Self::Rejection> {
64        let request_id = crate::request::request_id_from_extensions(&parts.extensions);
65        let scoped_container = parts.extensions.get::<Container>().cloned();
66        let State(root_container): State<Container> = State::from_request_parts(parts, state)
67            .await
68            .map_err(|_| {
69                HttpException::internal_server_error("Container state not available")
70                    .with_optional_request_id(request_id.clone())
71            })?;
72        let container = scoped_container.unwrap_or(root_container);
73
74        Self::from(&container).map_err(|err| {
75            HttpException::internal_server_error(format!(
76                "Failed to resolve dependency `{}`: {}",
77                std::any::type_name::<T>(),
78                err
79            ))
80            .with_optional_request_id(request_id)
81        })
82    }
83}
84
85impl<T> AsRef<T> for Inject<T> {
86    fn as_ref(&self) -> &T {
87        &self.0
88    }
89}