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
use ControlFlow;
use Error;
/// Indicates how a recoverable error should affect contextual control flow.
///
/// `Catch` is returned from fallible operations whose errors may be handled in
/// more than one way. Rather than deciding globally whether an error is fatal,
/// `Catch` lets the caller describe whether the error should break out of the
/// current context or continue from the next recoverable boundary.
///
/// The meaning of [`ControlFlow::Break`] and [`ControlFlow::Continue`] depends
/// on where the `Catch` is handled:
///
/// - In [`before`] middleware, `Break` rejects the request while `Continue`
/// skips the wrapped middleware and resumes execution with [`Next`].
///
/// - In a WebSocket listener passed to [`ws`], `Break` closes the connection
/// while `Continue` keeps the connection open and restarts the listener.
///
/// - In other adapters, `Break` and `Continue` may map to whatever boundary the
/// adapter defines as fatal or recoverable.
///
/// See [`Propagate`] for convenience methods that convert common result-like
/// data structures into a result with an error type of `Catch`.
///
/// [`Next`]: crate::Next
/// [`before`]: fn@crate::before
/// [`ws`]: fn@crate::ws
pub type Catch = ;
/// Convert result-like data structures into contextual control flow.
///
/// `Propagate` provides convenience methods for indicating whether an error
/// is considered fatal or recoverable.
///
/// The context in which a [`Catch`] occurs determines the significance of each
/// discriminant in [`ControlFlow`].
///
/// # Example
///
/// When returned from a request decorator in [`before`] middleware, the
/// `Continue` variant skips the wrapped middleware and resumes execution with
/// [`Next`], while the `Break` variant immediately rejects the request with
/// the contained error.
///
/// ```
/// use via::error::{Catch, Propagate};
/// use via::{Request, err};
/// #
/// # #[derive(Clone, Copy, PartialEq)]
/// # struct Identity([u8; 16]);
/// #
/// # impl std::str::FromStr for Identity {
/// # type Err = via::Error;
/// #
/// # fn from_str(input: &str) -> Result<Self, Self::Err> {
/// # todo!()
/// # }
/// # }
///
/// pub fn restore(request: &mut Request) -> Result<(), Catch> {
/// let token = request
/// .cookies()
/// .get("via-session")
/// .ok_or_else(|| err!(401, "unauthorized."))
/// .and_then(|cookie| cookie.value().parse::<Identity>())
/// .or_continue()?;
/// // ^^^^^^^^^^^
/// //
/// // Continue to the next middleware.
/// // The user may be trying to create an account or login.
/// //
/// // Insert the identity token into the request extensions.
/// request.extensions_mut().insert(token);
/// //
/// // Request decorated successfully.
/// Ok(())
/// }
/// ```
///
/// When returned from a web socket listener passed to [`ws`], the `Continue`
/// discriminant keeps the web socket connection open and restarts the listener
/// where the `Break` variant immediately closes the connection.
///
/// ```
/// use std::ops::ControlFlow;
///
/// use via::error::Propagate;
/// use via::ws::{self, Channel, Request};
/// #
/// # #[derive(Clone, Copy)]
/// # pub struct Identity([u8; 16]);
/// #
/// # pub trait Session {
/// # fn session(&self) -> Option<&Identity>;
/// # }
/// #
/// # impl Session for Request {
/// # fn session(&self) -> Option<&Identity> { todo!() }
/// # }
///
/// pub async fn echo(mut channel: Channel, request: Request) -> ws::Result {
/// let Some(_me) = request.session().copied() else {
/// return via::err!(401, "unauthorized.").or_break();
/// // ^^^^^^^^
/// // If the request is not authenticated, close the connection.
/// };
///
/// while let Some(message) = channel.recv().await {
/// if message.is_binary() || message.is_text() {
/// let text = message.to_text().or_continue()?;
/// // ^^^^^^^^^^^
/// // Invalid UTF-8 rejects this frame and restarts the receive loop.
/// //
/// // Valid UTF-8 is echoed back to the client.
/// channel.send(text).await?;
/// // ^
/// // Send errors unconditionally close the connection as they
/// // imply a dropped receiver.
/// } else if message.is_close() {
/// break; // A close opcode gracefully terminates the listener.
/// }
/// }
///
/// Ok(())
/// }
/// ```
///
/// [`Next`]: crate::Next
/// [`before`]: fn@crate::before
/// [`ws`]: fn@crate::ws