aws_smithy_runtime_api/http/
headers.rs
1use crate::http::error::{HttpError, NonUtf8Header};
9use std::borrow::Cow;
10use std::fmt::Debug;
11use std::str::FromStr;
12
13#[derive(Clone, Default, Debug)]
15pub struct Headers {
16 pub(super) headers: http_02x::HeaderMap<HeaderValue>,
17}
18
19impl<'a> IntoIterator for &'a Headers {
20 type Item = (&'a str, &'a str);
21 type IntoIter = HeadersIter<'a>;
22
23 fn into_iter(self) -> Self::IntoIter {
24 HeadersIter {
25 inner: self.headers.iter(),
26 }
27 }
28}
29
30pub struct HeadersIter<'a> {
32 inner: http_02x::header::Iter<'a, HeaderValue>,
33}
34
35impl<'a> Iterator for HeadersIter<'a> {
36 type Item = (&'a str, &'a str);
37
38 fn next(&mut self) -> Option<Self::Item> {
39 self.inner.next().map(|(k, v)| (k.as_str(), v.as_ref()))
40 }
41}
42
43impl Headers {
44 pub fn new() -> Self {
46 Self::default()
47 }
48
49 #[cfg(feature = "http-1x")]
50 pub(crate) fn http1_headermap(self) -> http_1x::HeaderMap {
51 let mut headers = http_1x::HeaderMap::new();
52 headers.reserve(self.headers.len());
53 headers.extend(self.headers.into_iter().map(|(k, v)| {
54 (
55 k.map(|n| {
56 http_1x::HeaderName::from_bytes(n.as_str().as_bytes()).expect("proven valid")
57 }),
58 v.into_http1x(),
59 )
60 }));
61 headers
62 }
63
64 #[cfg(feature = "http-02x")]
65 pub(crate) fn http0_headermap(self) -> http_02x::HeaderMap {
66 let mut headers = http_02x::HeaderMap::new();
67 headers.reserve(self.headers.len());
68 headers.extend(self.headers.into_iter().map(|(k, v)| (k, v.into_http02x())));
69 headers
70 }
71
72 pub fn get(&self, key: impl AsRef<str>) -> Option<&str> {
77 self.headers.get(key.as_ref()).map(|v| v.as_ref())
78 }
79
80 pub fn get_all(&self, key: impl AsRef<str>) -> impl Iterator<Item = &str> {
82 self.headers
83 .get_all(key.as_ref())
84 .iter()
85 .map(|v| v.as_ref())
86 }
87
88 pub fn iter(&self) -> HeadersIter<'_> {
90 HeadersIter {
91 inner: self.headers.iter(),
92 }
93 }
94
95 pub fn len(&self) -> usize {
97 self.headers.len()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.len() == 0
103 }
104
105 pub fn contains_key(&self, key: impl AsRef<str>) -> bool {
107 self.headers.contains_key(key.as_ref())
108 }
109
110 pub fn insert(
117 &mut self,
118 key: impl AsHeaderComponent,
119 value: impl AsHeaderComponent,
120 ) -> Option<String> {
121 let key = header_name(key, false).unwrap();
122 let value = header_value(value.into_maybe_static().unwrap(), false).unwrap();
123 self.headers
124 .insert(key, value)
125 .map(|old_value| old_value.into())
126 }
127
128 pub fn try_insert(
134 &mut self,
135 key: impl AsHeaderComponent,
136 value: impl AsHeaderComponent,
137 ) -> Result<Option<String>, HttpError> {
138 let key = header_name(key, true)?;
139 let value = header_value(value.into_maybe_static()?, true)?;
140 Ok(self
141 .headers
142 .insert(key, value)
143 .map(|old_value| old_value.into()))
144 }
145
146 pub fn append(&mut self, key: impl AsHeaderComponent, value: impl AsHeaderComponent) -> bool {
151 let key = header_name(key.into_maybe_static().unwrap(), false).unwrap();
152 let value = header_value(value.into_maybe_static().unwrap(), false).unwrap();
153 self.headers.append(key, value)
154 }
155
156 pub fn try_append(
160 &mut self,
161 key: impl AsHeaderComponent,
162 value: impl AsHeaderComponent,
163 ) -> Result<bool, HttpError> {
164 let key = header_name(key.into_maybe_static()?, true)?;
165 let value = header_value(value.into_maybe_static()?, true)?;
166 Ok(self.headers.append(key, value))
167 }
168
169 pub fn remove(&mut self, key: impl AsRef<str>) -> Option<String> {
173 self.headers
174 .remove(key.as_ref())
175 .map(|h| h.as_str().to_string())
176 }
177}
178
179#[cfg(feature = "http-02x")]
180impl TryFrom<http_02x::HeaderMap> for Headers {
181 type Error = HttpError;
182
183 fn try_from(value: http_02x::HeaderMap) -> Result<Self, Self::Error> {
184 if let Some(utf8_error) = value.iter().find_map(|(k, v)| {
185 std::str::from_utf8(v.as_bytes())
186 .err()
187 .map(|err| NonUtf8Header::new(k.as_str().to_owned(), v.as_bytes().to_vec(), err))
188 }) {
189 Err(HttpError::non_utf8_header(utf8_error))
190 } else {
191 let mut string_safe_headers: http_02x::HeaderMap<HeaderValue> = Default::default();
192 string_safe_headers.extend(
193 value
194 .into_iter()
195 .map(|(k, v)| (k, HeaderValue::from_http02x(v).expect("validated above"))),
196 );
197 Ok(Headers {
198 headers: string_safe_headers,
199 })
200 }
201 }
202}
203
204#[cfg(feature = "http-1x")]
205impl TryFrom<http_1x::HeaderMap> for Headers {
206 type Error = HttpError;
207
208 fn try_from(value: http_1x::HeaderMap) -> Result<Self, Self::Error> {
209 if let Some(utf8_error) = value.iter().find_map(|(k, v)| {
210 std::str::from_utf8(v.as_bytes())
211 .err()
212 .map(|err| NonUtf8Header::new(k.as_str().to_owned(), v.as_bytes().to_vec(), err))
213 }) {
214 Err(HttpError::non_utf8_header(utf8_error))
215 } else {
216 let mut string_safe_headers: http_02x::HeaderMap<HeaderValue> = Default::default();
217 string_safe_headers.extend(value.into_iter().map(|(k, v)| {
218 (
219 k.map(|v| {
220 http_02x::HeaderName::from_bytes(v.as_str().as_bytes())
221 .expect("known valid")
222 }),
223 HeaderValue::from_http1x(v).expect("validated above"),
224 )
225 }));
226 Ok(Headers {
227 headers: string_safe_headers,
228 })
229 }
230 }
231}
232
233use sealed::AsHeaderComponent;
234
235mod sealed {
236 use super::*;
237 pub trait AsHeaderComponent {
239 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError>;
241
242 fn as_str(&self) -> Result<&str, HttpError>;
244
245 fn repr_as_http02x_header_name(self) -> Result<http_02x::HeaderName, Self>
247 where
248 Self: Sized,
249 {
250 Err(self)
251 }
252 }
253
254 impl AsHeaderComponent for &'static str {
255 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
256 Ok(Cow::Borrowed(self))
257 }
258
259 fn as_str(&self) -> Result<&str, HttpError> {
260 Ok(self)
261 }
262 }
263
264 impl AsHeaderComponent for String {
265 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
266 Ok(Cow::Owned(self))
267 }
268
269 fn as_str(&self) -> Result<&str, HttpError> {
270 Ok(self)
271 }
272 }
273
274 impl AsHeaderComponent for Cow<'static, str> {
275 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
276 Ok(self)
277 }
278
279 fn as_str(&self) -> Result<&str, HttpError> {
280 Ok(self.as_ref())
281 }
282 }
283
284 impl AsHeaderComponent for http_02x::HeaderValue {
285 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
286 Ok(Cow::Owned(
287 std::str::from_utf8(self.as_bytes())
288 .map_err(|err| {
289 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
290 self.as_bytes().to_vec(),
291 err,
292 ))
293 })?
294 .to_string(),
295 ))
296 }
297
298 fn as_str(&self) -> Result<&str, HttpError> {
299 std::str::from_utf8(self.as_bytes()).map_err(|err| {
300 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
301 self.as_bytes().to_vec(),
302 err,
303 ))
304 })
305 }
306 }
307
308 impl AsHeaderComponent for http_02x::HeaderName {
309 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
310 Ok(self.to_string().into())
311 }
312
313 fn as_str(&self) -> Result<&str, HttpError> {
314 Ok(self.as_ref())
315 }
316
317 fn repr_as_http02x_header_name(self) -> Result<http_02x::HeaderName, Self>
318 where
319 Self: Sized,
320 {
321 Ok(self)
322 }
323 }
324}
325
326mod header_value {
327 use super::*;
328
329 #[derive(Debug, Clone)]
333 pub struct HeaderValue {
334 _private: Inner,
335 }
336
337 #[derive(Debug, Clone)]
338 enum Inner {
339 H0(http_02x::HeaderValue),
340 #[allow(dead_code)]
341 H1(http_1x::HeaderValue),
342 }
343
344 impl HeaderValue {
345 #[allow(dead_code)]
346 pub(crate) fn from_http02x(value: http_02x::HeaderValue) -> Result<Self, HttpError> {
347 let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
348 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
349 value.as_bytes().to_vec(),
350 err,
351 ))
352 })?;
353 Ok(Self {
354 _private: Inner::H0(value),
355 })
356 }
357
358 #[allow(dead_code)]
359 pub(crate) fn from_http1x(value: http_1x::HeaderValue) -> Result<Self, HttpError> {
360 let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
361 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
362 value.as_bytes().to_vec(),
363 err,
364 ))
365 })?;
366 Ok(Self {
367 _private: Inner::H1(value),
368 })
369 }
370
371 #[allow(dead_code)]
372 pub(crate) fn into_http02x(self) -> http_02x::HeaderValue {
373 match self._private {
374 Inner::H0(v) => v,
375 Inner::H1(v) => http_02x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
376 }
377 }
378
379 #[allow(dead_code)]
380 pub(crate) fn into_http1x(self) -> http_1x::HeaderValue {
381 match self._private {
382 Inner::H1(v) => v,
383 Inner::H0(v) => http_1x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
384 }
385 }
386 }
387
388 impl AsRef<str> for HeaderValue {
389 fn as_ref(&self) -> &str {
390 let bytes = match &self._private {
391 Inner::H0(v) => v.as_bytes(),
392 Inner::H1(v) => v.as_bytes(),
393 };
394 std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
395 }
396 }
397
398 impl From<HeaderValue> for String {
399 fn from(value: HeaderValue) -> Self {
400 value.as_ref().to_string()
401 }
402 }
403
404 impl HeaderValue {
405 pub fn as_str(&self) -> &str {
407 self.as_ref()
408 }
409 }
410
411 impl FromStr for HeaderValue {
412 type Err = HttpError;
413
414 fn from_str(s: &str) -> Result<Self, Self::Err> {
415 HeaderValue::try_from(s.to_string())
416 }
417 }
418
419 impl TryFrom<String> for HeaderValue {
420 type Error = HttpError;
421
422 fn try_from(value: String) -> Result<Self, Self::Error> {
423 Ok(HeaderValue::from_http02x(
424 http_02x::HeaderValue::try_from(value).map_err(HttpError::invalid_header_value)?,
425 )
426 .expect("input was a string"))
427 }
428 }
429}
430
431pub use header_value::HeaderValue;
432
433type MaybeStatic = Cow<'static, str>;
434
435fn header_name(
436 name: impl AsHeaderComponent,
437 panic_safe: bool,
438) -> Result<http_02x::HeaderName, HttpError> {
439 name.repr_as_http02x_header_name().or_else(|name| {
440 name.into_maybe_static().and_then(|mut cow| {
441 if cow.chars().any(|c| c.is_ascii_uppercase()) {
442 cow = Cow::Owned(cow.to_ascii_uppercase());
443 }
444 match cow {
445 Cow::Borrowed(s) if panic_safe => {
446 http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
447 }
448 Cow::Borrowed(static_s) => Ok(http_02x::HeaderName::from_static(static_s)),
449 Cow::Owned(s) => {
450 http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
451 }
452 }
453 })
454 })
455}
456
457fn header_value(value: MaybeStatic, panic_safe: bool) -> Result<HeaderValue, HttpError> {
458 let header = match value {
459 Cow::Borrowed(b) if panic_safe => {
460 http_02x::HeaderValue::try_from(b).map_err(HttpError::invalid_header_value)?
461 }
462 Cow::Borrowed(b) => http_02x::HeaderValue::from_static(b),
463 Cow::Owned(s) => {
464 http_02x::HeaderValue::try_from(s).map_err(HttpError::invalid_header_value)?
465 }
466 };
467 HeaderValue::from_http02x(header)
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn headers_can_be_any_string() {
476 let _: HeaderValue = "😹".parse().expect("can be any string");
477 let _: HeaderValue = "abcd".parse().expect("can be any string");
478 let _ = "a\nb"
479 .parse::<HeaderValue>()
480 .expect_err("cannot contain control characters");
481 }
482
483 #[test]
484 fn no_panic_insert_upper_case_header_name() {
485 let mut headers = Headers::new();
486 headers.insert("I-Have-Upper-Case", "foo");
487 }
488 #[test]
489 fn no_panic_append_upper_case_header_name() {
490 let mut headers = Headers::new();
491 headers.append("I-Have-Upper-Case", "foo");
492 }
493
494 #[test]
495 #[should_panic]
496 fn panic_insert_invalid_ascii_key() {
497 let mut headers = Headers::new();
498 headers.insert("💩", "foo");
499 }
500 #[test]
501 #[should_panic]
502 fn panic_insert_invalid_header_value() {
503 let mut headers = Headers::new();
504 headers.insert("foo", "💩");
505 }
506 #[test]
507 #[should_panic]
508 fn panic_append_invalid_ascii_key() {
509 let mut headers = Headers::new();
510 headers.append("💩", "foo");
511 }
512 #[test]
513 #[should_panic]
514 fn panic_append_invalid_header_value() {
515 let mut headers = Headers::new();
516 headers.append("foo", "💩");
517 }
518
519 #[test]
520 fn no_panic_try_insert_invalid_ascii_key() {
521 let mut headers = Headers::new();
522 assert!(headers.try_insert("💩", "foo").is_err());
523 }
524 #[test]
525 fn no_panic_try_insert_invalid_header_value() {
526 let mut headers = Headers::new();
527 assert!(headers
528 .try_insert(
529 "foo",
530 http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
532 )
533 .is_err());
534 }
535 #[test]
536 fn no_panic_try_append_invalid_ascii_key() {
537 let mut headers = Headers::new();
538 assert!(headers.try_append("💩", "foo").is_err());
539 }
540 #[test]
541 fn no_panic_try_append_invalid_header_value() {
542 let mut headers = Headers::new();
543 assert!(headers
544 .try_insert(
545 "foo",
546 http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
548 )
549 .is_err());
550 }
551
552 proptest::proptest! {
553 #[test]
554 fn insert_header_prop_test(input in ".*") {
555 let mut headers = Headers::new();
556 let _ = headers.try_insert(input.clone(), input);
557 }
558
559 #[test]
560 fn append_header_prop_test(input in ".*") {
561 let mut headers = Headers::new();
562 let _ = headers.try_append(input.clone(), input);
563 }
564 }
565}