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