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
use ControlFlow;
use crateCatch;
use crate::;
/// A middleware adapter that decorates a request before delegation.
///
/// `Before` runs a synchronous decorator function against the incoming
/// [`Request`] before invoking another middleware.
///
/// The decorator may mutate the request in place to derive, normalize, or cache
/// request state for downstream middleware. This is useful for work such as
/// restoring session identity, parsing cookies, or initializing extensions from
/// request metadata.
///
/// Decorators return [`Catch`], which determines how decoration failures affect
/// the pipeline:
///
/// - [`ControlFlow::Break`] terminates execution and returns the error.
/// - [`ControlFlow::Continue`] logs the error, skips the wrapped middleware,
/// and continues with [`Next`].
///
/// See [`before`] for a convenient constructor.
/// Creates middleware that decorates a request before invoking another
/// middleware.
///
/// The `decorator` receives a mutable reference to the request and may mutate
/// it in place before the wrapped middleware executes.
///
/// If decoration succeeds, the wrapped middleware is called. If decoration
/// fails with [`ControlFlow::Break`], the error is returned immediately. If it
/// fails with [`ControlFlow::Continue`], the error is logged and the request is
/// forwarded to the next middleware without calling the wrapped middleware.
///
/// # Example
///
/// Restore an identity token from the session cookie before conditionally
/// refreshing the active user session.
///
/// ```no_run
/// mod session {
/// // Implementations elided...
/// # use via::error::Catch;
/// # use via::guard::Predicate;
/// # use via::{Middleware, Request};
/// #
/// # use super::Buttercup;
/// #
/// # pub const COOKIE: &str = "via-session";
/// #
/// # pub fn needs_verified() -> fn(&Request<Buttercup>) -> bool { |_| true }
/// # pub fn restore(_: &mut Request<Buttercup>) -> Result<(), Catch> { todo!() }
/// # pub fn verify() -> impl Middleware<Buttercup> + 'static {
/// # via::middleware(|request, next| next.call(request))
/// # }
/// }
///
/// use cookie::Key;
/// use std::process::ExitCode;
/// use via::guard::{self, media, method};
/// use via::{Router, Server, cookies, rescue};
///
/// struct Buttercup {
/// secret: Key,
/// }
///
/// #[tokio::main]
/// async fn main() -> via::Result<ExitCode> {
/// // Define the routes that our application responds to.
/// let router = Router::new(|home| {
/// // Start defining descendants of "/".
/// let mut path = home.prefix();
///
/// // The /api namespace.
/// let mut api = path.push("api");
///
/// // If an error occurs, respond with JSON.
/// api.middleware(rescue::json().build());
///
/// // Parse and track changes that are made to the session cookie.
/// api.middleware(cookies([session::COOKIE]));
///
/// // Content negotiation and authentication guards.
/// api.middleware(guard::flat_map(
/// // Confirm that the client speaks JSON.
/// guard::content!(media::json()),
/// // Then, initialize the active user session.
/// via::before(
/// // Restore an identity token from the session cookie.
/// session::restore,
/// // If the request is read only or the active users account has
/// // been confirmed to exist in the past hour, skip verification.
/// //
/// // Such an optimization is sound so long as your app is the
/// // authoritative source of truth for the session and you
/// // properly end websocket sessions when a user deletes their
/// // account.
/// guard::filter(
/// guard::or((method::is_mutation(), session::needs_verified())),
/// session::verify(),
/// ),
/// ),
/// ));
///
/// // Start defining descendants of "/api".
/// let mut path = api.prefix();
/// });
///
/// // Setup our application, "Buttercup".
/// let buttercup = Buttercup {
/// secret: Key::generate(),
/// // secret: std::env::var("VIA_SECRET_KEY")
/// // .map(|secret| secret.as_bytes().try_into())
/// // .expect("missing required env var: VIA_SECRET_KEY")
/// // .expect("unexpected end of input while parsing VIA_SECRET_KEY"),
/// };
///
/// // Start listening at http://localhost:8080 for incoming requests.
/// Server::new(router, buttercup)
/// .listen(("127.0.0.1", 8080))
/// .await
///}
/// ```
///
/// <details>
/// <summary>Click here to view an example <code>mod session</code>.</summary>
///
/// ```rust
/// # use cookie::Key;
/// #
/// # struct Unicorn {
/// # secret: Key,
/// # }
/// #
/// # impl Unicorn {
/// # fn secret(&self) -> &Key { &self.secret }
/// # }
/// #
/// use via::error::{Catch, Error, Propagate};
/// use via::{Middleware, Request, err};
///
/// pub const COOKIE: &str = "via-session";
///
/// #[derive(Clone, Copy, PartialEq)]
/// pub struct Identity([u8; 16]);
///
/// impl std::str::FromStr for Identity {
/// type Err = Error;
///
/// fn from_str(input: &str) -> Result<Self, Self::Err> {
/// todo!()
/// }
/// }
///
/// /// Restore the active user session from the session cookie.
/// pub fn restore(request: &mut Request<Unicorn>) -> Result<(), Catch> {
/// // Authenticate the signed cookie jar using the signing secret
/// // stored in the application (Unicorn).
/// let jar = {
/// let secret = request.app().secret();
/// request.cookies().signed(secret)
/// };
///
/// let token = jar
/// // Retrieve the session cookie from the signed cookie jar.
/// .get(COOKIE)
/// // If the session cookie *is not* present, return a generic,
/// // `401 Unauthorized` error.
/// .ok_or_else(|| err!(401, "unauthorized."))
/// // If the session cookie *is* present, parse an identity token
/// // from the base64 encoded string in its value.
/// .and_then(|cookie| cookie.value().parse::<Identity>())
/// // If an error occurred, continue to the next middleware.
/// // The user may be trying to create an account or login.
/// .or_continue()?;
///
/// // Insert the identity token into the request extensions.
/// request.extensions_mut().insert(token);
///
/// // The request was decorated successfully.
/// Ok(())
/// }
///
/// /// Returns a middleware that verifies the active user account.
/// pub fn verify() -> impl Middleware<Unicorn> + 'static {
/// via::middleware(|request, next| {
/// todo!("verify that the active user has an account.")
/// })
/// }
/// ```
/// </details>