aws_smithy_http/
header.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Utilities for parsing information from headers
7
8use aws_smithy_types::date_time::Format;
9use aws_smithy_types::primitive::Parse;
10use aws_smithy_types::DateTime;
11use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
12use std::borrow::Cow;
13use std::error::Error;
14use std::fmt;
15use std::str::FromStr;
16
17/// An error was encountered while parsing a header
18#[derive(Debug)]
19pub struct ParseError {
20    message: Cow<'static, str>,
21    source: Option<Box<dyn Error + Send + Sync + 'static>>,
22}
23
24impl ParseError {
25    /// Create a new parse error with the given `message`
26    pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
27        Self {
28            message: message.into(),
29            source: None,
30        }
31    }
32
33    /// Attach a source to this error.
34    pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
35        Self {
36            source: Some(source.into()),
37            ..self
38        }
39    }
40}
41
42impl fmt::Display for ParseError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "output failed to parse in headers: {}", self.message)
45    }
46}
47
48impl Error for ParseError {
49    fn source(&self) -> Option<&(dyn Error + 'static)> {
50        self.source.as_ref().map(|err| err.as_ref() as _)
51    }
52}
53
54/// Read all the dates from the header map at `key` according the `format`
55///
56/// This is separate from `read_many` below because we need to invoke `DateTime::read` to take advantage
57/// of comma-aware parsing
58pub fn many_dates<'a>(
59    values: impl Iterator<Item = &'a str>,
60    format: Format,
61) -> Result<Vec<DateTime>, ParseError> {
62    let mut out = vec![];
63    for header in values {
64        let mut header = header;
65        while !header.is_empty() {
66            let (v, next) = DateTime::read(header, format, ',').map_err(|err| {
67                ParseError::new(format!("header could not be parsed as date: {}", err))
68            })?;
69            out.push(v);
70            header = next;
71        }
72    }
73    Ok(out)
74}
75
76/// Returns an iterator over pairs where the first element is the unprefixed header name that
77/// starts with the input `key` prefix, and the second element is the full header name.
78pub fn headers_for_prefix<'a>(
79    header_names: impl Iterator<Item = &'a str>,
80    key: &'a str,
81) -> impl Iterator<Item = (&'a str, &'a str)> {
82    let lower_key = key.to_ascii_lowercase();
83    header_names
84        .filter(move |k| k.starts_with(&lower_key))
85        .map(move |k| (&k[key.len()..], k))
86}
87
88/// Convert a `HeaderValue` into a `Vec<T>` where `T: FromStr`
89pub fn read_many_from_str<'a, T: FromStr>(
90    values: impl Iterator<Item = &'a str>,
91) -> Result<Vec<T>, ParseError>
92where
93    T::Err: Error + Send + Sync + 'static,
94{
95    read_many(values, |v: &str| {
96        v.parse().map_err(|err| {
97            ParseError::new("failed during `FromString` conversion").with_source(err)
98        })
99    })
100}
101
102/// Convert a `HeaderValue` into a `Vec<T>` where `T: Parse`
103pub fn read_many_primitive<'a, T: Parse>(
104    values: impl Iterator<Item = &'a str>,
105) -> Result<Vec<T>, ParseError> {
106    read_many(values, |v: &str| {
107        T::parse_smithy_primitive(v)
108            .map_err(|err| ParseError::new("failed reading a list of primitives").with_source(err))
109    })
110}
111
112/// Read many comma / header delimited values from HTTP headers for `FromStr` types
113fn read_many<'a, T>(
114    values: impl Iterator<Item = &'a str>,
115    f: impl Fn(&str) -> Result<T, ParseError>,
116) -> Result<Vec<T>, ParseError> {
117    let mut out = vec![];
118    for header in values {
119        let mut header = header.as_bytes();
120        while !header.is_empty() {
121            let (v, next) = read_one(header, &f)?;
122            out.push(v);
123            header = next;
124        }
125    }
126    Ok(out)
127}
128
129/// Read exactly one or none from a headers iterator
130///
131/// This function does not perform comma splitting like `read_many`
132pub fn one_or_none<'a, T: FromStr>(
133    mut values: impl Iterator<Item = &'a str>,
134) -> Result<Option<T>, ParseError>
135where
136    T::Err: Error + Send + Sync + 'static,
137{
138    let first = match values.next() {
139        Some(v) => v,
140        None => return Ok(None),
141    };
142    match values.next() {
143        None => T::from_str(first.trim())
144            .map_err(|err| ParseError::new("failed to parse string").with_source(err))
145            .map(Some),
146        Some(_) => Err(ParseError::new(
147            "expected a single value but found multiple",
148        )),
149    }
150}
151
152/// Given an HTTP request, set a request header if that header was not already set.
153pub fn set_request_header_if_absent<V>(
154    request: http_02x::request::Builder,
155    key: HeaderName,
156    value: V,
157) -> http_02x::request::Builder
158where
159    HeaderValue: TryFrom<V>,
160    <HeaderValue as TryFrom<V>>::Error: Into<http_02x::Error>,
161{
162    if !request
163        .headers_ref()
164        .map(|map| map.contains_key(&key))
165        .unwrap_or(false)
166    {
167        request.header(key, value)
168    } else {
169        request
170    }
171}
172
173/// Given an HTTP response, set a response header if that header was not already set.
174pub fn set_response_header_if_absent<V>(
175    response: http_02x::response::Builder,
176    key: HeaderName,
177    value: V,
178) -> http_02x::response::Builder
179where
180    HeaderValue: TryFrom<V>,
181    <HeaderValue as TryFrom<V>>::Error: Into<http_02x::Error>,
182{
183    if !response
184        .headers_ref()
185        .map(|map| map.contains_key(&key))
186        .unwrap_or(false)
187    {
188        response.header(key, value)
189    } else {
190        response
191    }
192}
193
194/// Functions for parsing multiple comma-delimited header values out of a
195/// single header. This parsing adheres to
196/// [RFC-7230's specification of header values](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6).
197mod parse_multi_header {
198    use super::ParseError;
199    use std::borrow::Cow;
200
201    fn trim(s: Cow<'_, str>) -> Cow<'_, str> {
202        match s {
203            Cow::Owned(s) => Cow::Owned(s.trim().into()),
204            Cow::Borrowed(s) => Cow::Borrowed(s.trim()),
205        }
206    }
207
208    fn replace<'a>(value: Cow<'a, str>, pattern: &str, replacement: &str) -> Cow<'a, str> {
209        if value.contains(pattern) {
210            Cow::Owned(value.replace(pattern, replacement))
211        } else {
212            value
213        }
214    }
215
216    /// Reads a single value out of the given input, and returns a tuple containing
217    /// the parsed value and the remainder of the slice that can be used to parse
218    /// more values.
219    pub(crate) fn read_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
220        for (index, &byte) in input.iter().enumerate() {
221            let current_slice = &input[index..];
222            match byte {
223                b' ' | b'\t' => { /* skip whitespace */ }
224                b'"' => return read_quoted_value(&current_slice[1..]),
225                _ => {
226                    let (value, rest) = read_unquoted_value(current_slice)?;
227                    return Ok((trim(value), rest));
228                }
229            }
230        }
231
232        // We only end up here if the entire header value was whitespace or empty
233        Ok((Cow::Borrowed(""), &[]))
234    }
235
236    fn read_unquoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
237        let next_delim = input.iter().position(|&b| b == b',').unwrap_or(input.len());
238        let (first, next) = input.split_at(next_delim);
239        let first = std::str::from_utf8(first)
240            .map_err(|_| ParseError::new("header was not valid utf-8"))?;
241        Ok((Cow::Borrowed(first), then_comma(next).unwrap()))
242    }
243
244    /// Reads a header value that is surrounded by quotation marks and may have escaped
245    /// quotes inside of it.
246    fn read_quoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
247        for index in 0..input.len() {
248            match input[index] {
249                b'"' if index == 0 || input[index - 1] != b'\\' => {
250                    let mut inner = Cow::Borrowed(
251                        std::str::from_utf8(&input[0..index])
252                            .map_err(|_| ParseError::new("header was not valid utf-8"))?,
253                    );
254                    inner = replace(inner, "\\\"", "\"");
255                    inner = replace(inner, "\\\\", "\\");
256                    let rest = then_comma(&input[(index + 1)..])?;
257                    return Ok((inner, rest));
258                }
259                _ => {}
260            }
261        }
262        Err(ParseError::new(
263            "header value had quoted value without end quote",
264        ))
265    }
266
267    fn then_comma(s: &[u8]) -> Result<&[u8], ParseError> {
268        if s.is_empty() {
269            Ok(s)
270        } else if s.starts_with(b",") {
271            Ok(&s[1..])
272        } else {
273            Err(ParseError::new("expected delimiter `,`"))
274        }
275    }
276}
277
278/// Read one comma delimited value for `FromStr` types
279fn read_one<'a, T>(
280    s: &'a [u8],
281    f: &impl Fn(&str) -> Result<T, ParseError>,
282) -> Result<(T, &'a [u8]), ParseError> {
283    let (value, rest) = parse_multi_header::read_value(s)?;
284    Ok((f(&value)?, rest))
285}
286
287/// Conditionally quotes and escapes a header value if the header value contains a comma or quote.
288pub fn quote_header_value<'a>(value: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
289    let value = value.into();
290    if value.trim().len() != value.len()
291        || value.contains('"')
292        || value.contains(',')
293        || value.contains('(')
294        || value.contains(')')
295    {
296        Cow::Owned(format!(
297            "\"{}\"",
298            value.replace('\\', "\\\\").replace('"', "\\\"")
299        ))
300    } else {
301        value
302    }
303}
304
305/// Given two [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
306/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
307pub fn append_merge_header_maps(
308    mut lhs: HeaderMap<HeaderValue>,
309    rhs: HeaderMap<HeaderValue>,
310) -> HeaderMap<HeaderValue> {
311    let mut last_header_name_seen = None;
312    for (header_name, header_value) in rhs.into_iter() {
313        // For each yielded item that has None provided for the `HeaderName`,
314        // then the associated header name is the same as that of the previously
315        // yielded item. The first yielded item will have `HeaderName` set.
316        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
317        match (&mut last_header_name_seen, header_name) {
318            (_, Some(header_name)) => {
319                lhs.append(header_name.clone(), header_value);
320                last_header_name_seen = Some(header_name);
321            }
322            (Some(header_name), None) => {
323                lhs.append(header_name.clone(), header_value);
324            }
325            (None, None) => unreachable!(),
326        };
327    }
328
329    lhs
330}
331
332#[cfg(test)]
333mod test {
334    use super::quote_header_value;
335    use crate::header::{
336        append_merge_header_maps, headers_for_prefix, many_dates, read_many_from_str,
337        read_many_primitive, set_request_header_if_absent, set_response_header_if_absent,
338        ParseError,
339    };
340    use aws_smithy_runtime_api::http::Request;
341    use aws_smithy_types::error::display::DisplayErrorContext;
342    use aws_smithy_types::{date_time::Format, DateTime};
343    use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
344    use std::collections::HashMap;
345
346    #[test]
347    fn put_on_request_if_absent() {
348        let builder = http_02x::Request::builder().header("foo", "bar");
349        let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
350        let builder =
351            set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
352        let req = builder.body(()).expect("valid request");
353        assert_eq!(
354            req.headers().get_all("foo").iter().collect::<Vec<_>>(),
355            vec!["bar"]
356        );
357        assert_eq!(
358            req.headers().get_all("other").iter().collect::<Vec<_>>(),
359            vec!["value"]
360        );
361    }
362
363    #[test]
364    fn put_on_response_if_absent() {
365        let builder = http_02x::Response::builder().header("foo", "bar");
366        let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
367        let builder =
368            set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
369        let response = builder.body(()).expect("valid response");
370        assert_eq!(
371            response.headers().get_all("foo").iter().collect::<Vec<_>>(),
372            vec!["bar"]
373        );
374        assert_eq!(
375            response
376                .headers()
377                .get_all("other")
378                .iter()
379                .collect::<Vec<_>>(),
380            vec!["value"]
381        );
382    }
383
384    #[test]
385    fn parse_floats() {
386        let test_request = http_02x::Request::builder()
387            .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
388            .header("X-Float-Error", "notafloat")
389            .body(())
390            .unwrap();
391        assert_eq!(
392            read_many_primitive::<f32>(
393                test_request
394                    .headers()
395                    .get_all("X-Float-Multi")
396                    .iter()
397                    .map(|v| v.to_str().unwrap())
398            )
399            .expect("valid"),
400            vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
401        );
402        let message = format!(
403            "{}",
404            DisplayErrorContext(
405                read_many_primitive::<f32>(
406                    test_request
407                        .headers()
408                        .get_all("X-Float-Error")
409                        .iter()
410                        .map(|v| v.to_str().unwrap())
411                )
412                .expect_err("invalid")
413            )
414        );
415        let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
416        assert!(
417            message.starts_with(expected),
418            "expected '{message}' to start with '{expected}'"
419        );
420    }
421
422    #[test]
423    fn test_many_dates() {
424        let test_request = http_02x::Request::builder()
425            .header("Empty", "")
426            .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
427            .header(
428                "MultipleHttpDates",
429                "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
430            )
431            .header("SingleEpochSeconds", "1234.5678")
432            .header("MultipleEpochSeconds", "1234.5678,9012.3456")
433            .body(())
434            .unwrap();
435        let read = |name: &str, format: Format| {
436            many_dates(
437                test_request
438                    .headers()
439                    .get_all(name)
440                    .iter()
441                    .map(|v| v.to_str().unwrap()),
442                format,
443            )
444        };
445        let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
446        assert_eq!(
447            read_valid("Empty", Format::DateTime),
448            Vec::<DateTime>::new()
449        );
450        assert_eq!(
451            read_valid("SingleHttpDate", Format::HttpDate),
452            vec![DateTime::from_secs_and_nanos(1445412480, 0)]
453        );
454        assert_eq!(
455            read_valid("MultipleHttpDates", Format::HttpDate),
456            vec![
457                DateTime::from_secs_and_nanos(1445412480, 0),
458                DateTime::from_secs_and_nanos(1445498880, 0)
459            ]
460        );
461        assert_eq!(
462            read_valid("SingleEpochSeconds", Format::EpochSeconds),
463            vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
464        );
465        assert_eq!(
466            read_valid("MultipleEpochSeconds", Format::EpochSeconds),
467            vec![
468                DateTime::from_secs_and_nanos(1234, 567_800_000),
469                DateTime::from_secs_and_nanos(9012, 345_600_000)
470            ]
471        );
472    }
473
474    #[test]
475    fn read_many_strings() {
476        let test_request = http_02x::Request::builder()
477            .header("Empty", "")
478            .header("Foo", "  foo")
479            .header("FooTrailing", "foo   ")
480            .header("FooInQuotes", "\"  foo  \"")
481            .header("CommaInQuotes", "\"foo,bar\",baz")
482            .header("CommaInQuotesTrailing", "\"foo,bar\",baz  ")
483            .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
484            .header(
485                "QuoteInQuotesWithSpaces",
486                "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
487            )
488            .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
489            .header("EmptyQuotes", "\"\",baz")
490            .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
491            .body(())
492            .unwrap();
493        let read = |name: &str| {
494            read_many_from_str::<String>(
495                test_request
496                    .headers()
497                    .get_all(name)
498                    .iter()
499                    .map(|v| v.to_str().unwrap()),
500            )
501        };
502        let read_valid = |name: &str| read(name).expect("valid");
503        assert_eq!(read_valid("Empty"), Vec::<String>::new());
504        assert_eq!(read_valid("Foo"), vec!["foo"]);
505        assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
506        assert_eq!(read_valid("FooInQuotes"), vec!["  foo  "]);
507        assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
508        assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
509        assert_eq!(
510            read_valid("QuoteInQuotes"),
511            vec!["foo\",bar", "\"asdf\"", "baz"]
512        );
513        assert_eq!(
514            read_valid("QuoteInQuotesWithSpaces"),
515            vec!["foo\",bar", "\"asdf\"", "baz"]
516        );
517        assert!(read("JunkFollowingQuotes").is_err());
518        assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
519        assert_eq!(
520            read_valid("EscapedSlashesInQuotes"),
521            vec!["foo", "(foo\\bar)"]
522        );
523    }
524
525    #[test]
526    fn read_many_bools() {
527        let test_request = http_02x::Request::builder()
528            .header("X-Bool-Multi", "true,false")
529            .header("X-Bool-Multi", "true")
530            .header("X-Bool", "true")
531            .header("X-Bool-Invalid", "truth,falsy")
532            .header("X-Bool-Single", "true,false,true,true")
533            .header("X-Bool-Quoted", "true,\"false\",true,true")
534            .body(())
535            .unwrap();
536        assert_eq!(
537            read_many_primitive::<bool>(
538                test_request
539                    .headers()
540                    .get_all("X-Bool-Multi")
541                    .iter()
542                    .map(|v| v.to_str().unwrap())
543            )
544            .expect("valid"),
545            vec![true, false, true]
546        );
547
548        assert_eq!(
549            read_many_primitive::<bool>(
550                test_request
551                    .headers()
552                    .get_all("X-Bool")
553                    .iter()
554                    .map(|v| v.to_str().unwrap())
555            )
556            .unwrap(),
557            vec![true]
558        );
559        assert_eq!(
560            read_many_primitive::<bool>(
561                test_request
562                    .headers()
563                    .get_all("X-Bool-Single")
564                    .iter()
565                    .map(|v| v.to_str().unwrap())
566            )
567            .unwrap(),
568            vec![true, false, true, true]
569        );
570        assert_eq!(
571            read_many_primitive::<bool>(
572                test_request
573                    .headers()
574                    .get_all("X-Bool-Quoted")
575                    .iter()
576                    .map(|v| v.to_str().unwrap())
577            )
578            .unwrap(),
579            vec![true, false, true, true]
580        );
581        read_many_primitive::<bool>(
582            test_request
583                .headers()
584                .get_all("X-Bool-Invalid")
585                .iter()
586                .map(|v| v.to_str().unwrap()),
587        )
588        .expect_err("invalid");
589    }
590
591    #[test]
592    fn check_read_many_i16() {
593        let test_request = http_02x::Request::builder()
594            .header("X-Multi", "123,456")
595            .header("X-Multi", "789")
596            .header("X-Num", "777")
597            .header("X-Num-Invalid", "12ef3")
598            .header("X-Num-Single", "1,2,3,-4,5")
599            .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
600            .body(())
601            .unwrap();
602        assert_eq!(
603            read_many_primitive::<i16>(
604                test_request
605                    .headers()
606                    .get_all("X-Multi")
607                    .iter()
608                    .map(|v| v.to_str().unwrap())
609            )
610            .expect("valid"),
611            vec![123, 456, 789]
612        );
613
614        assert_eq!(
615            read_many_primitive::<i16>(
616                test_request
617                    .headers()
618                    .get_all("X-Num")
619                    .iter()
620                    .map(|v| v.to_str().unwrap())
621            )
622            .unwrap(),
623            vec![777]
624        );
625        assert_eq!(
626            read_many_primitive::<i16>(
627                test_request
628                    .headers()
629                    .get_all("X-Num-Single")
630                    .iter()
631                    .map(|v| v.to_str().unwrap())
632            )
633            .unwrap(),
634            vec![1, 2, 3, -4, 5]
635        );
636        assert_eq!(
637            read_many_primitive::<i16>(
638                test_request
639                    .headers()
640                    .get_all("X-Num-Quoted")
641                    .iter()
642                    .map(|v| v.to_str().unwrap())
643            )
644            .unwrap(),
645            vec![1, 2, 3, -4, 5]
646        );
647        read_many_primitive::<i16>(
648            test_request
649                .headers()
650                .get_all("X-Num-Invalid")
651                .iter()
652                .map(|v| v.to_str().unwrap()),
653        )
654        .expect_err("invalid");
655    }
656
657    #[test]
658    fn test_prefix_headers() {
659        let test_request = Request::try_from(
660            http_02x::Request::builder()
661                .header("X-Prefix-A", "123,456")
662                .header("X-Prefix-B", "789")
663                .header("X-Prefix-C", "777")
664                .header("X-Prefix-C", "777")
665                .body(())
666                .unwrap(),
667        )
668        .unwrap();
669        let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
670            headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
671                .map(|(key, header_name)| {
672                    let values = test_request.headers().get_all(header_name);
673                    read_many_primitive(values).map(|v| (key.to_string(), v))
674                })
675                .collect();
676        let resp = resp.expect("valid");
677        assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
678    }
679
680    #[test]
681    fn test_quote_header_value() {
682        assert_eq!("", &quote_header_value(""));
683        assert_eq!("foo", &quote_header_value("foo"));
684        assert_eq!("\"  foo\"", &quote_header_value("  foo"));
685        assert_eq!("foo bar", &quote_header_value("foo bar"));
686        assert_eq!("\"foo,bar\"", &quote_header_value("foo,bar"));
687        assert_eq!("\",\"", &quote_header_value(","));
688        assert_eq!("\"\\\"foo\\\"\"", &quote_header_value("\"foo\""));
689        assert_eq!("\"\\\"f\\\\oo\\\"\"", &quote_header_value("\"f\\oo\""));
690        assert_eq!("\"(\"", &quote_header_value("("));
691        assert_eq!("\")\"", &quote_header_value(")"));
692    }
693
694    #[test]
695    fn test_append_merge_header_maps_with_shared_key() {
696        let header_name = HeaderName::from_static("some_key");
697        let left_header_value = HeaderValue::from_static("lhs value");
698        let right_header_value = HeaderValue::from_static("rhs value");
699
700        let mut left_hand_side_headers = HeaderMap::new();
701        left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
702
703        let mut right_hand_side_headers = HeaderMap::new();
704        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
705
706        let merged_header_map =
707            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
708        let actual_merged_values: Vec<_> =
709            merged_header_map.get_all(header_name).into_iter().collect();
710
711        let expected_merged_values = vec![left_header_value, right_header_value];
712
713        assert_eq!(actual_merged_values, expected_merged_values);
714    }
715
716    #[test]
717    fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
718        let header_name = HeaderName::from_static("some_key");
719        let left_header_value_1 = HeaderValue::from_static("lhs value 1");
720        let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
721        let right_header_value = HeaderValue::from_static("rhs value");
722
723        let mut left_hand_side_headers = HeaderMap::new();
724        left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
725        left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
726
727        let mut right_hand_side_headers = HeaderMap::new();
728        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
729
730        let merged_header_map =
731            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
732        let actual_merged_values: Vec<_> =
733            merged_header_map.get_all(header_name).into_iter().collect();
734
735        let expected_merged_values =
736            vec![left_header_value_1, left_header_value_2, right_header_value];
737
738        assert_eq!(actual_merged_values, expected_merged_values);
739    }
740
741    #[test]
742    fn test_append_merge_header_maps_with_empty_left_hand_map() {
743        let header_name = HeaderName::from_static("some_key");
744        let right_header_value_1 = HeaderValue::from_static("rhs value 1");
745        let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
746
747        let left_hand_side_headers = HeaderMap::new();
748
749        let mut right_hand_side_headers = HeaderMap::new();
750        right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
751        right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
752
753        let merged_header_map =
754            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
755        let actual_merged_values: Vec<_> =
756            merged_header_map.get_all(header_name).into_iter().collect();
757
758        let expected_merged_values = vec![right_header_value_1, right_header_value_2];
759
760        assert_eq!(actual_merged_values, expected_merged_values);
761    }
762}