1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//! Customize how nested routes should behave.
use pavex_bp_schema::{
Blueprint as BlueprintSchema, Domain, Location, NestedBlueprint, PathPrefix,
};
use crate::Blueprint;
use super::Import;
/// The type returned by [`Blueprint::prefix`] and [`Blueprint::domain`].
///
/// Customize routing behaviour for a subset of routes.
///
/// [`Blueprint::prefix`]: crate::Blueprint::prefix
/// [`Blueprint::domain`]: crate::Blueprint::domain
#[must_use = "`prefix` and `domain` do nothing unless you invoke `nest` to register some routes under them"]
pub struct RoutingModifiers<'a> {
pub(super) blueprint: &'a mut BlueprintSchema,
pub(super) path_prefix: Option<PathPrefix>,
pub(super) domain: Option<Domain>,
}
impl<'a> RoutingModifiers<'a> {
pub(super) fn empty(blueprint: &'a mut BlueprintSchema) -> Self {
Self {
blueprint,
path_prefix: None,
domain: None,
}
}
/// Only requests to the specified domain will be forwarded to routes nested under this condition.
///
/// Check out [`Blueprint::domain`](crate::Blueprint::domain) for more details.
#[track_caller]
pub fn domain(mut self, domain: &str) -> Self {
let location = Location::caller();
self.domain = Some(Domain {
domain: domain.into(),
registered_at: location,
});
self
}
/// Prepends a common prefix to all routes nested under this condition.
///
/// If a prefix has already been set, it will be overridden.
///
/// Check out [`Blueprint::prefix`](crate::Blueprint::prefix) for more details.
#[track_caller]
pub fn prefix(mut self, prefix: &str) -> Self {
let location = Location::caller();
self.path_prefix = Some(PathPrefix {
path_prefix: prefix.into(),
registered_at: location,
});
self
}
#[track_caller]
#[doc(alias("scope"))]
/// Nest a [`Blueprint`], optionally applying a [common prefix](`Self::prefix`) and a [domain restriction](`Self::domain`) to all its routes.
///
/// Nesting also has consequences when it comes to constructors' visibility.
///
/// # Constructors
///
/// Constructors registered against the parent blueprint will be available to the nested
/// blueprint—they are **inherited**.
/// Constructors registered against the nested blueprint will **not** be available to other
/// sibling blueprints that are nested under the same parent—they are **private**.
///
/// Check out the example below to better understand the implications of nesting blueprints.
///
/// ## Visibility
///
/// ```rust
/// use pavex::{blueprint::from, Blueprint};
///
/// fn app() -> Blueprint {
/// let mut bp = Blueprint::new();
/// bp.constructor(DB_CONNECTION_POOL);
/// bp.nest(home_bp());
/// bp.nest(user_bp());
/// bp
/// }
///
/// /// All property-related routes and constructors.
/// fn home_bp() -> Blueprint {
/// let mut bp = Blueprint::new();
/// bp.import(from![crate::home]);
/// bp.routes(from![crate::home]);
/// bp
/// }
///
/// /// All user-related routes and constructors.
/// fn user_bp() -> Blueprint {
/// let mut bp = Blueprint::new();
/// bp.import(from![crate::user]);
/// bp.routes(from![crate::user]);
/// bp
/// }
///
/// # struct ConnectionPool;
/// #[pavex::singleton]
/// pub fn db_connection_pool() -> ConnectionPool {
/// // [...]
/// # todo!()
/// }
///
/// pub mod home {
/// // [...]
/// }
///
/// pub mod user {
/// # struct Session;
/// pub fn get_session() -> Session {
/// // [...]
/// # todo!()
/// }
/// // [...]
/// }
/// ```
///
/// In this example, we import two constructors:
/// - `crate::user::get_session`, for `Session`;
/// - `crate::db_connection_pool`, for `ConnectionPool`.
///
/// The constructors defined in the `crate::user` module are only imported by the `user_bp` blueprint.
/// Since we are **nesting** the `user_bp` blueprint, those constructors will only be available
/// to the routes declared in the `user_bp` blueprint.
/// If a route declared in `home_bp` tries to inject a `Session`, Pavex will report an error
/// at compile-time, complaining that there is no registered constructor for `Session`.
/// In other words, all constructors imported in the `user_bp` blueprint are **private**
/// and **isolated** from the rest of the application.
///
/// The `db_connection_pool` constructor, instead, is declared against the parent blueprint
/// and will therefore be available to all routes declared in `home_bp` and `user_bp`—i.e.
/// nested blueprints **inherit** all the constructors declared against their parent(s).
///
/// ## Precedence
///
/// If a constructor is declared against both the parent and one of its nested blueprints, the one
/// declared against the nested blueprint takes precedence.
///
/// ```rust
/// use pavex::{blueprint::from, Blueprint};
///
/// fn app() -> Blueprint {
/// let mut bp = Blueprint::new();
/// // These constructors are registered against the root blueprint and they're visible
/// // to all nested blueprints.
/// bp.import(from![crate::global]);
/// bp.nest(user_bp());
/// // [..]
/// bp
/// }
///
/// fn user_bp() -> Blueprint {
/// let mut bp = Blueprint::new();
/// // They can be overridden by a constructor for the same type registered
/// // against a nested blueprint.
/// // All routes in `user_bp` will use `user::get_session` instead of `global::get_session`.
/// bp.import(from![crate::user]);
/// // [...]
/// bp
/// }
///
/// pub mod global {
/// # struct Session;
/// pub fn get_session() -> Session {
/// // [...]
/// # todo!()
/// }
/// }
///
/// pub mod user {
/// # struct Session;
/// pub fn get_session() -> Session {
/// // [...]
/// # todo!()
/// }
/// }
/// ```
///
/// ## Singletons
///
/// There is one exception to the precedence rule: [singletons][Lifecycle::Singleton].
/// Pavex guarantees that there will be only one instance of a singleton type for the entire
/// lifecycle of the application. What should happen if two different constructors are registered for
/// the same `Singleton` type by two nested blueprints that share the same parent?
/// We can't honor both constructors without ending up with two different instances of the same
/// type, which would violate the singleton contract.
///
/// It goes one step further! Even if those two constructors are identical, what is the expected
/// behaviour? Does the user expect the same singleton instance to be injected in both blueprints?
/// Or does the user expect two different singleton instances to be injected in each nested blueprint?
///
/// To avoid this ambiguity, Pavex takes a conservative approach: a singleton constructor
/// must be registered **exactly once** for each type.
/// If multiple nested blueprints need access to the singleton, the constructor must be
/// registered against a common parent blueprint—the root blueprint, if necessary.
///
/// [Lifecycle::Singleton]: crate::blueprint::Lifecycle::Singleton
pub fn nest(self, bp: Blueprint) {
self.blueprint.components.push(
NestedBlueprint {
blueprint: bp.schema,
path_prefix: self.path_prefix,
nested_at: Location::caller(),
domain: self.domain,
}
.into(),
);
}
#[track_caller]
/// Register a group of routes.
///
/// Their path will be prepended with a common prefix if one was provided via [`.prefix()`][`Self::prefix`].
/// They will be restricted to a specific domain if one was specified via [`.domain()`][`Self::domain`].
///
/// # Example
///
/// ```
/// use pavex::{Blueprint, blueprint::from};
///
/// let mut bp = Blueprint::new();
/// bp.prefix("/api").routes(from![crate::api]);
/// ```
pub fn routes(self, import: Import) {
let mut bp = Blueprint::new();
bp.routes(import);
self.nest(bp);
}
}