pavex/blueprint/
blueprint.rs

1use super::Constructor;
2use super::ErrorHandler;
3use super::ErrorObserver;
4use super::Fallback;
5use super::Import;
6use super::PostProcessingMiddleware;
7use super::PreProcessingMiddleware;
8use super::Prebuilt;
9use super::RegisteredConstructor;
10use super::RegisteredErrorHandler;
11use super::RegisteredImport;
12use super::Route;
13use super::WrappingMiddleware;
14use super::conversions::{coordinates2coordinates, created_at2created_at, sources2sources};
15use super::nesting::RoutingModifiers;
16use super::{
17    Config, RegisteredConfig, RegisteredFallback, RegisteredPostProcessingMiddleware,
18    RegisteredPreProcessingMiddleware, RegisteredRoute, RegisteredRoutes,
19    RegisteredWrappingMiddleware,
20};
21use crate::blueprint::RegisteredErrorObserver;
22use crate::blueprint::RegisteredPrebuilt;
23use pavex_bp_schema::Blueprint as BlueprintSchema;
24use pavex_bp_schema::Location;
25
26/// The structure of your Pavex application.
27///
28/// # Guide
29///
30/// Check out the ["Project structure"](https://pavex.dev/docs/guide/project_structure) section of
31/// Pavex's guide for more details on the role of [`Blueprint`] in Pavex applications.
32///
33/// # Overview
34///
35/// A blueprint keeps track of:
36///
37/// - [Routes](https://pavex.dev/docs/guide/routing/), registered via [`.routes()`][`Blueprint::route`] and [`.route()`][`Blueprint::route`]
38/// - [Middlewares](https://pavex.dev/docs/guide/middleware/), registered via [`.pre_process()`][`Blueprint::pre_process`], [`.wrap()`][`Blueprint::wrap`] and
39///   [`.post_process()`][`Blueprint::post_process`]
40/// - [Error observers](https://pavex.dev/docs/guide/errors/error_observers/), registered via [`.error_observer()`][`Blueprint::error_observer`]
41/// - [Constructors](https://pavex.dev/docs/guide/dependency_injection/), imported via [`.import()`][`Blueprint::import`] or registered via [`.constructor()`][`Blueprint::constructor`]
42/// - [Configuration types](https://pavex.dev/docs/guide/configuration/), imported via [`.import()`][`Blueprint::import`] or registered via [`.config()`][`Blueprint::config`]
43/// - [Prebuilt types](https://pavex.dev/docs/guide/dependency_injection/prebuilt_types/), imported via [`.import()`][`Blueprint::import`] or registered via [`.prebuilt()`][`Blueprint::prebuilt`]
44/// - [Error handlers](https://pavex.dev/docs/guide/errors/error_handlers/), imported via [`.import()`][`Blueprint::import`] or registered via [`.error_handler()`][`Blueprint::error_handler`]
45/// - Fallback routes, registered via [`.fallback()`][`Blueprint::fallback`]
46///
47/// You can also decompose your application into smaller sub-components
48/// using [`.nest()`][`Blueprint::nest`], [`.prefix()`][`Blueprint::prefix`] and [`.domain()`][`Blueprint::domain`].
49///
50/// A blueprint can be serialized via [`.persist()`][`Blueprint::persist`] and forwarded to Pavex's CLI
51/// to (re)generate the [server SDK crate](https://pavex.dev/docs/guide/project_structure/server_sdk/).
52///
53/// # Example
54///
55/// ```rust
56/// use pavex::{Blueprint, blueprint::from};
57///
58/// # pub fn _blueprint(
59/// # LOGGER: pavex::blueprint::WrappingMiddleware,
60/// # ERROR_LOGGER: pavex::blueprint::ErrorObserver,
61/// # RESPONSE_LOGGER: pavex::blueprint::PostProcessingMiddleware) {
62/// let mut bp = Blueprint::new();
63/// // Bring into scope constructors, error handlers and configuration
64/// // types defined in the crates listed via `from!`.
65/// bp.import(from![
66///     // Local components, defined in this crate
67///     crate,
68///     // Components defined in the `pavex` crate,
69///     // by the framework itself.
70///     pavex,
71/// ]);
72///
73/// // Attach a `tracing` span to every incoming request.
74/// bp.wrap(LOGGER);
75/// // Log the status code of every response.
76/// bp.post_process(RESPONSE_LOGGER);
77/// // Capture the error message and source chain
78/// // of every unhandled error.
79/// bp.error_observer(ERROR_LOGGER);
80///
81/// // Register all routes defined in this crate,
82/// // prepending `/api` to their paths.
83/// bp.prefix("/api").routes(from![crate]);
84/// # }
85/// ```
86pub struct Blueprint {
87    pub(super) schema: BlueprintSchema,
88}
89
90impl Default for Blueprint {
91    #[track_caller]
92    fn default() -> Self {
93        Self {
94            schema: BlueprintSchema {
95                creation_location: Location::caller(),
96                components: Vec::new(),
97            },
98        }
99    }
100}
101
102impl Blueprint {
103    #[track_caller]
104    /// Create a new [`Blueprint`].
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    #[track_caller]
110    /// Import all constructors, error handlers, configuration and prebuilt types defined in the target modules.
111    ///
112    /// Components that have been annotated with Pavex's macros (e.g. `#[singleton]`) aren't automatically
113    /// considered when resolving the dependency graph for your application.\
114    /// They need to be explicitly imported using one or more invocations of this method.
115    ///
116    /// # Guide
117    ///
118    /// Check out the ["Dependency Injection"](https://pavex.dev/docs/guide/dependency_injection) section of Pavex's guide
119    /// for a thorough introduction to dependency injection in Pavex applications.
120    ///
121    /// # Wildcard import
122    ///
123    /// You can import all components defined in the current crate and its direct dependencies using the wildcard source, `*`:
124    ///
125    /// ```rust
126    /// use pavex::{blueprint::from, Blueprint};
127    ///
128    /// # fn main() {
129    /// let mut bp = Blueprint::new();
130    /// bp.import(from![*]);
131    /// # }
132    /// ```
133    ///
134    /// # All local components
135    ///
136    /// Use `crate` as source to import all components defined in the current crate:
137    ///
138    /// ```rust
139    /// use pavex::{blueprint::from, Blueprint};
140    ///
141    /// # fn main() {
142    /// let mut bp = Blueprint::new();
143    /// bp.import(from![crate]);
144    /// # }
145    /// ```
146    ///
147    /// # Specific modules
148    ///
149    /// You can restrict the import to modules:
150    ///
151    /// ```rust
152    /// use pavex::{blueprint::from, Blueprint};
153    ///
154    /// # fn main() {
155    /// let mut bp = Blueprint::new();
156    /// // It will only import components defined
157    /// // in the `crate::a` and `crate::b` modules.
158    /// bp.import(from![crate::a, crate::b]);
159    /// # }
160    /// ```
161    ///
162    /// # Dependencies
163    ///
164    /// You can import components from a dependency using the same mechanism:
165    ///
166    /// ```rust
167    /// use pavex::{blueprint::from, Blueprint};
168    ///
169    /// # fn main() {
170    /// let mut bp = Blueprint::new();
171    /// // Import components from the `pavex_session` and
172    /// // `pavex_session_sqlx` crates.
173    /// bp.import(from![pavex_session, pavex_session_sqlx]);
174    /// # }
175    /// ```
176    ///
177    /// The specified crates must be direct dependencies of the current crate.
178    pub fn import(&mut self, import: Import) -> RegisteredImport<'_> {
179        let import = pavex_bp_schema::Import {
180            sources: sources2sources(import.sources),
181            relative_to: import.relative_to.to_owned(),
182            created_at: created_at2created_at(import.created_at),
183            registered_at: Location::caller(),
184        };
185        let component_id = self.push_component(import);
186        RegisteredImport {
187            blueprint: &mut self.schema,
188            component_id,
189        }
190    }
191
192    #[track_caller]
193    /// Register all the routes defined in the target modules.
194    ///
195    /// Components that have been annotated with Pavex's macros (e.g. `#[pavex::get]`) aren't automatically
196    /// added to your application.\
197    /// They need to be explicitly imported using this method or [`.route()`](Blueprint::route).
198    ///
199    /// # Guide
200    ///
201    /// Check out the ["Routing"](https://pavex.dev/docs/guide/routing) section of Pavex's guide
202    /// for a thorough introduction to routing in Pavex applications.
203    ///
204    /// Check out [`.route()`](Blueprint::route)'s documentation to learn how routes are defined.
205    ///
206    /// # All local routes
207    ///
208    /// Use `crate` as source to register all the routes defined in the current crate:
209    ///
210    /// ```rust
211    /// use pavex::{blueprint::from, Blueprint};
212    ///
213    /// # fn main() {
214    /// let mut bp = Blueprint::new();
215    /// bp.routes(from![crate]);
216    /// # }
217    /// ```
218    ///
219    /// # Specific modules
220    ///
221    /// You can restrict the scope to specific modules:
222    ///
223    /// ```rust
224    /// use pavex::{blueprint::from, Blueprint};
225    ///
226    /// # fn main() {
227    /// let mut bp = Blueprint::new();
228    /// // It will only register routes defined
229    /// // in the `crate::routes::user` and `crate::routes::post` modules.
230    /// bp.routes(from![
231    ///     crate::routes::user,
232    ///     crate::routes::post
233    /// ]);
234    /// # }
235    /// ```
236    ///
237    /// # Dependencies
238    ///
239    /// You can register routes defined in one of your dependencies using the same mechanism:
240    ///
241    /// ```rust
242    /// use pavex::{blueprint::from, Blueprint};
243    ///
244    /// # fn main() {
245    /// let mut bp = Blueprint::new();
246    /// // Register request handlers from the `pavex_session` crate
247    /// bp.routes(from![pavex_session]);
248    /// # }
249    /// ```
250    ///
251    /// The specified crates must be direct dependencies of the current crate.
252    ///
253    /// # Wildcard import
254    ///
255    /// You can import all routes defined in the current crate and its direct dependencies using the wildcard source, `*`:
256    ///
257    /// ```rust
258    /// use pavex::{blueprint::from, Blueprint};
259    ///
260    /// # fn main() {
261    /// let mut bp = Blueprint::new();
262    /// bp.routes(from![*]);
263    /// # }
264    /// ```
265    ///
266    /// This is generally discouraged.
267    pub fn routes(&mut self, import: Import) -> RegisteredRoutes<'_> {
268        let import = pavex_bp_schema::RoutesImport {
269            sources: sources2sources(import.sources),
270            relative_to: import.relative_to.to_owned(),
271            created_at: created_at2created_at(import.created_at),
272            registered_at: Location::caller(),
273        };
274        let component_id = self.push_component(import);
275        RegisteredRoutes {
276            blueprint: &mut self.schema,
277            component_id,
278        }
279    }
280
281    #[track_caller]
282    /// Register a route to handle incoming requests.
283    ///
284    /// You can register at most one route for any given [path](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Path) and
285    /// [method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) pair.
286    ///
287    /// # Guide
288    ///
289    /// Check out the ["Routing"](https://pavex.dev/docs/guide/routing) section of Pavex's guide
290    /// for a thorough introduction to routing in Pavex applications.
291    ///
292    /// # Example: function route
293    ///
294    /// Add the [`get`](macro@crate::get) attribute to a function to create a route matching `GET` requests
295    /// to the given path:
296    ///
297    /// ```rust
298    /// use pavex::get;
299    /// use pavex::{request::RequestHead, Response};
300    ///
301    /// #[get(path = "/")]
302    /// pub fn get_root(request_head: &RequestHead) -> Response {
303    ///     // [...]
304    ///     # todo!()
305    /// }
306    /// ```
307    ///
308    /// The [`get`](macro@crate::get) attribute will define a new constant,
309    /// named `GET_ROOT`.\
310    /// Pass the constant to [`Blueprint::route`] to add the newly-defined route to your application:
311    ///
312    /// ```rust
313    /// # use pavex::Blueprint;
314    /// # fn blueprint(GET_ROOT: pavex::blueprint::Route) {
315    /// let mut bp = Blueprint::new();
316    /// bp.route(GET_ROOT);
317    /// # }
318    /// ```
319    ///
320    /// ## Method-specific attributes
321    ///
322    /// Pavex provides attributes for the most common HTTP methods: [`get`](macro@crate::get), [`post`](macro@crate::post), [`put`](macro@crate::put),
323    /// [`patch`](macro@crate::patch), [`delete`](macro@crate::delete), [`head`](macro@crate::head), and [`options`](macro@crate::options).
324    /// Use the [`route`](macro@crate::route) attribute, instead, to define routes that match multiple methods,
325    /// non-standard methods or arbitrary methods.
326    ///
327    /// # Example: method route
328    ///
329    /// You're not limited to free functions. Methods can be used as routes too:
330    ///
331    /// ```rust
332    /// use pavex::methods;
333    /// use pavex::request::RequestHead;
334    ///
335    /// pub struct LoginController(/* .. */);
336    ///
337    /// #[methods]
338    /// impl LoginController {
339    ///     #[get(path = "/login")]
340    ///     pub fn get(head: &RequestHead) -> Self {
341    ///         // [...]
342    ///         # todo!()
343    ///     }
344    ///
345    ///     #[post(path = "/login")]
346    ///     pub fn post(head: &RequestHead) -> Self {
347    ///         // [...]
348    ///         # todo!()
349    ///     }
350    /// }
351    /// ```
352    ///
353    /// For methods, you must add a `#[methods]` annotation on the `impl` block it belongs to,
354    /// in addition to the verb annotation on the method itself.\
355    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
356    ///
357    /// ```rust
358    /// # use pavex::Blueprint;
359    /// # fn blueprint(LOGIN_CONTROLLER_GET: pavex::blueprint::Route, LOGIN_CONTROLLER_POST: pavex::blueprint::Route) {
360    /// let mut bp = Blueprint::new();
361    /// bp.route(LOGIN_CONTROLLER_GET);
362    /// bp.route(LOGIN_CONTROLLER_POST);
363    /// # }
364    /// ```
365    ///
366    /// # Imports
367    ///
368    /// If you have defined multiple routes, you can invoke [`.routes()`][`Blueprint::routes`]
369    /// to register them in bulk:
370    ///
371    /// ```rust
372    /// use pavex::{Blueprint, blueprint::from};
373    ///
374    /// let mut bp = Blueprint::new();
375    /// // Import all the routes defined in the current crate.
376    /// // It's equivalent to invoking `bp.route` for every
377    /// // single route defined in the current crate.
378    /// bp.routes(from![crate]);
379    /// ```
380    ///
381    /// Check out the documentation for [`.routes()`][`Blueprint::routes`] for more information.
382    pub fn route(&mut self, route: Route) -> RegisteredRoute<'_> {
383        let registered = pavex_bp_schema::Route {
384            coordinates: coordinates2coordinates(route.coordinates),
385            registered_at: Location::caller(),
386            error_handler: None,
387        };
388        let component_id = self.push_component(registered);
389        RegisteredRoute {
390            blueprint: &mut self.schema,
391            component_id,
392        }
393    }
394
395    #[track_caller]
396    /// Add a new type to the application's configuration.
397    ///
398    /// # Required traits
399    ///
400    /// Configuration types *must* implement `Debug`, `Clone` and `serde::Deserialize`.
401    ///
402    /// # Guide
403    ///
404    /// Check out the ["Configuration"](https://pavex.dev/docs/guide/configuration)
405    /// section of Pavex's guide for a thorough introduction to Pavex's configuration system.
406    ///
407    /// # Example
408    ///
409    /// Add the [`config`](macro@crate::config) attribute to the type you want to include in
410    /// the configuration for your application:
411    ///
412    /// ```rust
413    /// use pavex::config;
414    ///
415    /// #[config(key = "pool")]
416    /// #[derive(serde::Deserialize, Debug, Clone)]
417    /// pub struct PoolConfig {
418    ///     pub max_n_connections: u32,
419    ///     pub min_n_connections: u32,
420    /// }
421    /// ```
422    ///
423    /// The [`config`](macro@crate::config) attribute will define a new constant, named `POOL_CONFIG`.\
424    /// Pass the constant to [`Blueprint::config`] to add the new configuration type to your application:
425    ///
426    /// ```rust
427    /// # use pavex::Blueprint;
428    /// # fn blueprint(POOL_CONFIG: pavex::blueprint::Config) {
429    /// let mut bp = Blueprint::new();
430    /// bp.config(POOL_CONFIG);
431    /// # }
432    /// ```
433    ///
434    /// A new field, named `pool` with type `PoolConfig`, will be added to the generated `ApplicationConfig` struct.
435    ///
436    /// # Imports
437    ///
438    /// If you have defined multiple configuration types, you can use an [import](`Blueprint::import`)
439    /// to register them in bulk:
440    ///
441    /// ```rust
442    /// use pavex::{Blueprint, blueprint::from};
443    ///
444    /// let mut bp = Blueprint::new();
445    /// // Import all the types from the current crate that
446    /// // have been annotated with `#[config]`.
447    /// // It's equivalent to calling `bp.config` for
448    /// // every single configuration type defined in the current crate.
449    /// bp.import(from![crate]);
450    /// ```
451    ///
452    /// Check out the documentation for [`Blueprint::import`] for more information.
453    pub fn config(&mut self, config: Config) -> RegisteredConfig<'_> {
454        let registered = pavex_bp_schema::ConfigType {
455            coordinates: coordinates2coordinates(config.coordinates),
456            cloning_policy: None,
457            default_if_missing: None,
458            include_if_unused: None,
459            registered_at: Location::caller(),
460        };
461        let component_id = self.push_component(registered);
462        RegisteredConfig {
463            blueprint: &mut self.schema,
464            component_id,
465        }
466    }
467
468    #[track_caller]
469    /// Register a constructor.
470    ///
471    /// If a constructor for the same type has already been registered, it will be overwritten.
472    ///
473    /// # Guide
474    ///
475    /// Check out the ["Dependency injection"](https://pavex.dev/docs/guide/dependency_injection)
476    /// section of Pavex's guide for a thorough introduction to dependency injection
477    /// in Pavex applications.
478    ///
479    /// # Example: function constructor
480    ///
481    /// Add the [`request_scoped`](macro@crate::request_scoped) attribute to a function to mark it as a
482    /// [request-scoped](crate::blueprint::Lifecycle) constructor:
483    ///
484    /// ```rust
485    /// use pavex::request_scoped;
486    /// use pavex::request::RequestHead;
487    ///
488    /// # struct LogLevel;
489    /// pub struct AuthorizationHeader(/* .. */);
490    ///
491    /// #[request_scoped]
492    /// pub fn extract_authorization(head: &RequestHead) -> AuthorizationHeader {
493    ///     // [...]
494    ///     # todo!()
495    /// }
496    /// ```
497    ///
498    /// The [`request_scoped`](macro@crate::request_scoped) attribute will define a new constant,
499    /// named `EXTRACT_AUTHORIZATION`.\
500    /// Pass the constant to [`Blueprint::constructor`] to allow other components to inject an instance
501    /// of the `AuthorizationHeader` type as an input parameter.
502    ///
503    /// ```rust
504    /// # use pavex::Blueprint;
505    /// # fn blueprint(EXTRACT_AUTHORIZATION: pavex::blueprint::Constructor) {
506    /// let mut bp = Blueprint::new();
507    /// bp.constructor(EXTRACT_AUTHORIZATION);
508    /// # }
509    /// ```
510    ///
511    /// ## Lifecycles
512    ///
513    /// You can also register constructors with [singleton](crate::blueprint::Lifecycle::Singleton) and
514    /// [transient](crate::blueprint::Lifecycle::Transient) lifecycles. Check out the respective
515    /// macros ([`singleton`](macro@crate::singleton) and [`transient`](macro@crate::transient)) for more
516    /// details.
517    ///
518    /// # Example: method constructor
519    ///
520    /// You're not limited to free functions. Methods can be used as constructors too:
521    ///
522    /// ```rust
523    /// use pavex::methods;
524    /// use pavex::request::RequestHead;
525    ///
526    /// # struct LogLevel;
527    /// pub struct AuthorizationHeader(/* .. */);
528    ///
529    /// #[methods]
530    /// impl AuthorizationHeader {
531    ///     #[request_scoped]
532    ///     pub fn new(head: &RequestHead) -> Self {
533    ///         // [...]
534    ///         # todo!()
535    ///     }
536    /// }
537    /// ```
538    ///
539    /// For methods, you must add a `#[methods]` annotation on the `impl` block it belongs to,
540    /// in addition to the `#[request_scoped]` annotation on the method itself.\
541    ///
542    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
543    ///
544    /// ```rust
545    /// # use pavex::Blueprint;
546    /// # fn blueprint(AUTHORIZATION_HEADER_NEW: pavex::blueprint::Constructor) {
547    /// let mut bp = Blueprint::new();
548    /// bp.constructor(AUTHORIZATION_HEADER_NEW);
549    /// # }
550    /// ```
551    ///
552    /// # Imports
553    ///
554    /// If you have defined multiple constructors, you can use an [import](`Blueprint::import`)
555    /// to register them in bulk:
556    ///
557    /// ```rust
558    /// use pavex::{Blueprint, blueprint::from};
559    ///
560    /// let mut bp = Blueprint::new();
561    /// // Import all the types from the current crate that
562    /// // have been annotated with either `#[singleton]`,
563    /// // `#[request_scoped]`, `#[transient]` or `#[constructor]`.
564    /// // It's equivalent to invoking `bp.constructor` for every
565    /// // single constructor defined in the current crate.
566    /// bp.import(from![crate]);
567    /// ```
568    ///
569    /// Check out the documentation for [`Blueprint::import`] for more information.
570    pub fn constructor(&mut self, constructor: Constructor) -> RegisteredConstructor<'_> {
571        let registered_constructor = pavex_bp_schema::Constructor {
572            coordinates: coordinates2coordinates(constructor.coordinates),
573            lifecycle: None,
574            cloning_policy: None,
575            error_handler: None,
576            lints: Default::default(),
577            registered_at: Location::caller(),
578        };
579        let component_id = self.push_component(registered_constructor);
580        RegisteredConstructor {
581            component_id,
582            blueprint: &mut self.schema,
583        }
584    }
585
586    #[track_caller]
587    /// Register a wrapping middleware.
588    ///
589    /// # Guide
590    ///
591    /// Check out the ["Middleware"](https://pavex.dev/docs/guide/middleware)
592    /// section of Pavex's guide for a thorough introduction to middlewares
593    /// in Pavex applications.
594    ///
595    /// # Example: function wrapper
596    ///
597    /// Add the [`wrap`](macro@crate::wrap) attribute to a function to mark it as a
598    /// a wrapping middleware:
599    ///
600    /// ```rust
601    /// use pavex::{middleware::Next, Response, wrap};
602    /// use std::time::Duration;
603    /// use tokio::time::{timeout, error::Elapsed};
604    ///
605    /// #[wrap]
606    /// pub async fn timeout_wrapper<C>(next: Next<C>) -> Result<Response, Elapsed>
607    /// where
608    ///     C: IntoFuture<Output = Response>
609    /// {
610    ///     timeout(Duration::from_secs(2), next.into_future()).await
611    /// }
612    /// ```
613    ///
614    /// The [`wrap`](macro@crate::wrap) attribute will define a new constant,
615    /// named `TIMEOUT_WRAPPER`.\
616    /// Pass the constant to [`Blueprint::wrap`] to add the newly-defined middleware to
617    /// your application:
618    ///
619    /// ```rust
620    /// # use pavex::Blueprint;
621    /// # fn blueprint(TIMEOUT_WRAPPER: pavex::blueprint::WrappingMiddleware) {
622    /// let mut bp = Blueprint::new();
623    /// bp.wrap(TIMEOUT_WRAPPER);
624    /// # }
625    /// ```
626    ///
627    /// # Example: method middleware
628    ///
629    /// You're not limited to free functions. Methods can be used as middlewares too:
630    ///
631    /// ```rust
632    /// use pavex::{middleware::Next, Response, methods};
633    /// use std::time::Duration;
634    /// use tokio::time::{timeout, error::Elapsed};
635    ///
636    /// pub struct TimeoutMiddleware {
637    ///     timeout: Duration,
638    /// }
639    ///
640    /// #[methods]
641    /// impl TimeoutMiddleware {
642    ///     #[wrap]
643    ///     pub async fn execute<C>(&self, next: Next<C>) -> Result<Response, Elapsed>
644    ///     where
645    ///         C: IntoFuture<Output = Response>
646    ///     {
647    ///         timeout(self.timeout, next.into_future()).await
648    ///     }
649    /// }
650    /// ```
651    ///
652    /// For methods, you must add a `#[methods]` annotation on the `impl` block it belongs to,
653    /// in addition to the `#[wrap]` annotation on the method itself.\
654    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
655    ///
656    /// ```rust
657    /// # use pavex::Blueprint;
658    /// # fn blueprint(TIMEOUT_MIDDLEWARE_EXECUTE: pavex::blueprint::WrappingMiddleware) {
659    /// let mut bp = Blueprint::new();
660    /// bp.wrap(TIMEOUT_MIDDLEWARE_EXECUTE);
661    /// # }
662    /// ```
663    #[doc(alias = "middleware")]
664    pub fn wrap(&mut self, m: WrappingMiddleware) -> RegisteredWrappingMiddleware<'_> {
665        let registered = pavex_bp_schema::WrappingMiddleware {
666            coordinates: coordinates2coordinates(m.coordinates),
667            registered_at: Location::caller(),
668            error_handler: None,
669        };
670        let component_id = self.push_component(registered);
671        RegisteredWrappingMiddleware {
672            blueprint: &mut self.schema,
673            component_id,
674        }
675    }
676
677    #[track_caller]
678    /// Register a post-processing middleware.
679    ///
680    /// # Guide
681    ///
682    /// Check out the ["Middleware"](https://pavex.dev/docs/guide/middleware)
683    /// section of Pavex's guide for a thorough introduction to middlewares
684    /// in Pavex applications.
685    ///
686    /// # Example: function middleware
687    ///
688    /// Add the [`post_process`](macro@crate::post_process) attribute to a function to mark it as a
689    /// a post-processing middleware:
690    ///
691    /// ```rust
692    /// use pavex::{post_process, Response};
693    /// use pavex_tracing::{
694    ///     RootSpan,
695    ///     fields::{http_response_status_code, HTTP_RESPONSE_STATUS_CODE}
696    /// };
697    ///
698    /// #[post_process]
699    /// pub fn response_logger(response: Response, root_span: &RootSpan) -> Response
700    /// {
701    ///     root_span.record(
702    ///         HTTP_RESPONSE_STATUS_CODE,
703    ///         http_response_status_code(&response),
704    ///     );
705    ///     response
706    /// }
707    /// ```
708    ///
709    /// The [`post_process`](macro@crate::post_process) attribute will define a new constant,
710    /// named `RESPONSE_LOGGER`.\
711    /// Pass the constant to [`Blueprint::post_process`] to add the newly-defined middleware to
712    /// your application:
713    ///
714    /// ```rust
715    /// # use pavex::Blueprint;
716    /// # fn blueprint(RESPONSE_LOGGER: pavex::blueprint::PostProcessingMiddleware) {
717    /// let mut bp = Blueprint::new();
718    /// bp.post_process(RESPONSE_LOGGER);
719    /// # }
720    /// ```
721    ///
722    /// # Example: method middleware
723    ///
724    /// You're not limited to free functions. Methods can be used as middlewares too:
725    ///
726    /// ```rust
727    /// use pavex::{methods, Response};
728    /// use pavex_tracing::{
729    ///     RootSpan,
730    ///     fields::{http_response_status_code, HTTP_RESPONSE_STATUS_CODE}
731    /// };
732    ///
733    /// pub struct ResponseLogger {
734    ///     log_body_size: bool,
735    /// }
736    ///
737    /// #[methods]
738    /// impl ResponseLogger {
739    ///     #[post_process]
740    ///     pub fn log(&self, response: Response, root_span: &RootSpan) -> Response
741    ///     {
742    ///         if self.log_body_size {
743    ///             // [...]
744    ///         }
745    ///         root_span.record(
746    ///             HTTP_RESPONSE_STATUS_CODE,
747    ///             http_response_status_code(&response),
748    ///         );
749    ///         response
750    ///     }
751    /// }
752    /// ```
753    ///
754    /// For methods, you must add a [`#[methods]`][macro@crate::methods] annotation on the `impl` block it belongs to,
755    /// in addition to the [`#[post_process]`][macro@crate::post_process] annotation on the method itself.\
756    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
757    ///
758    /// ```rust
759    /// # use pavex::Blueprint;
760    /// # fn blueprint(RESPONSE_LOGGER_LOG: pavex::blueprint::PostProcessingMiddleware) {
761    /// let mut bp = Blueprint::new();
762    /// bp.post_process(RESPONSE_LOGGER_LOG);
763    /// # }
764    /// ```
765    #[doc(alias = "middleware")]
766    #[doc(alias = "postprocess")]
767    pub fn post_process(
768        &mut self,
769        m: PostProcessingMiddleware,
770    ) -> RegisteredPostProcessingMiddleware<'_> {
771        let registered = pavex_bp_schema::PostProcessingMiddleware {
772            coordinates: coordinates2coordinates(m.coordinates),
773            registered_at: Location::caller(),
774            error_handler: None,
775        };
776        let component_id = self.push_component(registered);
777        RegisteredPostProcessingMiddleware {
778            blueprint: &mut self.schema,
779            component_id,
780        }
781    }
782
783    #[track_caller]
784    /// Register a pre-processing middleware.
785    ///
786    /// # Guide
787    ///
788    /// Check out the ["Middleware"](https://pavex.dev/docs/guide/middleware)
789    /// section of Pavex's guide for a thorough introduction to middlewares
790    /// in Pavex applications.
791    ///
792    /// # Example: function middleware
793    ///
794    /// Add the [`pre_process`](macro@crate::pre_process) attribute to a function to mark it as a
795    /// a pre-processing middleware:
796    ///
797    /// ```rust
798    /// use pavex::{Blueprint, pre_process, Response};
799    /// use pavex::middleware::Processing;
800    /// use pavex::http::{HeaderValue, header::LOCATION};
801    /// use pavex::request::RequestHead;
802    ///
803    /// /// If the request path ends with a `/`,
804    /// /// redirect to the same path without the trailing `/`.
805    /// #[pre_process]
806    /// pub fn redirect_to_normalized(request_head: &RequestHead) -> Processing
807    /// {
808    ///     let Some(normalized_path) = request_head.target.path().strip_suffix('/') else {
809    ///         // No need to redirect, we continue processing the request.
810    ///         return Processing::Continue;
811    ///     };
812    ///     let location = HeaderValue::from_str(normalized_path).unwrap();
813    ///     let redirect = Response::temporary_redirect().insert_header(LOCATION, location);
814    ///     // Short-circuit the request processing pipeline and return the redirect response
815    ///     // to the client without invoking downstream middlewares and the request handler.
816    ///     Processing::EarlyReturn(redirect)
817    /// }
818    /// ```
819    ///
820    /// The [`pre_process`](macro@crate::pre_process) attribute will define a new constant,
821    /// named `REDIRECT_TO_NORMALIZED`.\
822    /// Pass the constant to [`Blueprint::pre_process`] to add the newly-defined middleware to
823    /// your application:
824    ///
825    /// ```rust
826    /// # use pavex::Blueprint;
827    /// # fn blueprint(REDIRECT_TO_NORMALIZED: pavex::blueprint::PreProcessingMiddleware) {
828    /// let mut bp = Blueprint::new();
829    /// bp.pre_process(REDIRECT_TO_NORMALIZED);
830    /// # }
831    /// ```
832    ///
833    /// # Example: method middleware
834    ///
835    /// You're not limited to free functions. Methods can be used as middlewares too:
836    ///
837    /// ```rust
838    /// use pavex::{methods, Response};
839    /// use pavex::middleware::Processing;
840    /// use pavex::http::{HeaderValue, header::LOCATION};
841    /// use pavex::request::RequestHead;
842    ///
843    /// pub struct PathNormalizer {
844    ///     // [...]
845    /// }
846    ///
847    /// #[methods]
848    /// impl PathNormalizer {
849    ///     #[pre_process]
850    ///     pub fn redirect(request_head: &RequestHead) -> Processing
851    ///     {
852    ///         // [...]
853    ///         # todo!()
854    ///     }
855    /// }
856    /// ```
857    ///
858    /// For methods, you must add a [`#[methods]`][macro@crate::methods] annotation on the `impl` block it belongs to,
859    /// in addition to the [`#[pre_process]`][macro@crate::pre_process] annotation on the method itself.\
860    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
861    ///
862    /// ```rust
863    /// # use pavex::Blueprint;
864    /// # fn blueprint(PATH_NORMALIZER_REDIRECT: pavex::blueprint::PreProcessingMiddleware) {
865    /// let mut bp = Blueprint::new();
866    /// bp.pre_process(PATH_NORMALIZER_REDIRECT);
867    /// # }
868    /// ```
869    #[doc(alias = "middleware")]
870    #[doc(alias = "preprocess")]
871    pub fn pre_process(
872        &mut self,
873        m: PreProcessingMiddleware,
874    ) -> RegisteredPreProcessingMiddleware<'_> {
875        let registered = pavex_bp_schema::PreProcessingMiddleware {
876            coordinates: coordinates2coordinates(m.coordinates),
877            registered_at: Location::caller(),
878            error_handler: None,
879        };
880        let component_id = self.push_component(registered);
881        RegisteredPreProcessingMiddleware {
882            blueprint: &mut self.schema,
883            component_id,
884        }
885    }
886
887    /// Nest a [`Blueprint`] under the current [`Blueprint`] (the parent), without adding a [common path prefix](Self::prefix)
888    /// nor a [domain restriction](Self::domain) to its routes.
889    ///
890    /// Check out [`RoutingModifiers::nest`](super::RoutingModifiers::nest) for more details on nesting.
891    #[track_caller]
892    #[doc(alias("scope"))]
893    pub fn nest(&mut self, blueprint: Blueprint) {
894        self.push_component(pavex_bp_schema::NestedBlueprint {
895            blueprint: blueprint.schema,
896            path_prefix: None,
897            domain: None,
898            nested_at: Location::caller(),
899        });
900    }
901
902    #[track_caller]
903    /// A common prefix will be prepended to the path of routes nested under this condition.
904    ///
905    /// ```rust
906    /// use pavex::Blueprint;
907    /// use pavex::get;
908    /// use pavex::Response;
909    ///
910    /// fn app() -> Blueprint {
911    ///     let mut bp = Blueprint::new();
912    ///     // Adding `/api` as common prefix here
913    ///     bp.prefix("/api").nest(api_bp());
914    ///     bp
915    /// }
916    ///
917    /// #[get(path = "/version")]
918    /// pub fn get_api_version() -> Response {
919    ///     // [...]
920    ///     # todo!()
921    /// }
922    ///
923    /// fn api_bp() -> Blueprint {
924    ///     let mut bp = Blueprint::new();
925    ///     // This will match `GET` requests to `/api/version`.
926    ///     bp.route(GET_API_VERSION);
927    ///     bp
928    /// }
929    /// # pub fn handler() {}
930    /// ```
931    ///
932    /// You can also add a (sub)domain constraint, in addition to the common prefix:
933    ///
934    /// ```rust
935    /// use pavex::Blueprint;
936    /// use pavex::get;
937    /// use pavex::Response;
938    ///
939    /// fn app() -> Blueprint {
940    ///    let mut bp = Blueprint::new();
941    ///    bp.prefix("/v1").domain("api.mybusiness.com").nest(api_bp());
942    ///    bp
943    /// }
944    ///
945    /// #[get(path = "/about")]
946    /// pub fn get_about() -> Response {
947    ///     // [...]
948    ///     # todo!()
949    /// }
950    ///
951    /// fn api_bp() -> Blueprint {
952    ///    let mut bp = Blueprint::new();
953    ///   // This will match `GET` requests to `api.mybusiness.com/v1/about`.
954    ///   bp.route(GET_ABOUT);
955    ///   bp
956    /// }
957    /// ```
958    ///
959    /// Check out [`Blueprint::domain`] for more details on domain restrictions.
960    ///
961    /// ## Restrictions
962    ///
963    /// `prefix` must be non-empty and it must start with a `/`.
964    /// If you don't want to add a common prefix, check out [`Blueprint::nest`] or [`Blueprint::domain`].
965    ///
966    /// ## Trailing slashes
967    ///
968    /// `prefix` **can't** end with a trailing `/`.
969    /// This would result in routes with two consecutive `/` in their paths—e.g.
970    /// `/prefix//path`—which is rarely desirable.
971    /// If you actually need consecutive slashes in your route, you can add them explicitly to
972    /// the path of the route registered in the nested blueprint:
973    ///
974    /// ```rust
975    /// use pavex::Blueprint;
976    /// use pavex::get;
977    /// use pavex::Response;
978    ///
979    /// fn app() -> Blueprint {
980    ///     let mut bp = Blueprint::new();
981    ///     bp.prefix("/api").nest(api_bp());
982    ///     bp
983    /// }
984    ///
985    /// #[get(path = "//version")]
986    /// pub fn get_api_version() -> Response {
987    ///     // [...]
988    ///     # todo!()
989    /// }
990    ///
991    /// fn api_bp() -> Blueprint {
992    ///     let mut bp = Blueprint::new();
993    ///     // This will match `GET` requests to `/api//version`.
994    ///     bp.route(GET_API_VERSION);
995    ///     bp
996    /// }
997    /// # pub fn handler() {}
998    /// ```
999    pub fn prefix(&mut self, prefix: &str) -> RoutingModifiers<'_> {
1000        RoutingModifiers::empty(&mut self.schema).prefix(prefix)
1001    }
1002
1003    #[track_caller]
1004    /// Only requests to the specified domain will be forwarded to routes nested under this condition.
1005    ///
1006    /// # Example
1007    ///
1008    /// ```rust
1009    /// use pavex::Blueprint;
1010    /// # fn api_routes() -> Blueprint { Blueprint::new() }
1011    /// # fn console_routes() -> Blueprint { Blueprint::new() }
1012    ///
1013    /// let mut bp = Blueprint::new();
1014    ///
1015    /// // We split UI and API routes into separate blueprints,
1016    /// // and we serve them using different subdomains.
1017    /// bp.domain("api.mybusiness.com")
1018    ///   .nest(api_routes());
1019    /// bp.domain("console.mybusiness.com")
1020    ///   .nest(console_routes());
1021    /// ```
1022    ///
1023    /// You can also prepend a common path prefix to all registered routes, in addition to the
1024    /// domain constraint:
1025    ///
1026    /// ```rust
1027    /// use pavex::Blueprint;
1028    /// use pavex::get;
1029    /// use pavex::Response;
1030    ///
1031    /// fn app() -> Blueprint {
1032    ///    let mut bp = Blueprint::new();
1033    ///    bp.prefix("/v1").domain("api.mybusiness.com").nest(api_bp());
1034    ///    bp
1035    /// }
1036    ///
1037    /// #[get(path = "/about")]
1038    /// pub fn get_about() -> Response {
1039    ///     // [...]
1040    ///     # todo!()
1041    /// }
1042    ///
1043    /// fn api_bp() -> Blueprint {
1044    ///    let mut bp = Blueprint::new();
1045    ///   // This will match `GET` requests to `api.mybusiness.com/v1/about`.
1046    ///   bp.route(GET_ABOUT);
1047    ///   bp
1048    /// }
1049    /// ```
1050    ///
1051    /// Check out [`Blueprint::prefix`] for more details on path prefixes.
1052    ///
1053    /// # Domain detection
1054    ///
1055    /// Domain detection is based on the value of [`Host` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host).
1056    /// If the header is not present in the request, the condition will be considered as not met.
1057    ///
1058    /// Keep in mind that the [`Host` header can be easily spoofed by the client](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection),
1059    /// so you should not rely on its value for auth or other security-sensitive operations.
1060    pub fn domain(&mut self, domain: &str) -> RoutingModifiers<'_> {
1061        RoutingModifiers::empty(&mut self.schema).domain(domain)
1062    }
1063
1064    #[track_caller]
1065    /// Register a fallback handler to be invoked when an incoming request does **not** match
1066    /// any of the routes you registered with [`Blueprint::route`].
1067    ///
1068    /// If you don't register a fallback handler, the
1069    /// [default framework fallback](crate::router::default_fallback) will be used instead.
1070    ///
1071    /// If a fallback handler has already been registered against this `Blueprint`,
1072    /// it will be overwritten.
1073    ///
1074    /// # Example
1075    ///
1076    /// ```rust
1077    /// use pavex::{get, fallback, Blueprint};
1078    /// use pavex::Response;
1079    ///
1080    /// #[get(path = "/path")]
1081    /// pub fn get_path() -> Response {
1082    ///     // [...]
1083    ///     # todo!()
1084    /// }
1085    /// #[fallback]
1086    /// pub fn fallback_handler() -> Response {
1087    ///     // [...]
1088    ///     # todo!()
1089    /// }
1090    ///
1091    /// # fn main() {
1092    /// let mut bp = Blueprint::new();
1093    /// bp.route(GET_PATH);
1094    /// // The fallback handler will be invoked for all the requests that don't match `/path`.
1095    /// // E.g. `GET /home`, `POST /home`, `GET /home/123`, etc.
1096    /// bp.fallback(FALLBACK_HANDLER);
1097    /// # }
1098    /// ```
1099    ///
1100    /// # Signature
1101    ///
1102    /// A fallback handler is a function (or a method) that returns a [`Response`], either directly
1103    /// (if infallible) or wrapped in a [`Result`] (if fallible).
1104    ///
1105    /// Fallback handlers can take advantage of dependency injection, like any
1106    /// other component.
1107    /// You list what you want to see injected as function parameters
1108    /// and Pavex will inject them for you in the generated code.
1109    ///
1110    /// ## Nesting
1111    ///
1112    /// You can register a single fallback handler for each blueprint.
1113    /// If your application takes advantage of [nesting](Blueprint::nest), you can register
1114    /// a fallback against each nested blueprint in your application as well as one for the
1115    /// top-level blueprint.
1116    ///
1117    /// Let's explore how nesting affects the invocation of fallback handlers.
1118    ///
1119    /// ### Nesting without prefix
1120    ///
1121    /// The fallback registered against a blueprint will be invoked for all the requests that match
1122    /// the path of a route that was **directly** registered against that blueprint, but don't satisfy
1123    /// their method guards.
1124    ///
1125    /// ```rust
1126    /// use pavex::{get, fallback, Blueprint};
1127    /// use pavex::Response;
1128    ///
1129    /// #[get(path = "/home")]
1130    /// pub fn get_home() -> Response {
1131    ///     // [...]
1132    ///     # todo!()
1133    /// }
1134    ///
1135    /// #[get(path = "/room")]
1136    /// pub fn get_room() -> Response {
1137    ///     // [...]
1138    ///     # todo!()
1139    /// }
1140    ///
1141    /// #[fallback]
1142    /// pub fn fallback_handler() -> Response {
1143    ///     // [...]
1144    ///     # todo!()
1145    /// }
1146    ///
1147    /// # fn main() {
1148    /// let mut bp = Blueprint::new();
1149    /// bp.route(GET_HOME);
1150    /// bp.nest({
1151    ///     let mut bp = Blueprint::new();
1152    ///     bp.route(GET_ROOM);
1153    ///     bp.fallback(FALLBACK_HANDLER);
1154    ///     bp
1155    /// });
1156    /// # }
1157    /// ```
1158    ///
1159    /// In the example above, `fallback_handler` will be invoked for incoming `POST /room`
1160    /// requests: the path matches the path of a route registered against the nested blueprint
1161    /// (`GET /room`), but the method guard doesn't (`POST` vs `GET`).
1162    /// If the incoming requests don't have `/room` as their path instead (e.g. `GET /street`
1163    /// or `GET /room/123`), they will be handled by the fallback registered against the **parent**
1164    /// blueprint—the top-level one in this case.
1165    /// Since no fallback has been explicitly registered against the top-level blueprint, the
1166    /// [default framework fallback](crate::router::default_fallback) will be used instead.
1167    ///
1168    /// ### Nesting with prefix
1169    ///
1170    /// If the nested blueprint includes a nesting prefix (e.g. `bp.nest_at("/api", api_bp)`),
1171    /// its fallback will **also** be invoked for all the requests that start with the prefix
1172    /// but don't match any of the route paths registered against the nested blueprint.
1173    ///
1174    /// ```rust
1175    /// use pavex::{get, fallback, Blueprint};
1176    /// use pavex::Response;
1177    ///
1178    /// #[get(path = "/home")]
1179    /// pub fn get_home() -> Response {
1180    ///     // [...]
1181    ///     # todo!()
1182    /// }
1183    ///
1184    /// #[get(path = "/")]
1185    /// pub fn list_rooms() -> Response {
1186    ///     // [...]
1187    ///     # todo!()
1188    /// }
1189    ///
1190    /// #[fallback]
1191    /// pub fn fallback_handler() -> Response {
1192    ///     // [...]
1193    ///     # todo!()
1194    /// }
1195    ///
1196    /// # fn main() {
1197    /// let mut bp = Blueprint::new();
1198    /// bp.route(GET_HOME);
1199    /// bp.prefix("/room").nest({
1200    ///     let mut bp = Blueprint::new();
1201    ///     bp.route(LIST_ROOMS);
1202    ///     bp.fallback(FALLBACK_HANDLER);
1203    ///     bp
1204    /// });
1205    /// # }
1206    /// ```
1207    ///
1208    /// In the example above, `fallback_handler` will be invoked for both `POST /room`
1209    /// **and** `POST /room/123` requests: the path of the latter doesn't match the path of the only
1210    /// route registered against the nested blueprint (`GET /room/`), but it starts with the
1211    /// prefix of the nested blueprint (`/room`).
1212    ///
1213    /// [`Response`]: crate::Response
1214    pub fn fallback(&mut self, fallback: Fallback) -> RegisteredFallback<'_> {
1215        let registered = pavex_bp_schema::Fallback {
1216            coordinates: coordinates2coordinates(fallback.coordinates),
1217            registered_at: Location::caller(),
1218            error_handler: None,
1219        };
1220        let component_id = self.push_component(registered);
1221        RegisteredFallback {
1222            blueprint: &mut self.schema,
1223            component_id,
1224        }
1225    }
1226
1227    #[track_caller]
1228    /// Register an error observer to intercept and report errors that occur during request handling.
1229    ///
1230    /// # Guide
1231    ///
1232    /// Check out the ["Error observers"](https://pavex.dev/docs/guide/errors/error_observers)
1233    /// section of Pavex's guide for a thorough introduction to error observers
1234    /// in Pavex applications.
1235    ///
1236    /// # Example: function observer
1237    ///
1238    /// ```rust
1239    /// use pavex::error_observer;
1240    /// use tracing_log_error::log_error;
1241    ///
1242    /// #[error_observer]
1243    /// pub fn error_logger(e: &pavex::Error) {
1244    ///     log_error!(e, "An error occurred while handling a request");
1245    /// }
1246    /// ```
1247    ///
1248    /// The [`error_observer`](macro@crate::error_observer) attribute will define a new constant,
1249    /// named `ERROR_LOGGER`.\
1250    /// Pass the constant to [`.error_observer()`][`Blueprint::error_observer`] to register
1251    /// the newly defined error observer:
1252    ///
1253    /// ```rust
1254    /// # use pavex::Blueprint;
1255    /// # fn blueprint(ERROR_LOGGER: pavex::blueprint::ErrorObserver) {
1256    /// let mut bp = Blueprint::new();
1257    /// bp.error_observer(ERROR_LOGGER);
1258    /// # }
1259    /// ```
1260    ///
1261    /// # Example: method observer
1262    ///
1263    /// You're not limited to free functions. Methods can be used as error observers too:
1264    ///
1265    /// ```rust
1266    /// use pavex::methods;
1267    /// use tracing_log_error::log_error;
1268    ///
1269    /// pub struct ErrorLogger;
1270    ///
1271    /// #[methods]
1272    /// impl ErrorLogger {
1273    ///     #[error_observer]
1274    ///     pub fn log(e: &pavex::Error) {
1275    ///         log_error!(e, "An error occurred while handling a request");
1276    ///     }
1277    /// }
1278    /// ```
1279    ///
1280    /// For methods, you must add a [`#[methods]`](macro@crate::methods) annotation on the `impl` block it belongs to,
1281    /// in addition to the [`#[error_observer]`](macro@crate::error_observer) annotation on the method itself.\
1282    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
1283    ///
1284    /// ```rust
1285    /// # use pavex::Blueprint;
1286    /// # fn blueprint(ERROR_LOGGER_LOG: pavex::blueprint::ErrorObserver) {
1287    /// let mut bp = Blueprint::new();
1288    /// bp.error_observer(ERROR_LOGGER_LOG);
1289    /// # }
1290    /// ```
1291    pub fn error_observer(&mut self, error_observer: ErrorObserver) -> RegisteredErrorObserver<'_> {
1292        let registered = pavex_bp_schema::ErrorObserver {
1293            coordinates: coordinates2coordinates(error_observer.coordinates),
1294            registered_at: Location::caller(),
1295        };
1296        self.push_component(registered);
1297        RegisteredErrorObserver {
1298            blueprint: &mut self.schema,
1299        }
1300    }
1301
1302    #[track_caller]
1303    /// Register an error handler.
1304    ///
1305    /// # Guide
1306    ///
1307    /// Check out the ["Error handlers"](https://pavex.dev/docs/guide/errors/error_handlers)
1308    /// section of Pavex's guide for a thorough introduction to error handlers
1309    /// in Pavex applications.
1310    ///
1311    /// # Example: function handler
1312    ///
1313    /// Add the [`error_handler`](macro@crate::error_handler) attribute to a function to mark it as
1314    /// an error handler:
1315    ///
1316    /// ```rust
1317    /// use pavex::error_handler;
1318    /// use pavex::Response;
1319    ///
1320    /// pub enum LoginError {
1321    ///     InvalidCredentials,
1322    ///     DatabaseError,
1323    /// }
1324    ///
1325    /// #[error_handler]
1326    /// pub fn login_error_handler(e: &LoginError) -> Response {
1327    ///     match e {
1328    ///         LoginError::InvalidCredentials => Response::unauthorized(),
1329    ///         LoginError::DatabaseError => Response::internal_server_error(),
1330    ///     }
1331    /// }
1332    ///```
1333    ///
1334    /// The [`error_handler`](macro@crate::error_handler) attribute will define a new constant,
1335    /// named `LOGIN_ERROR_HANDLER`.\
1336    /// Pass the constant to [`.error_handler()`][`Blueprint::error_handler`] to register
1337    /// the newly defined error handler:
1338    ///
1339    /// ```rust
1340    /// # use pavex::Blueprint;
1341    /// # fn blueprint(LOGIN_ERROR_HANDLER: pavex::blueprint::ErrorHandler) {
1342    /// let mut bp = Blueprint::new();
1343    /// bp.error_handler(LOGIN_ERROR_HANDLER);
1344    /// # }
1345    /// ```
1346    ///
1347    /// # Example: method handler
1348    ///
1349    /// You're not limited to free functions. Methods can be used as error handlers too:
1350    ///
1351    /// ```rust
1352    /// use pavex::methods;
1353    /// use pavex::Response;
1354    ///
1355    /// pub enum LoginError {
1356    ///     InvalidCredentials,
1357    ///     DatabaseError,
1358    /// }
1359    ///
1360    /// #[methods]
1361    /// impl LoginError {
1362    ///     #[error_handler]
1363    ///     pub fn to_response(&self) -> Response {
1364    ///         match self {
1365    ///             LoginError::InvalidCredentials => Response::unauthorized(),
1366    ///             LoginError::DatabaseError => Response::internal_server_error(),
1367    ///         }
1368    ///     }
1369    /// }
1370    /// ```
1371    ///
1372    /// For methods, you must add a [`#[methods]`](macro@crate::methods) annotation on the `impl` block it belongs to,
1373    /// in addition to the [`#[error_handler]`](macro@crate::error_handler) annotation on the method itself.\
1374    /// The generated constant is named `<type_name>_<method_name>`, in constant case:
1375    ///
1376    /// ```rust
1377    /// # use pavex::Blueprint;
1378    /// # fn blueprint(LOGIN_ERROR_TO_RESPONSE: pavex::blueprint::ErrorHandler) {
1379    /// let mut bp = Blueprint::new();
1380    /// bp.error_handler(LOGIN_ERROR_TO_RESPONSE);
1381    /// # }
1382    /// ```
1383    pub fn error_handler(&mut self, m: ErrorHandler) -> RegisteredErrorHandler<'_> {
1384        let registered = pavex_bp_schema::ErrorHandler {
1385            coordinates: coordinates2coordinates(m.coordinates),
1386            registered_at: Location::caller(),
1387        };
1388        self.push_component(registered);
1389        RegisteredErrorHandler {
1390            blueprint: &mut self.schema,
1391        }
1392    }
1393
1394    #[track_caller]
1395    /// Register a type to be used as input parameter to the (generated) `ApplicationState::new`
1396    /// method.
1397    ///
1398    /// # Guide
1399    ///
1400    /// Check out the ["Dependency injection"](https://pavex.dev/docs/guide/dependency_injection)
1401    /// section of Pavex's guide for a thorough introduction to dependency injection
1402    /// in Pavex applications.
1403    pub fn prebuilt(&mut self, prebuilt: Prebuilt) -> RegisteredPrebuilt<'_> {
1404        let registered = pavex_bp_schema::PrebuiltType {
1405            coordinates: coordinates2coordinates(prebuilt.coordinates),
1406            cloning_policy: None,
1407            registered_at: Location::caller(),
1408        };
1409        let component_id = self.push_component(registered);
1410        RegisteredPrebuilt {
1411            blueprint: &mut self.schema,
1412            component_id,
1413        }
1414    }
1415
1416    /// Register a component and return its id (i.e. its index in the `components` vector).
1417    fn push_component(&mut self, component: impl Into<pavex_bp_schema::Component>) -> usize {
1418        let id = self.schema.components.len();
1419        self.schema.components.push(component.into());
1420        id
1421    }
1422}
1423
1424/// Methods to serialize and deserialize a [`Blueprint`].
1425/// These are used to pass the blueprint data to Pavex's CLI.
1426impl Blueprint {
1427    /// Serialize the [`Blueprint`] to a file in RON format.
1428    ///
1429    /// The file is only written to disk if the content of the blueprint has changed.
1430    pub fn persist(&self, filepath: &std::path::Path) -> Result<(), anyhow::Error> {
1431        let config = ron::ser::PrettyConfig::new();
1432        let contents = ron::ser::to_string_pretty(&self.schema, config)?;
1433        persist_if_changed::persist_if_changed(filepath, contents.as_bytes())?;
1434        Ok(())
1435    }
1436
1437    /// Read a RON-encoded [`Blueprint`] from a file.
1438    pub fn load(filepath: &std::path::Path) -> Result<Self, anyhow::Error> {
1439        let file = fs_err::OpenOptions::new().read(true).open(filepath)?;
1440        let value: BlueprintSchema = ron::de::from_reader(&file)?;
1441        Ok(Self { schema: value })
1442    }
1443}