Skip to main content

hosting_di/
error.rs

1use std::error::Error;
2use std::fmt;
3
4/// Boxed error type accepted from constructors and factories.
5pub type BoxError = Box<dyn Error + Send + Sync>;
6
7/// Why a service could not be resolved.
8///
9/// Every variant carries the dependency path: type names from the service that
10/// was requested down to the one the error is about.
11#[derive(Debug)]
12#[non_exhaustive]
13pub enum ResolveError {
14    /// The last service in `path` has no registration.
15    NotRegistered {
16        /// Dependency path.
17        path: Vec<&'static str>,
18    },
19    /// The last service in `path` is scoped but was requested outside a scope,
20    /// either from the root provider or by a singleton.
21    ScopedFromRoot {
22        /// Dependency path.
23        path: Vec<&'static str>,
24    },
25    /// The services in `path` depend on each other; the first and last entries
26    /// are the same service.
27    Cycle {
28        /// Dependency path.
29        path: Vec<&'static str>,
30    },
31    /// The constructor of the last service in `path` returned an error.
32    Construction {
33        /// Dependency path.
34        path: Vec<&'static str>,
35        /// The constructor's error.
36        source: BoxError,
37    },
38}
39
40impl ResolveError {
41    /// The dependency path this error refers to.
42    pub fn path(&self) -> &[&'static str] {
43        match self {
44            Self::NotRegistered { path }
45            | Self::ScopedFromRoot { path }
46            | Self::Cycle { path }
47            | Self::Construction { path, .. } => path,
48        }
49    }
50}
51
52impl fmt::Display for ResolveError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        let message = match self {
55            Self::NotRegistered { .. } => "service not registered",
56            Self::ScopedFromRoot { .. } => "scoped service resolved outside a scope",
57            Self::Cycle { .. } => "dependency cycle",
58            Self::Construction { .. } => "failed to construct service",
59        };
60        write!(f, "{message}: ")?;
61        write_path(f, self.path())
62    }
63}
64
65impl Error for ResolveError {
66    fn source(&self) -> Option<&(dyn Error + 'static)> {
67        match self {
68            Self::Construction { source, .. } => Some(source.as_ref()),
69            _ => None,
70        }
71    }
72}
73
74/// The service collection was rejected by `build`.
75#[derive(Debug)]
76pub struct BuildError {
77    issues: Vec<BuildIssue>,
78}
79
80impl BuildError {
81    pub(crate) fn new(issues: Vec<BuildIssue>) -> Self {
82        Self { issues }
83    }
84
85    /// Every problem found: cycles, then missing dependencies, then captive
86    /// dependencies, each in registration order.
87    pub fn issues(&self) -> &[BuildIssue] {
88        &self.issues
89    }
90}
91
92impl fmt::Display for BuildError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        for (i, issue) in self.issues.iter().enumerate() {
95            if i > 0 {
96                f.write_str("; ")?;
97            }
98            write!(f, "{issue}")?;
99        }
100        Ok(())
101    }
102}
103
104impl Error for BuildError {}
105
106/// A single problem found while building a service collection.
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum BuildIssue {
110    /// The services in `path` depend on each other; the first and last entries
111    /// are the same service.
112    Cycle {
113        /// Dependency path.
114        path: Vec<&'static str>,
115    },
116    /// The first service in `path` depends on the second, which has no
117    /// registration. Reported only with
118    /// [`validate_dependencies`](crate::ServiceCollection::validate_dependencies).
119    MissingDependency {
120        /// The service and its missing dependency.
121        path: Vec<&'static str>,
122    },
123    /// The singleton first in `path` reaches the scoped service last in it,
124    /// directly or through transients, and would hold on to one scope's
125    /// instance. Reported only with
126    /// [`validate_scopes`](crate::ServiceCollection::validate_scopes).
127    CaptiveDependency {
128        /// The singleton, any transients in between, and the scoped service.
129        path: Vec<&'static str>,
130    },
131}
132
133impl fmt::Display for BuildIssue {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            Self::Cycle { path } => {
137                f.write_str("dependency cycle: ")?;
138                write_path(f, path)
139            }
140            Self::MissingDependency { path } => {
141                f.write_str("dependency not registered: ")?;
142                write_path(f, path)
143            }
144            Self::CaptiveDependency { path } => {
145                f.write_str("singleton depends on scoped service: ")?;
146                write_path(f, path)
147            }
148        }
149    }
150}
151
152fn write_path(f: &mut fmt::Formatter<'_>, path: &[&'static str]) -> fmt::Result {
153    for (i, name) in path.iter().enumerate() {
154        if i > 0 {
155            f.write_str(" -> ")?;
156        }
157        f.write_str(name)?;
158    }
159    Ok(())
160}