praxis_filter/body/mode.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2024 Praxis Contributors
3
4//! Body delivery mode declarations.
5
6// -----------------------------------------------------------------------------
7// BodyMode
8// -----------------------------------------------------------------------------
9
10/// Controls how body chunks are delivered to a filter.
11///
12/// ```
13/// use praxis_filter::BodyMode;
14///
15/// let mode = BodyMode::default();
16/// assert!(matches!(mode, BodyMode::Stream));
17///
18/// let buffered = BodyMode::StreamBuffer {
19/// max_bytes: Some(1024),
20/// };
21/// assert!(matches!(
22/// buffered,
23/// BodyMode::StreamBuffer {
24/// max_bytes: Some(1024)
25/// }
26/// ));
27///
28/// let stream_buf = BodyMode::StreamBuffer { max_bytes: None };
29/// assert!(matches!(
30/// stream_buf,
31/// BodyMode::StreamBuffer { max_bytes: None }
32/// ));
33///
34/// let limited = BodyMode::StreamBuffer {
35/// max_bytes: Some(1024),
36/// };
37/// assert!(matches!(
38/// limited,
39/// BodyMode::StreamBuffer {
40/// max_bytes: Some(1024)
41/// }
42/// ));
43///
44/// let size_limited = BodyMode::SizeLimit { max_bytes: 2048 };
45/// assert!(matches!(
46/// size_limited,
47/// BodyMode::SizeLimit { max_bytes: 2048 }
48/// ));
49/// ```
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51#[non_exhaustive]
52pub enum BodyMode {
53 /// Deliver chunks as they arrive. Low latency, low memory.
54 ///
55 /// ```
56 /// use praxis_filter::BodyMode;
57 ///
58 /// let mode = BodyMode::Stream;
59 /// assert_eq!(mode, BodyMode::default());
60 /// ```
61 #[default]
62 Stream,
63
64 /// Deliver chunks incrementally (like [`Stream`]) but accumulate
65 /// them and defer upstream forwarding until a filter returns
66 /// [`FilterAction::Release`] or end-of-stream is reached.
67 ///
68 /// When `max_bytes` is `Some`, requests exceeding the limit
69 /// receive 413. When `None` and no global body size ceiling is
70 /// configured, body buffering is unbounded; a warning is emitted
71 /// at pipeline build time in that case.
72 ///
73 /// ```
74 /// use praxis_filter::BodyMode;
75 ///
76 /// let unlimited = BodyMode::StreamBuffer { max_bytes: None };
77 /// assert!(matches!(
78 /// unlimited,
79 /// BodyMode::StreamBuffer { max_bytes: None }
80 /// ));
81 ///
82 /// let limited = BodyMode::StreamBuffer {
83 /// max_bytes: Some(4096),
84 /// };
85 /// assert!(matches!(
86 /// limited,
87 /// BodyMode::StreamBuffer {
88 /// max_bytes: Some(4096)
89 /// }
90 /// ));
91 /// ```
92 ///
93 /// [`Stream`]: BodyMode::Stream
94 /// [`FilterAction::Release`]: crate::FilterAction::Release
95 StreamBuffer {
96 /// Optional maximum body size in bytes. `None` means
97 /// unbounded buffering (a warning is emitted at build time).
98 max_bytes: Option<usize>,
99 },
100
101 /// Stream chunks through without buffering, but enforce a byte
102 /// ceiling. Returns 413 if the running byte count exceeds
103 /// `max_bytes`.
104 ///
105 /// Used by [`apply_body_limits`] when no filter needs body access
106 /// but a global size limit is configured.
107 ///
108 /// ```
109 /// use praxis_filter::BodyMode;
110 ///
111 /// let mode = BodyMode::SizeLimit { max_bytes: 4096 };
112 /// assert!(matches!(mode, BodyMode::SizeLimit { max_bytes: 4096 }));
113 /// ```
114 ///
115 /// [`apply_body_limits`]: crate::FilterPipeline::apply_body_limits
116 SizeLimit {
117 /// Maximum body size in bytes.
118 max_bytes: usize,
119 },
120}
121
122// -----------------------------------------------------------------------------
123// Tests
124// -----------------------------------------------------------------------------
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn body_mode_default_is_stream() {
132 assert_eq!(
133 BodyMode::default(),
134 BodyMode::Stream,
135 "default BodyMode should be Stream"
136 );
137 }
138
139 #[test]
140 fn body_mode_stream_buffer_unlimited() {
141 let mode = BodyMode::StreamBuffer { max_bytes: None };
142 assert!(
143 matches!(mode, BodyMode::StreamBuffer { max_bytes: None }),
144 "StreamBuffer should support unlimited mode"
145 );
146 }
147
148 #[test]
149 fn body_mode_stream_buffer_with_limit() {
150 let mode = BodyMode::StreamBuffer { max_bytes: Some(4096) };
151 assert!(
152 matches!(mode, BodyMode::StreamBuffer { max_bytes: Some(4096) }),
153 "StreamBuffer should carry configured byte limit"
154 );
155 }
156
157 #[test]
158 fn body_mode_size_limit_carries_limit() {
159 let mode = BodyMode::SizeLimit { max_bytes: 2048 };
160 assert!(
161 matches!(mode, BodyMode::SizeLimit { max_bytes: 2048 }),
162 "SizeLimit variant should carry configured limit"
163 );
164 }
165
166 #[test]
167 fn body_mode_size_limit_is_distinct_from_stream_buffer() {
168 assert_ne!(
169 BodyMode::SizeLimit { max_bytes: 100 },
170 BodyMode::StreamBuffer { max_bytes: Some(100) },
171 "SizeLimit and StreamBuffer should be distinct even with same limit"
172 );
173 }
174
175 #[test]
176 fn body_mode_stream_buffer_is_distinct_from_stream() {
177 assert_ne!(
178 BodyMode::StreamBuffer { max_bytes: None },
179 BodyMode::Stream,
180 "StreamBuffer and Stream should be distinct variants"
181 );
182 }
183}