rama_http_headers/common/
connection.rs1use rama_core::error::BoxError;
2use rama_core::{combinators::Either, telemetry::tracing};
3use rama_http_types::{
4 HeaderName, HeaderValue,
5 header::{KEEP_ALIVE, UPGRADE},
6};
7use rama_utils::collections::NonEmptyVec;
8
9use crate::util::{
10 FlatCsvSeparator, TryFromValues, try_decode_flat_csv_header_values_as_non_empty_vec,
11 try_encode_non_empty_vec_as_flat_csv_header_value,
12};
13
14#[derive(Clone, Debug)]
44pub struct Connection(Directive);
45
46impl Connection {
47 pub fn iter_headers(&self) -> impl Iterator<Item = &HeaderName> {
48 match &self.0 {
49 Directive::Close => Either::A(std::iter::empty()),
50 Directive::Open(non_empty_vec) => Either::B(non_empty_vec.iter()),
51 }
52 }
53}
54
55#[derive(Clone, Debug)]
56enum Directive {
57 Close,
58 Open(NonEmptyVec<HeaderName>),
59}
60
61impl TryFrom<&Directive> for HeaderValue {
62 type Error = BoxError;
63
64 fn try_from(value: &Directive) -> Result<Self, Self::Error> {
65 match value {
66 Directive::Close => Ok(DIRECTIVE_HEADER_VALUE_CLOSE),
67 Directive::Open(values) => {
68 try_encode_non_empty_vec_as_flat_csv_header_value(values, FlatCsvSeparator::Comma)
69 }
70 }
71 }
72}
73
74impl TryFromValues for Directive {
75 fn try_from_values<'i, I>(values: &mut I) -> Result<Self, crate::Error>
76 where
77 Self: Sized,
78 I: Iterator<Item = &'i HeaderValue>,
79 {
80 match try_decode_flat_csv_header_values_as_non_empty_vec(values, FlatCsvSeparator::Comma) {
81 Ok(values) => {
82 if values.len() == 1 && values.first() == "close" {
83 Ok(Self::Close)
84 } else {
85 Ok(Self::Open(values))
86 }
87 }
88 Err(err) => {
89 tracing::trace!("invalid connection directive: {err}");
90 Err(crate::Error::invalid())
91 }
92 }
93 }
94}
95
96const DIRECTIVE_HEADER_VALUE_CLOSE: HeaderValue = HeaderValue::from_static("close");
97
98impl crate::TypedHeader for Connection {
99 fn name() -> &'static ::rama_http_types::header::HeaderName {
100 &::rama_http_types::header::CONNECTION
101 }
102}
103
104impl crate::HeaderDecode for Connection {
105 fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
106 where
107 I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
108 {
109 Directive::try_from_values(values).map(Self)
110 }
111}
112
113impl crate::HeaderEncode for Connection {
114 fn encode<E: Extend<::rama_http_types::HeaderValue>>(&self, values: &mut E) {
115 match HeaderValue::try_from(&self.0) {
116 Ok(value) => values.extend(::std::iter::once(value)),
117 Err(err) => {
118 rama_core::telemetry::tracing::debug!(
119 "failed to encode connection directive {:?} as flat csv header: {err}",
120 self.0,
121 );
122 }
123 }
124 }
125}
126
127impl Connection {
128 #[inline]
131 #[must_use]
132 pub fn open(headers: NonEmptyVec<HeaderName>) -> Self {
133 Self(Directive::Open(headers))
134 }
135
136 #[inline]
138 #[must_use]
139 pub fn close() -> Self {
140 Self(Directive::Close)
141 }
142
143 #[inline]
145 pub fn is_close(&self) -> bool {
146 matches!(self.0, Directive::Close)
147 }
148
149 #[inline]
151 #[must_use]
152 pub fn keep_alive() -> Self {
153 Self(Directive::Open(NonEmptyVec::new(KEEP_ALIVE.clone())))
154 }
155
156 #[inline]
158 #[must_use]
159 pub fn upgrade() -> Self {
160 Self(Directive::Open(NonEmptyVec::new(UPGRADE.clone())))
161 }
162
163 #[inline]
178 #[expect(clippy::needless_pass_by_value)]
179 pub fn contains_header(&self, name: impl PartialEq<HeaderName>) -> bool {
180 match &self.0 {
181 Directive::Close => false,
182 Directive::Open(values) => values.iter().any(|candidate| name.eq(candidate)),
183 }
184 }
185
186 #[inline]
197 pub fn contains_upgrade(&self) -> bool {
198 self.contains_header(&UPGRADE)
199 }
200
201 #[inline]
212 pub fn contains_keep_alive(&self) -> bool {
213 self.contains_header(&KEEP_ALIVE)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::super::{test_decode, test_encode};
220 use super::*;
221 use rama_utils::collections::non_empty_vec;
222
223 #[test]
224 fn decode_header_single_open() {
225 let Connection(directive) = test_decode(&["foo, bar"]).unwrap();
226 match directive {
227 Directive::Close => panic!("unexpecte close directive"),
228 Directive::Open(non_empty_vec) => {
229 assert_eq!(2, non_empty_vec.len());
230 assert_eq!(non_empty_vec[0], "foo");
231 assert_eq!(non_empty_vec[1], "bar");
232 }
233 }
234 }
235
236 #[test]
237 fn decode_header_single_close() {
238 let Connection(directive) = test_decode(&["close"]).unwrap();
239 match directive {
240 Directive::Close => (),
241 Directive::Open(non_empty_vec) => {
242 panic!("unexpected open directive, headers: {non_empty_vec:?}")
243 }
244 }
245 }
246
247 #[test]
248 fn encode_open() {
249 let allow = Connection::open(non_empty_vec![
250 ::rama_http_types::header::KEEP_ALIVE.clone(),
251 ::rama_http_types::header::TRAILER,
252 ]);
253
254 let headers = test_encode(allow);
255 assert_eq!(headers["connection"], "keep-alive, trailer");
256 }
257
258 #[test]
259 fn decode_with_empty_header_value() {
260 assert!(test_decode::<Connection>(&[""]).is_none());
261 }
262
263 #[test]
264 fn decode_with_no_headers() {
265 assert!(test_decode::<Connection>(&[]).is_none());
266 }
267
268 #[test]
269 fn decode_with_invalid_value_str() {
270 assert!(test_decode::<Connection>(&["foo foo, bar"]).is_none());
271 }
272}