pavex/blueprint/
constructor.rs

1use crate::blueprint::ErrorHandler;
2use crate::blueprint::Lint;
3use crate::blueprint::conversions::{
4    cloning2cloning, coordinates2coordinates, lifecycle2lifecycle, lint2lint,
5};
6use pavex_bp_schema::Component;
7use pavex_bp_schema::{Blueprint as BlueprintSchema, LintSetting, Location};
8
9use super::CloningPolicy;
10use super::Lifecycle;
11use super::reflection::AnnotationCoordinates;
12
13/// The input type for [`Blueprint::constructor`].
14///
15/// Check out [`Blueprint::constructor`] for more information on dependency injection
16/// in Pavex.
17///
18/// # Stability guarantees
19///
20/// Use one of Pavex's constructor attributes (
21/// [`singleton`](macro@crate::singleton), [`request_scoped`](macro@crate::request_scoped), or [`transient`](macro@crate::transient))
22/// to create instances of `Constructor`.\
23/// `Constructor`'s fields are an implementation detail of Pavex's macros and should not be relied upon:
24/// newer versions of Pavex may add, remove or modify its fields.
25///
26/// [`Blueprint::constructor`]: crate::Blueprint::constructor
27pub struct Constructor {
28    #[doc(hidden)]
29    pub coordinates: AnnotationCoordinates,
30}
31
32/// A constructor registered via [`Blueprint::constructor`].
33///
34/// # Example
35///
36/// You can use the methods exposed by [`RegisteredConstructor`] to tune the behaviour
37/// of the registered constructor type.
38/// For example, instruct Pavex to clone the constructed type if it's necessary to satisfy
39/// the borrow checker:
40///
41/// ```rust
42/// use pavex::{methods, Blueprint};
43///
44/// # pub struct PoolConfig;
45/// pub struct Pool {
46///     // [...]
47/// }
48///
49/// #[methods]
50/// impl Pool {
51///     #[singleton]
52///     pub fn new(config: &PoolConfig) -> Self {
53///         # todo!()
54///         // [...]
55///     }
56/// }
57///
58/// let mut bp = Blueprint::new();
59/// // This is equivalent to `#[singleton(clone_if_necessary)]`
60/// bp.constructor(POOL_NEW).clone_if_necessary();
61/// ```
62///
63/// # Example: override the annotation
64///
65/// You can also override the behaviour specified via the [`singleton`](macro@crate::singleton) attribute.
66///
67/// ```rust
68/// use pavex::{methods, Blueprint};
69///
70/// # pub struct PoolConfig;
71/// pub struct Pool {
72///     // [...]
73/// }
74///
75/// #[methods]
76/// impl Pool {
77///     #[singleton(clone_if_necessary)]
78///     pub fn new(config: &PoolConfig) -> Self {
79///         # todo!()
80///         // [...]
81///     }
82/// }
83///
84/// let mut bp = Blueprint::new();
85/// // Using `never_clone` here, we are overriding the `clone_if_necessary`
86/// // flag specified via the `singleton` attribute.
87/// // This is equivalent to `#[singleton]`, thus restoring
88/// // the default behaviour.
89/// bp.constructor(POOL_NEW).never_clone();
90/// ```
91///
92/// [`Blueprint::constructor`]: crate::Blueprint::constructor
93pub struct RegisteredConstructor<'a> {
94    pub(crate) blueprint: &'a mut BlueprintSchema,
95    /// The index of the registered constructor in the blueprint's `components` vector.
96    pub(crate) component_id: usize,
97}
98
99impl RegisteredConstructor<'_> {
100    #[track_caller]
101    /// Register an error handler.
102    ///
103    /// If an error handler has already been registered for this constructor, it will be
104    /// overwritten.
105    ///
106    /// # Guide
107    ///
108    /// Check out the ["Error handlers"](https://pavex.dev/docs/guide/errors/error_handlers)
109    /// section of Pavex's guide for a thorough introduction to error handlers
110    /// in Pavex applications.
111    ///
112    /// # Example
113    ///
114    /// ```rust
115    /// use pavex::Blueprint;
116    /// use pavex::Response;
117    /// use pavex::{methods, transient};
118    /// # struct LogLevel;
119    /// # struct Logger;
120    /// # struct ConfigurationError;
121    ///
122    /// // 👇 a fallible constructor
123    /// #[transient]
124    /// pub fn logger() -> Result<Logger, ConfigurationError> {
125    ///     // [...]
126    ///     # todo!()
127    /// }
128    ///
129    /// #[methods]
130    /// impl ConfigurationError {
131    ///     #[error_handler]
132    ///     fn to_response(
133    ///         #[px(error_ref)] &self,
134    ///         log_level: LogLevel,
135    ///     ) -> Response {
136    ///         // [...]
137    ///         # todo!()
138    ///     }
139    /// }
140    ///
141    /// # fn main() {
142    /// let mut bp = Blueprint::new();
143    /// bp.constructor(LOGGER)
144    ///     .error_handler(CONFIGURATION_ERROR_TO_RESPONSE);
145    /// # }
146    /// ```
147    pub fn error_handler(mut self, error_handler: ErrorHandler) -> Self {
148        let error_handler = pavex_bp_schema::ErrorHandler {
149            coordinates: coordinates2coordinates(error_handler.coordinates),
150            registered_at: Location::caller(),
151        };
152        self.constructor().error_handler = Some(error_handler);
153        self
154    }
155
156    /// Change the constructor lifecycle.
157    pub fn lifecycle(mut self, lifecycle: Lifecycle) -> Self {
158        self.constructor().lifecycle = Some(lifecycle2lifecycle(lifecycle));
159        self
160    }
161
162    /// Set the cloning strategy for the output type returned by this constructor.
163    ///
164    /// By default,
165    /// Pavex will **never** try to clone the output type returned by a constructor.
166    /// If the output type implements [`Clone`], you can change the default by invoking
167    /// [`clone_if_necessary`](Self::clone_if_necessary): Pavex will clone the output type if
168    /// it's necessary to generate code that satisfies Rust's borrow checker.
169    pub fn cloning(mut self, strategy: CloningPolicy) -> Self {
170        self.constructor().cloning_policy = Some(cloning2cloning(strategy));
171        self
172    }
173
174    /// Set the cloning strategy to [`CloningPolicy::CloneIfNecessary`].
175    /// Check out [`cloning`](Self::cloning) method for more details.
176    pub fn clone_if_necessary(self) -> Self {
177        self.cloning(CloningPolicy::CloneIfNecessary)
178    }
179
180    /// Set the cloning strategy to [`CloningPolicy::NeverClone`].
181    /// Check out [`cloning`](Self::cloning) method for more details.
182    pub fn never_clone(self) -> Self {
183        self.cloning(CloningPolicy::NeverClone)
184    }
185
186    /// Silence a specific [`Lint`] for this constructor.
187    pub fn allow(mut self, lint: Lint) -> Self {
188        self.constructor()
189            .lints
190            .insert(lint2lint(lint), LintSetting::Allow);
191        self
192    }
193
194    /// Emit a warning if a specific [`Lint`] triggers for this constructor.
195    pub fn warn(mut self, lint: Lint) -> Self {
196        self.constructor()
197            .lints
198            .insert(lint2lint(lint), LintSetting::Warn);
199        self
200    }
201
202    /// Fail the build if a specific [`Lint`] triggers
203    /// for this constructor.
204    pub fn deny(mut self, lint: Lint) -> Self {
205        self.constructor()
206            .lints
207            .insert(lint2lint(lint), LintSetting::Deny);
208        self
209    }
210
211    fn constructor(&mut self) -> &mut pavex_bp_schema::Constructor {
212        let component = &mut self.blueprint.components[self.component_id];
213        let Component::Constructor(c) = component else {
214            unreachable!("The component should be a constructor")
215        };
216        c
217    }
218}