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 {}