1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Copyright (c) 2022-2023 Yuki Kishimoto
// Distributed under the MIT software license

//! Url

use core::fmt;
use core::str::FromStr;

use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
use url::{ParseError, Url};

/// Url Error
#[derive(Debug, Error, PartialEq, Eq)]
pub enum Error {
    /// Url error
    #[error(transparent)]
    Url(#[from] ParseError),
}

/// MintUrl Url
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct MintUrl(String);

impl MintUrl {
    /// New mint url
    pub fn new<S>(url: S) -> Self
    where
        S: Into<String>,
    {
        let url: String = url.into();
        Self(url.trim_end_matches('/').to_string())
    }

    /// Empty mint url
    pub fn empty() -> Self {
        Self(String::new())
    }

    /// Join onto url
    pub fn join(&self, path: &str) -> Result<Url, Error> {
        let url: Url = self.try_into()?;
        Ok(url.join(path)?)
    }

    /// Remove trailing slashes from url
    pub fn trim_trailing_slashes(&self) -> Self {
        Self(self.to_string().trim_end_matches('/').to_string())
    }
}

impl<'de> Deserialize<'de> for MintUrl {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        MintUrl::from_str(&s).map_err(serde::de::Error::custom)
    }
}

impl<S> From<S> for MintUrl
where
    S: Into<String>,
{
    fn from(url: S) -> Self {
        let url: String = url.into();
        Self(url.trim_end_matches('/').to_string())
    }
}

impl FromStr for MintUrl {
    type Err = Error;

    fn from_str(url: &str) -> Result<Self, Self::Err> {
        Ok(Self::from(url))
    }
}

impl TryFrom<MintUrl> for Url {
    type Error = Error;

    fn try_from(mint_url: MintUrl) -> Result<Url, Self::Error> {
        Ok(Self::parse(&mint_url.0)?)
    }
}

impl TryFrom<&MintUrl> for Url {
    type Error = Error;

    fn try_from(mint_url: &MintUrl) -> Result<Url, Self::Error> {
        Ok(Self::parse(mint_url.0.as_str())?)
    }
}

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!("http://url-to-check.com", very_trimmed_url.to_string());

        let trimmed_url = MintUrl::from_str(unformatted_url).unwrap();
        assert_eq!("http://url-to-check.com", trimmed_url.to_string());

        let unchanged_url = MintUrl::from_str(formatted_url).unwrap();
        assert_eq!("http://url-to-check.com", unchanged_url.to_string());
    }
}