Skip to main content

reliar_core/
headers.rs

1//! Validating custom-header newtype (SRS §13, §13.1, §14, ADR 0004, ADR 0011).
2
3use core::fmt;
4use std::collections::HashMap;
5
6use crate::ids::contains_control_char;
7
8/// Application-defined metadata Reliar does not understand: a validating newtype, never a
9/// `HashMap` alias and never exposed through `Deref` (ADR 0011). Reserves the entire `reliar-`
10/// prefix (case-insensitive) so framework metadata is never duplicated here — see
11/// [`Metadata`](crate::Metadata) for the one canonical source of truth (ADR 0004, §14).
12#[derive(Clone, Default, PartialEq, Eq)]
13pub struct Headers(HashMap<String, String>);
14
15/// **Never derived.** Header values are application-supplied and may be secrets (SRS §33,
16/// conventions §9): every value prints as `<redacted>`, keys print verbatim so the shape of a
17/// header set is still debuggable.
18impl fmt::Debug for Headers {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.debug_map()
21            .entries(self.0.keys().map(|k| (k, "<redacted>")))
22            .finish()
23    }
24}
25
26impl Headers {
27    /// Case-insensitively reserved prefix; [`Self::insert`] rejects any key starting with it.
28    pub const RESERVED_PREFIX: &'static str = "reliar-";
29    /// Maximum key length in bytes.
30    pub const MAX_KEY_LEN: usize = 128;
31    /// Maximum value length in bytes.
32    pub const MAX_VALUE_LEN: usize = 1024;
33    /// Maximum number of distinct headers on one envelope.
34    pub const MAX_COUNT: usize = 32;
35
36    /// Inserts a header, returning the previous value if the key was already present.
37    ///
38    /// Returns `Err` — never a silent drop or overwrite — for: the reserved `reliar-` prefix
39    /// (matched case-insensitively, so `Reliar-Correlation-Id` is rejected too), an empty key, a
40    /// key or value containing a control character (including CR/LF — a header-injection surface
41    /// once a mapper writes the value onto the wire, same rule as [`crate::CorrelationId`],
42    /// [`crate::EndpointAddress`] and [`crate::ContentType`]), a key over [`Self::MAX_KEY_LEN`],
43    /// or a value over [`Self::MAX_VALUE_LEN`]. Replacing a key that is already present never
44    /// counts against [`Self::MAX_COUNT`]; adding a genuinely new key while already at the cap
45    /// does.
46    ///
47    /// # Errors
48    ///
49    /// Returns [`HeaderError`] for a reserved, empty, control-character-containing, or
50    /// over-length key, a control-character-containing or over-length value, or a new key that
51    /// would exceed [`Self::MAX_COUNT`].
52    pub fn insert(
53        &mut self,
54        key: impl Into<String>,
55        value: impl Into<String>,
56    ) -> Result<Option<String>, HeaderError> {
57        let key = key.into();
58        let value = value.into();
59
60        if Self::has_reserved_prefix(&key) {
61            return Err(HeaderError::Reserved { key });
62        }
63        if key.is_empty() {
64            return Err(HeaderError::EmptyKey);
65        }
66        if contains_control_char(&key) {
67            return Err(HeaderError::ControlCharacterInKey { key });
68        }
69        if key.len() > Self::MAX_KEY_LEN {
70            return Err(HeaderError::KeyTooLong { len: key.len() });
71        }
72        if contains_control_char(&value) {
73            return Err(HeaderError::ControlCharacterInValue { key });
74        }
75        if value.len() > Self::MAX_VALUE_LEN {
76            return Err(HeaderError::ValueTooLong { len: value.len() });
77        }
78        if !self.0.contains_key(&key) && self.0.len() >= Self::MAX_COUNT {
79            return Err(HeaderError::TooManyHeaders {
80                limit: Self::MAX_COUNT,
81            });
82        }
83
84        Ok(self.0.insert(key, value))
85    }
86
87    /// Looks up a header by exact key.
88    #[must_use]
89    pub fn get(&self, key: &str) -> Option<&str> {
90        self.0.get(key).map(String::as_str)
91    }
92
93    /// Removes a header, returning its value if present.
94    pub fn remove(&mut self, key: &str) -> Option<String> {
95        self.0.remove(key)
96    }
97
98    /// The number of headers stored.
99    #[must_use]
100    pub fn len(&self) -> usize {
101        self.0.len()
102    }
103
104    /// Returns `true` if no headers are stored.
105    #[must_use]
106    pub fn is_empty(&self) -> bool {
107        self.0.is_empty()
108    }
109
110    /// Iterates over every stored header as borrowed string slices.
111    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
112        self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
113    }
114
115    fn has_reserved_prefix(key: &str) -> bool {
116        key.len() >= Self::RESERVED_PREFIX.len()
117            && key.as_bytes()[..Self::RESERVED_PREFIX.len()]
118                .eq_ignore_ascii_case(Self::RESERVED_PREFIX.as_bytes())
119    }
120}
121
122/// [`Headers::insert`] failures.
123#[derive(Debug, Clone, PartialEq, Eq)]
124#[non_exhaustive]
125pub enum HeaderError {
126    /// The key starts with the reserved `reliar-` prefix (case-insensitive).
127    Reserved {
128        /// The rejected key.
129        key: String,
130    },
131    /// The key was empty.
132    EmptyKey,
133    /// The key contained a control character (including CR/LF — a header-injection surface).
134    ControlCharacterInKey {
135        /// The rejected key.
136        key: String,
137    },
138    /// The value for this key contained a control character (including CR/LF). The value
139    /// itself is never carried on the error — it may be a secret (SRS §33); the key is, so the
140    /// caller can tell which header was rejected.
141    ControlCharacterInValue {
142        /// The key whose value was rejected.
143        key: String,
144    },
145    /// The key exceeded [`Headers::MAX_KEY_LEN`].
146    KeyTooLong {
147        /// The key's actual length in bytes.
148        len: usize,
149    },
150    /// The value exceeded [`Headers::MAX_VALUE_LEN`].
151    ValueTooLong {
152        /// The value's actual length in bytes.
153        len: usize,
154    },
155    /// Inserting a new key would exceed [`Headers::MAX_COUNT`].
156    TooManyHeaders {
157        /// The configured limit.
158        limit: usize,
159    },
160}
161
162impl fmt::Display for HeaderError {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            Self::Reserved { key } => {
166                write!(f, "header key {key:?} uses the reserved `reliar-` prefix")
167            }
168            Self::EmptyKey => f.write_str("header key must not be empty"),
169            Self::ControlCharacterInKey { key } => {
170                write!(f, "header key {key:?} contains a control character")
171            }
172            Self::ControlCharacterInValue { key } => {
173                write!(
174                    f,
175                    "header value for key {key:?} contains a control character"
176                )
177            }
178            Self::KeyTooLong { len } => write!(
179                f,
180                "header key length {len} exceeds the maximum of {}",
181                Headers::MAX_KEY_LEN
182            ),
183            Self::ValueTooLong { len } => write!(
184                f,
185                "header value length {len} exceeds the maximum of {}",
186                Headers::MAX_VALUE_LEN
187            ),
188            Self::TooManyHeaders { limit } => {
189                write!(f, "header count would exceed the maximum of {limit}")
190            }
191        }
192    }
193}
194
195impl std::error::Error for HeaderError {}