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}