AWS SDK

AWS SDK

rev. e48ccee0154f81d455955afa85c29300f723084d (ignoring whitespace)

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/header.rs

@@ -1,0 +762,0 @@
    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         -
    8         -
use aws_smithy_types::date_time::Format;
    9         -
use aws_smithy_types::primitive::Parse;
   10         -
use aws_smithy_types::DateTime;
   11         -
use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
   12         -
use std::borrow::Cow;
   13         -
use std::error::Error;
   14         -
use std::fmt;
   15         -
use std::str::FromStr;
   16         -
   17         -
/// An error was encountered while parsing a header
   18         -
#[derive(Debug)]
   19         -
pub struct ParseError {
   20         -
    message: Cow<'static, str>,
   21         -
    source: Option<Box<dyn Error + Send + Sync + 'static>>,
   22         -
}
   23         -
   24         -
impl 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         -
   42         -
impl 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         -
   48         -
impl 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
   58         -
pub 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.
   78         -
pub 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`
   89         -
pub fn read_many_from_str<'a, T: FromStr>(
   90         -
    values: impl Iterator<Item = &'a str>,
   91         -
) -> Result<Vec<T>, ParseError>
   92         -
where
   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`
  103         -
pub 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
  113         -
fn 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`
  132         -
pub fn one_or_none<'a, T: FromStr>(
  133         -
    mut values: impl Iterator<Item = &'a str>,
  134         -
) -> Result<Option<T>, ParseError>
  135         -
where
  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.
  153         -
pub fn set_request_header_if_absent<V>(
  154         -
    request: http_02x::request::Builder,
  155         -
    key: HeaderName,
  156         -
    value: V,
  157         -
) -> http_02x::request::Builder
  158         -
where
  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.
  174         -
pub fn set_response_header_if_absent<V>(
  175         -
    response: http_02x::response::Builder,
  176         -
    key: HeaderName,
  177         -
    value: V,
  178         -
) -> http_02x::response::Builder
  179         -
where
  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).
  197         -
mod 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
  279         -
fn 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.
  288         -
pub 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`.
  307         -
pub 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)]
  333         -
mod 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         -
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/label.rs

@@ -1,0 +64,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
    6         -
//! Formatting values as Smithy
    7         -
//! [httpLabel](https://smithy.io/2.0/spec/http-bindings.html#httplabel-trait)
    8         -
    9         -
use crate::urlencode::BASE_SET;
   10         -
use aws_smithy_types::date_time::{DateTimeFormatError, Format};
   11         -
use aws_smithy_types::DateTime;
   12         -
use percent_encoding::AsciiSet;
   13         -
   14         -
const GREEDY: &AsciiSet = &BASE_SET.remove(b'/');
   15         -
   16         -
/// The encoding strategy used when parsing an `httpLabel`.
   17         -
#[non_exhaustive]
   18         -
#[derive(Clone, Debug, Eq, PartialEq)]
   19         -
pub enum EncodingStrategy {
   20         -
    /// The default strategy when parsing an `httpLabel`. Only one path segment will be matched.
   21         -
    Default,
   22         -
    /// When parsing an `httpLabel`, this strategy will attempt to parse as many path segments as possible.
   23         -
    Greedy,
   24         -
}
   25         -
   26         -
/// Format a given `httpLabel` as a string according to an [`EncodingStrategy`]
   27         -
pub fn fmt_string<T: AsRef<str>>(t: T, strategy: EncodingStrategy) -> String {
   28         -
    let uri_set = if strategy == EncodingStrategy::Greedy {
   29         -
        GREEDY
   30         -
    } else {
   31         -
        BASE_SET
   32         -
    };
   33         -
    percent_encoding::utf8_percent_encode(t.as_ref(), uri_set).to_string()
   34         -
}
   35         -
   36         -
/// Format a given [`DateTime`] as a string according to an [`EncodingStrategy`]
   37         -
pub fn fmt_timestamp(t: &DateTime, format: Format) -> Result<String, DateTimeFormatError> {
   38         -
    Ok(fmt_string(t.fmt(format)?, EncodingStrategy::Default))
   39         -
}
   40         -
   41         -
#[cfg(test)]
   42         -
mod test {
   43         -
    use crate::label::{fmt_string, EncodingStrategy};
   44         -
    use http_02x::Uri;
   45         -
    use proptest::proptest;
   46         -
   47         -
    #[test]
   48         -
    fn greedy_params() {
   49         -
        assert_eq!(fmt_string("a/b", EncodingStrategy::Default), "a%2Fb");
   50         -
        assert_eq!(fmt_string("a/b", EncodingStrategy::Greedy), "a/b");
   51         -
    }
   52         -
   53         -
    proptest! {
   54         -
        #[test]
   55         -
        fn test_encode_request(s: String) {
   56         -
            let _: Uri = format!("http://host.example.com/{}", fmt_string(&s, EncodingStrategy::Default))
   57         -
                .parse()
   58         -
                .expect("all strings should be encoded properly");
   59         -
            let _: Uri = format!("http://host.example.com/{}", fmt_string(&s, EncodingStrategy::Greedy))
   60         -
                .parse()
   61         -
                .expect("all strings should be encoded properly");
   62         -
        }
   63         -
    }
   64         -
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/lib.rs

@@ -1,0 +46,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
    6         -
/* Automatically managed default lints */
    7         -
#![cfg_attr(docsrs, feature(doc_cfg))]
    8         -
/* End of automatically managed default lints */
    9         -
#![warn(
   10         -
    missing_docs,
   11         -
    rustdoc::missing_crate_level_docs,
   12         -
    unreachable_pub,
   13         -
    rust_2018_idioms
   14         -
)]
   15         -
   16         -
//! Core HTTP primitives for service clients generated by [smithy-rs](https://github.com/smithy-lang/smithy-rs) including:
   17         -
//! - HTTP Body implementation
   18         -
//! - Endpoint support
   19         -
//! - HTTP header deserialization
   20         -
//! - Event streams
   21         -
//!
   22         -
//! | Feature        | Description |
   23         -
//! |----------------|-------------|
   24         -
//! | `rt-tokio`     | Provides features that are dependent on `tokio` including the `ByteStream::from_path` util |
   25         -
//! | `event-stream` | Provides Sender/Receiver implementations for Event Stream codegen. |
   26         -
   27         -
#![allow(clippy::derive_partial_eq_without_eq)]
   28         -
   29         -
pub mod endpoint;
   30         -
// Marked as `doc(hidden)` because a type in the module is used both by this crate and by the code
   31         -
// generator, but not by external users. Also, by the module being `doc(hidden)` instead of it being
   32         -
// in `rust-runtime/inlineable`, each user won't have to pay the cost of running the module's tests
   33         -
// when compiling their generated SDK.
   34         -
#[doc(hidden)]
   35         -
pub mod futures_stream_adapter;
   36         -
pub mod header;
   37         -
pub mod label;
   38         -
pub mod operation;
   39         -
pub mod query;
   40         -
#[doc(hidden)]
   41         -
pub mod query_writer;
   42         -
   43         -
#[cfg(feature = "event-stream")]
   44         -
pub mod event_stream;
   45         -
   46         -
mod urlencode;

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/operation.rs

@@ -1,0 +13,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
    6         -
//! Deprecated metadata type.
    7         -
    8         -
/// Metadata added to the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag) that identifies the API being called.
    9         -
#[deprecated(
   10         -
    since = "0.60.2",
   11         -
    note = "Use aws_smithy_runtime_api::client::orchestrator::Metadata instead."
   12         -
)]
   13         -
pub type Metadata = aws_smithy_runtime_api::client::orchestrator::Metadata;

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/query.rs

@@ -1,0 +97,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
    6         -
//! Utilities for writing Smithy values into a query string.
    7         -
//!
    8         -
//! Formatting values into the query string as specified in
    9         -
//! [httpQuery](https://smithy.io/2.0/spec/http-bindings.html#httpquery-trait)
   10         -
   11         -
use crate::urlencode::BASE_SET;
   12         -
use aws_smithy_types::date_time::{DateTimeFormatError, Format};
   13         -
use aws_smithy_types::DateTime;
   14         -
use percent_encoding::utf8_percent_encode;
   15         -
   16         -
/// Format a given string as a query string.
   17         -
pub fn fmt_string<T: AsRef<str>>(t: T) -> String {
   18         -
    utf8_percent_encode(t.as_ref(), BASE_SET).to_string()
   19         -
}
   20         -
   21         -
/// Format a given [`DateTime`] as a query string.
   22         -
pub fn fmt_timestamp(t: &DateTime, format: Format) -> Result<String, DateTimeFormatError> {
   23         -
    Ok(fmt_string(t.fmt(format)?))
   24         -
}
   25         -
   26         -
/// Simple abstraction to enable appending params to a string as query params.
   27         -
///
   28         -
/// ```rust
   29         -
/// use aws_smithy_legacy_http::query::Writer;
   30         -
/// let mut s = String::from("www.example.com");
   31         -
/// let mut q = Writer::new(&mut s);
   32         -
/// q.push_kv("key", "value");
   33         -
/// q.push_v("another_value");
   34         -
/// assert_eq!(s, "www.example.com?key=value&another_value");
   35         -
/// ```
   36         -
#[allow(missing_debug_implementations)]
   37         -
pub struct Writer<'a> {
   38         -
    out: &'a mut String,
   39         -
    prefix: char,
   40         -
}
   41         -
   42         -
impl<'a> Writer<'a> {
   43         -
    /// Create a new query string writer.
   44         -
    pub fn new(out: &'a mut String) -> Self {
   45         -
        Writer { out, prefix: '?' }
   46         -
    }
   47         -
   48         -
    /// Add a new key and value pair to this writer.
   49         -
    pub fn push_kv(&mut self, k: &str, v: &str) {
   50         -
        self.out.push(self.prefix);
   51         -
        self.out.push_str(k);
   52         -
        self.out.push('=');
   53         -
        self.out.push_str(v);
   54         -
        self.prefix = '&';
   55         -
    }
   56         -
   57         -
    /// Add a new value (which is its own key) to this writer.
   58         -
    pub fn push_v(&mut self, v: &str) {
   59         -
        self.out.push(self.prefix);
   60         -
        self.out.push_str(v);
   61         -
        self.prefix = '&';
   62         -
    }
   63         -
}
   64         -
   65         -
#[cfg(test)]
   66         -
mod test {
   67         -
    use crate::query::{fmt_string, Writer};
   68         -
    use http_02x::Uri;
   69         -
    use proptest::proptest;
   70         -
   71         -
    #[test]
   72         -
    fn url_encode() {
   73         -
        assert_eq!(fmt_string("y̆").as_str(), "y%CC%86");
   74         -
        assert_eq!(fmt_string(" ").as_str(), "%20");
   75         -
        assert_eq!(fmt_string("foo/baz%20").as_str(), "foo%2Fbaz%2520");
   76         -
        assert_eq!(fmt_string("&=").as_str(), "%26%3D");
   77         -
        assert_eq!(fmt_string("🐱").as_str(), "%F0%9F%90%B1");
   78         -
        // `:` needs to be encoded, but only for AWS services
   79         -
        assert_eq!(fmt_string("a:b"), "a%3Ab")
   80         -
    }
   81         -
   82         -
    #[test]
   83         -
    fn writer_sets_prefix_properly() {
   84         -
        let mut out = String::new();
   85         -
        let mut writer = Writer::new(&mut out);
   86         -
        writer.push_v("a");
   87         -
        writer.push_kv("b", "c");
   88         -
        assert_eq!(out, "?a&b=c");
   89         -
    }
   90         -
   91         -
    proptest! {
   92         -
        #[test]
   93         -
        fn test_encode_request(s: String) {
   94         -
            let _: Uri = format!("http://host.example.com/?{}", fmt_string(s)).parse().expect("all strings should be encoded properly");
   95         -
        }
   96         -
    }
   97         -
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/query_writer.rs

@@ -1,0 +177,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
    6         -
use crate::query::fmt_string as percent_encode_query;
    7         -
use http_02x::uri::InvalidUri;
    8         -
use http_02x::Uri;
    9         -
   10         -
/// Utility for updating the query string in a [`Uri`].
   11         -
#[allow(missing_debug_implementations)]
   12         -
pub struct QueryWriter {
   13         -
    base_uri: Uri,
   14         -
    new_path_and_query: String,
   15         -
    prefix: Option<char>,
   16         -
}
   17         -
   18         -
impl QueryWriter {
   19         -
    /// Creates a new `QueryWriter` from a string
   20         -
    pub fn new_from_string(uri: &str) -> Result<Self, InvalidUri> {
   21         -
        Ok(Self::new(&Uri::try_from(uri)?))
   22         -
    }
   23         -
   24         -
    /// Creates a new `QueryWriter` based off the given `uri`.
   25         -
    pub fn new(uri: &Uri) -> Self {
   26         -
        let new_path_and_query = uri
   27         -
            .path_and_query()
   28         -
            .map(|pq| pq.to_string())
   29         -
            .unwrap_or_default();
   30         -
        let prefix = if uri.query().is_none() {
   31         -
            Some('?')
   32         -
        } else if !uri.query().unwrap_or_default().is_empty() {
   33         -
            Some('&')
   34         -
        } else {
   35         -
            None
   36         -
        };
   37         -
        QueryWriter {
   38         -
            base_uri: uri.clone(),
   39         -
            new_path_and_query,
   40         -
            prefix,
   41         -
        }
   42         -
    }
   43         -
   44         -
    /// Clears all query parameters.
   45         -
    pub fn clear_params(&mut self) {
   46         -
        if let Some(index) = self.new_path_and_query.find('?') {
   47         -
            self.new_path_and_query.truncate(index);
   48         -
            self.prefix = Some('?');
   49         -
        }
   50         -
    }
   51         -
   52         -
    /// Inserts a new query parameter. The key and value are percent encoded
   53         -
    /// by `QueryWriter`. Passing in percent encoded values will result in double encoding.
   54         -
    pub fn insert(&mut self, k: &str, v: &str) {
   55         -
        self.insert_encoded(&percent_encode_query(k), &percent_encode_query(v));
   56         -
    }
   57         -
   58         -
    /// Inserts a new already encoded query parameter. The key and value will be inserted
   59         -
    /// as is.
   60         -
    pub fn insert_encoded(&mut self, encoded_k: &str, encoded_v: &str) {
   61         -
        if let Some(prefix) = self.prefix {
   62         -
            self.new_path_and_query.push(prefix);
   63         -
        }
   64         -
        self.prefix = Some('&');
   65         -
        self.new_path_and_query.push_str(encoded_k);
   66         -
        self.new_path_and_query.push('=');
   67         -
        self.new_path_and_query.push_str(encoded_v)
   68         -
    }
   69         -
   70         -
    /// Returns just the built query string.
   71         -
    pub fn build_query(self) -> String {
   72         -
        self.build_uri().query().unwrap_or_default().to_string()
   73         -
    }
   74         -
   75         -
    /// Returns a full [`Uri`] with the query string updated.
   76         -
    pub fn build_uri(self) -> Uri {
   77         -
        let mut parts = self.base_uri.into_parts();
   78         -
        parts.path_and_query = Some(
   79         -
            self.new_path_and_query
   80         -
                .parse()
   81         -
                .expect("adding query should not invalidate URI"),
   82         -
        );
   83         -
        Uri::from_parts(parts).expect("a valid URL in should always produce a valid URL out")
   84         -
    }
   85         -
}
   86         -
   87         -
#[cfg(test)]
   88         -
mod test {
   89         -
    use super::QueryWriter;
   90         -
    use http_02x::Uri;
   91         -
   92         -
    #[test]
   93         -
    fn empty_uri() {
   94         -
        let uri = Uri::from_static("http://www.example.com");
   95         -
        let mut query_writer = QueryWriter::new(&uri);
   96         -
        query_writer.insert("key", "val%ue");
   97         -
        query_writer.insert("another", "value");
   98         -
        assert_eq!(
   99         -
            query_writer.build_uri(),
  100         -
            Uri::from_static("http://www.example.com?key=val%25ue&another=value")
  101         -
        );
  102         -
    }
  103         -
  104         -
    #[test]
  105         -
    fn uri_with_path() {
  106         -
        let uri = Uri::from_static("http://www.example.com/path");
  107         -
        let mut query_writer = QueryWriter::new(&uri);
  108         -
        query_writer.insert("key", "val%ue");
  109         -
        query_writer.insert("another", "value");
  110         -
        assert_eq!(
  111         -
            query_writer.build_uri(),
  112         -
            Uri::from_static("http://www.example.com/path?key=val%25ue&another=value")
  113         -
        );
  114         -
    }
  115         -
  116         -
    #[test]
  117         -
    fn uri_with_path_and_query() {
  118         -
        let uri = Uri::from_static("http://www.example.com/path?original=here");
  119         -
        let mut query_writer = QueryWriter::new(&uri);
  120         -
        query_writer.insert("key", "val%ue");
  121         -
        query_writer.insert("another", "value");
  122         -
        assert_eq!(
  123         -
            query_writer.build_uri(),
  124         -
            Uri::from_static(
  125         -
                "http://www.example.com/path?original=here&key=val%25ue&another=value"
  126         -
            )
  127         -
        );
  128         -
    }
  129         -
  130         -
    #[test]
  131         -
    fn build_query() {
  132         -
        let uri = Uri::from_static("http://www.example.com");
  133         -
        let mut query_writer = QueryWriter::new(&uri);
  134         -
        query_writer.insert("key", "val%ue");
  135         -
        query_writer.insert("ano%ther", "value");
  136         -
        assert_eq!("key=val%25ue&ano%25ther=value", query_writer.build_query());
  137         -
    }
  138         -
  139         -
    #[test]
  140         -
    // This test ensures that the percent encoding applied to queries always produces a valid URI if
  141         -
    // the starting URI is valid
  142         -
    fn doesnt_panic_when_adding_query_to_valid_uri() {
  143         -
        let uri = Uri::from_static("http://www.example.com");
  144         -
  145         -
        let mut problematic_chars = Vec::new();
  146         -
  147         -
        for byte in u8::MIN..=u8::MAX {
  148         -
            match std::str::from_utf8(&[byte]) {
  149         -
                // If we can't make a str from the byte then we certainly can't make a URL from it
  150         -
                Err(_) => {
  151         -
                    continue;
  152         -
                }
  153         -
                Ok(value) => {
  154         -
                    let mut query_writer = QueryWriter::new(&uri);
  155         -
                    query_writer.insert("key", value);
  156         -
  157         -
                    if std::panic::catch_unwind(|| query_writer.build_uri()).is_err() {
  158         -
                        problematic_chars.push(char::from(byte));
  159         -
                    };
  160         -
                }
  161         -
            }
  162         -
        }
  163         -
  164         -
        if !problematic_chars.is_empty() {
  165         -
            panic!("we got some bad bytes here: {problematic_chars:#?}")
  166         -
        }
  167         -
    }
  168         -
  169         -
    #[test]
  170         -
    fn clear_params() {
  171         -
        let uri = Uri::from_static("http://www.example.com/path?original=here&foo=1");
  172         -
        let mut query_writer = QueryWriter::new(&uri);
  173         -
        query_writer.clear_params();
  174         -
        query_writer.insert("new", "value");
  175         -
        assert_eq!("new=value", query_writer.build_query());
  176         -
    }
  177         -
}