// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use http::uri::{self, Uri};

pub trait HttpUriExt {
    /// Normalizes empty paths to `/`, appends `/` to `self`'s path if it does not end with one,
    /// then appends `path`, preserving any query parameters. Does nothing if `path` is the empty
    /// string.
    ///
    /// Note that this will add a path to a `Uri` without one, and `Uri` requires that `Uri`s
    /// with a path and authority have a scheme, so this will fail when called on a `Uri` that
    /// has an authority and no scheme.
    fn extend_dir_with_path(self, path: &str) -> Result<Uri, Error>;
}

impl HttpUriExt for Uri {
    fn extend_dir_with_path(self, path: &str) -> Result<Uri, Error> {
        if path.is_empty() {
            return Ok(self);
        }
        let mut base_parts = self.into_parts();
        let (base_path, query) = match &base_parts.path_and_query {
            Some(path_and_query) => (path_and_query.path(), path_and_query.query()),
            None => ("/", None),
        };
        let new_path_and_query = if base_path.ends_with("/") {
            if let Some(query) = query {
                format!("{}{}?{}", base_path, path, query)
            } else {
                format!("{}{}", base_path, path)
            }
        } else {
            if let Some(query) = query {
                format!("{}/{}?{}", base_path, path, query)
            } else {
                format!("{}/{}", base_path, path)
            }
        };
        base_parts.path_and_query = Some(new_path_and_query.parse()?);
        Ok(Uri::from_parts(base_parts)?)
    }
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("invalid uri: {0}")]
    InvalidUri(#[from] uri::InvalidUri),
    #[error("invalid uri parts: {0}")]
    InvalidUriParts(#[from] uri::InvalidUriParts),
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_uri_from_path_and_query(path_and_query: Option<&str>) -> Uri {
        let mut parts = uri::Parts::default();
        parts.path_and_query = path_and_query.map(|p| p.parse().unwrap());
        Uri::from_parts(parts).unwrap()
    }

    fn assert_expected_path(base: Option<&str>, added: &str, expected: Option<&str>) {
        let uri = make_uri_from_path_and_query(base).extend_dir_with_path(added).unwrap();
        assert_eq!(
            uri.into_parts().path_and_query.map(|p| p.to_string()),
            expected.map(|s| s.to_string())
        );
    }

    #[test]
    fn no_query_empty_argument() {
        assert_expected_path(None, "", None);
        assert_expected_path(Some(""), "", None);
        assert_expected_path(Some("/"), "", Some("/"));
        assert_expected_path(Some("/a"), "", Some("/a"));
        assert_expected_path(Some("/a/"), "", Some("/a/"));
    }

    #[test]
    fn has_query_empty_argument() {
        assert_expected_path(Some("?k=v"), "", Some("/?k=v"));
        assert_expected_path(Some("/?k=v"), "", Some("/?k=v"));
        assert_expected_path(Some("/a?k=v"), "", Some("/a?k=v"));
        assert_expected_path(Some("/a/?k=v"), "", Some("/a/?k=v"));
    }

    #[test]
    fn no_query_has_argument() {
        assert_expected_path(None, "c", Some("/c"));
        assert_expected_path(Some(""), "c", Some("/c"));
        assert_expected_path(Some("/"), "c", Some("/c"));
        assert_expected_path(Some("/a"), "c", Some("/a/c"));
        assert_expected_path(Some("/a/"), "c", Some("/a/c"));
    }

    #[test]
    fn has_query_has_argument() {
        assert_expected_path(Some("?k=v"), "c", Some("/c?k=v"));
        assert_expected_path(Some("/?k=v"), "c", Some("/c?k=v"));
        assert_expected_path(Some("/a?k=v"), "c", Some("/a/c?k=v"));
        assert_expected_path(Some("/a/?k=v"), "c", Some("/a/c?k=v"));
    }
}
