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 #[cfg(feature = "http-1x")]
326 impl AsHeaderComponent for http_1x::HeaderName {
327 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
328 Ok(self.to_string().into())
329 }
330
331 fn as_str(&self) -> Result<&str, HttpError> {
332 Ok(self.as_ref())
333 }
334 }
335
336 #[cfg(feature = "http-1x")]
337 impl AsHeaderComponent for http_1x::HeaderValue {
338 fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
339 Ok(Cow::Owned(
340 std::str::from_utf8(self.as_bytes())
341 .map_err(|err| {
342 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
343 self.as_bytes().to_vec(),
344 err,
345 ))
346 })?
347 .to_string(),
348 ))
349 }
350
351 fn as_str(&self) -> Result<&str, HttpError> {
352 std::str::from_utf8(self.as_bytes()).map_err(|err| {
353 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
354 self.as_bytes().to_vec(),
355 err,
356 ))
357 })
358 }
359 }
360}
361
362mod header_value {
363 use super::*;
364
365 #[derive(Debug, Clone)]
369 pub struct HeaderValue {
370 _private: Inner,
371 }
372
373 #[derive(Debug, Clone)]
374 enum Inner {
375 H0(http_02x::HeaderValue),
376 #[allow(dead_code)]
377 H1(http_1x::HeaderValue),
378 }
379
380 impl HeaderValue {
381 #[allow(dead_code)]
382 pub(crate) fn from_http02x(value: http_02x::HeaderValue) -> Result<Self, HttpError> {
383 let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
384 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
385 value.as_bytes().to_vec(),
386 err,
387 ))
388 })?;
389 Ok(Self {
390 _private: Inner::H0(value),
391 })
392 }
393
394 #[allow(dead_code)]
395 pub(crate) fn from_http1x(value: http_1x::HeaderValue) -> Result<Self, HttpError> {
396 let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
397 HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
398 value.as_bytes().to_vec(),
399 err,
400 ))
401 })?;
402 Ok(Self {
403 _private: Inner::H1(value),
404 })
405 }
406
407 #[allow(dead_code)]
408 pub(crate) fn into_http02x(self) -> http_02x::HeaderValue {
409 match self._private {
410 Inner::H0(v) => v,
411 Inner::H1(v) => http_02x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
412 }
413 }
414
415 #[allow(dead_code)]
416 pub(crate) fn into_http1x(self) -> http_1x::HeaderValue {
417 match self._private {
418 Inner::H1(v) => v,
419 Inner::H0(v) => http_1x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
420 }
421 }
422 }
423
424 impl AsRef<str> for HeaderValue {
425 fn as_ref(&self) -> &str {
426 let bytes = match &self._private {
427 Inner::H0(v) => v.as_bytes(),
428 Inner::H1(v) => v.as_bytes(),
429 };
430 std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
431 }
432 }
433
434 impl From<HeaderValue> for String {
435 fn from(value: HeaderValue) -> Self {
436 value.as_ref().to_string()
437 }
438 }
439
440 impl HeaderValue {
441 pub fn as_str(&self) -> &str {
443 self.as_ref()
444 }
445 }
446
447 impl FromStr for HeaderValue {
448 type Err = HttpError;
449
450 fn from_str(s: &str) -> Result<Self, Self::Err> {
451 HeaderValue::try_from(s.to_string())
452 }
453 }
454
455 impl TryFrom<String> for HeaderValue {
456 type Error = HttpError;
457
458 fn try_from(value: String) -> Result<Self, Self::Error> {
459 Ok(HeaderValue::from_http02x(
460 http_02x::HeaderValue::try_from(value).map_err(HttpError::invalid_header_value)?,
461 )
462 .expect("input was a string"))
463 }
464 }
465}
466
467pub use header_value::HeaderValue;
468
469type MaybeStatic = Cow<'static, str>;
470
471fn header_name(
472 name: impl AsHeaderComponent,
473 panic_safe: bool,
474) -> Result<http_02x::HeaderName, HttpError> {
475 name.repr_as_http02x_header_name().or_else(|name| {
476 name.into_maybe_static().and_then(|mut cow| {
477 if cow.chars().any(|c| c.is_ascii_uppercase()) {
478 cow = Cow::Owned(cow.to_ascii_uppercase());
479 }
480 match cow {
481 Cow::Borrowed(s) if panic_safe => {
482 http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
483 }
484 Cow::Borrowed(static_s) => Ok(http_02x::HeaderName::from_static(static_s)),
485 Cow::Owned(s) => {
486 http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
487 }
488 }
489 })
490 })
491}
492
493fn header_value(value: MaybeStatic, panic_safe: bool) -> Result<HeaderValue, HttpError> {
494 let header = match value {
495 Cow::Borrowed(b) if panic_safe => {
496 http_02x::HeaderValue::try_from(b).map_err(HttpError::invalid_header_value)?
497 }
498 Cow::Borrowed(b) => http_02x::HeaderValue::from_static(b),
499 Cow::Owned(s) => {
500 http_02x::HeaderValue::try_from(s).map_err(HttpError::invalid_header_value)?
501 }
502 };
503 HeaderValue::from_http02x(header)
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn headers_can_be_any_string() {
512 let _: HeaderValue = "😹".parse().expect("can be any string");
513 let _: HeaderValue = "abcd".parse().expect("can be any string");
514 let _ = "a\nb"
515 .parse::<HeaderValue>()
516 .expect_err("cannot contain control characters");
517 }
518
519 #[test]
520 fn no_panic_insert_upper_case_header_name() {
521 let mut headers = Headers::new();
522 headers.insert("I-Have-Upper-Case", "foo");
523 }
524 #[test]
525 fn no_panic_append_upper_case_header_name() {
526 let mut headers = Headers::new();
527 headers.append("I-Have-Upper-Case", "foo");
528 }
529
530 #[test]
531 #[should_panic]
532 fn panic_insert_invalid_ascii_key() {
533 let mut headers = Headers::new();
534 headers.insert("💩", "foo");
535 }
536 #[test]
537 #[should_panic]
538 fn panic_insert_invalid_header_value() {
539 let mut headers = Headers::new();
540 headers.insert("foo", "💩");
541 }
542 #[test]
543 #[should_panic]
544 fn panic_append_invalid_ascii_key() {
545 let mut headers = Headers::new();
546 headers.append("💩", "foo");
547 }
548 #[test]
549 #[should_panic]
550 fn panic_append_invalid_header_value() {
551 let mut headers = Headers::new();
552 headers.append("foo", "💩");
553 }
554
555 #[test]
556 fn no_panic_try_insert_invalid_ascii_key() {
557 let mut headers = Headers::new();
558 assert!(headers.try_insert("💩", "foo").is_err());
559 }
560 #[test]
561 fn no_panic_try_insert_invalid_header_value() {
562 let mut headers = Headers::new();
563 assert!(headers
564 .try_insert(
565 "foo",
566 http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
568 )
569 .is_err());
570 }
571 #[test]
572 fn no_panic_try_append_invalid_ascii_key() {
573 let mut headers = Headers::new();
574 assert!(headers.try_append("💩", "foo").is_err());
575 }
576 #[test]
577 fn no_panic_try_append_invalid_header_value() {
578 let mut headers = Headers::new();
579 assert!(headers
580 .try_insert(
581 "foo",
582 http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
584 )
585 .is_err());
586 }
587
588 proptest::proptest! {
589 #[test]
590 fn insert_header_prop_test(input in ".*") {
591 let mut headers = Headers::new();
592 let _ = headers.try_insert(input.clone(), input);
593 }
594
595 #[test]
596 fn append_header_prop_test(input in ".*") {
597 let mut headers = Headers::new();
598 let _ = headers.try_append(input.clone(), input);
599 }
600 }
601}