123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- // Copyright (c) 2022-2023 Yuki Kishimoto
- // Distributed under the MIT software license
- //! Url
- use core::fmt;
- use core::str::FromStr;
- use serde::{Deserialize, Serialize};
- use thiserror::Error;
- use url::{ParseError, Url};
- use crate::ensure_cdk;
- /// Url Error
- #[derive(Debug, Error, PartialEq, Eq)]
- pub enum Error {
- /// Url error
- #[error(transparent)]
- Url(#[from] ParseError),
- /// Invalid URL structure
- #[error("Invalid URL")]
- InvalidUrl,
- }
- /// MintUrl Url
- #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
- pub struct MintUrl(String);
- impl MintUrl {
- fn format_url(url: &str) -> Result<String, Error> {
- ensure_cdk!(!url.is_empty(), Error::InvalidUrl);
- let url = url.trim_end_matches('/');
- // https://URL.com/path/TO/resource -> https://url.com/path/TO/resource
- let protocol = url
- .split("://")
- .nth(0)
- .ok_or(Error::InvalidUrl)?
- .to_lowercase();
- let host = url
- .split("://")
- .nth(1)
- .ok_or(Error::InvalidUrl)?
- .split('/')
- .nth(0)
- .ok_or(Error::InvalidUrl)?
- .to_lowercase();
- let path = url
- .split("://")
- .nth(1)
- .ok_or(Error::InvalidUrl)?
- .split('/')
- .skip(1)
- .collect::<Vec<&str>>()
- .join("/");
- let mut formatted_url = format!("{}://{}", protocol, host);
- if !path.is_empty() {
- formatted_url.push_str(&format!("/{}/", path));
- }
- Ok(formatted_url)
- }
- /// Join onto url
- pub fn join(&self, path: &str) -> Result<Url, Error> {
- Url::parse(&self.0)
- .and_then(|url| url.join(path))
- .map_err(Into::into)
- }
- /// Append path elements onto the URL
- pub fn join_paths(&self, path_elements: &[&str]) -> Result<Url, Error> {
- self.join(&path_elements.join("/"))
- }
- }
- impl FromStr for MintUrl {
- type Err = Error;
- fn from_str(url: &str) -> Result<Self, Self::Err> {
- let formatted_url = Self::format_url(url);
- match formatted_url {
- Ok(url) => Ok(Self(url)),
- Err(_) => Err(Error::InvalidUrl),
- }
- }
- }
- impl fmt::Display for MintUrl {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.0)
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- #[test]
- fn test_trim_trailing_slashes() {
- let very_unformatted_url = "http://url-to-check.com////";
- let unformatted_url = "http://url-to-check.com/";
- let formatted_url = "http://url-to-check.com";
- let very_trimmed_url = MintUrl::from_str(very_unformatted_url).unwrap();
- assert_eq!(formatted_url, very_trimmed_url.to_string());
- let trimmed_url = MintUrl::from_str(unformatted_url).unwrap();
- assert_eq!(formatted_url, trimmed_url.to_string());
- let unchanged_url = MintUrl::from_str(formatted_url).unwrap();
- assert_eq!(formatted_url, unchanged_url.to_string());
- }
- #[test]
- fn test_case_insensitive() {
- let wrong_cased_url = "http://URL-to-check.com";
- let correct_cased_url = "http://url-to-check.com";
- let cased_url_formatted = MintUrl::from_str(wrong_cased_url).unwrap();
- assert_eq!(correct_cased_url, cased_url_formatted.to_string());
- let wrong_cased_url_with_path = "http://URL-to-check.com/PATH/to/check";
- let correct_cased_url_with_path = "http://url-to-check.com/PATH/to/check/";
- let cased_url_with_path_formatted = MintUrl::from_str(wrong_cased_url_with_path).unwrap();
- assert_eq!(
- correct_cased_url_with_path,
- cased_url_with_path_formatted.to_string()
- );
- }
- #[test]
- fn test_join_paths() {
- let url_no_path = "http://url-to-check.com";
- let url = MintUrl::from_str(url_no_path).unwrap();
- assert_eq!(
- format!("{url_no_path}/hello/world"),
- url.join_paths(&["hello", "world"]).unwrap().to_string()
- );
- let url_no_path_with_slash = "http://url-to-check.com/";
- let url = MintUrl::from_str(url_no_path_with_slash).unwrap();
- assert_eq!(
- format!("{url_no_path_with_slash}hello/world"),
- url.join_paths(&["hello", "world"]).unwrap().to_string()
- );
- let url_with_path = "http://url-to-check.com/my/path";
- let url = MintUrl::from_str(url_with_path).unwrap();
- assert_eq!(
- format!("{url_with_path}/hello/world"),
- url.join_paths(&["hello", "world"]).unwrap().to_string()
- );
- let url_with_path_with_slash = "http://url-to-check.com/my/path/";
- let url = MintUrl::from_str(url_with_path_with_slash).unwrap();
- assert_eq!(
- format!("{url_with_path_with_slash}hello/world"),
- url.join_paths(&["hello", "world"]).unwrap().to_string()
- );
- }
- }
|