nethsm/
connection.rs

1//! Components for NetHSM connection handling.
2
3use std::{fmt::Display, str::FromStr};
4
5use serde::{Deserialize, Serialize};
6
7use crate::ConnectionSecurity;
8#[cfg(doc)]
9use crate::NetHsm;
10
11/// An error that may occur when working with NetHSM connections.
12#[derive(Debug, thiserror::Error)]
13pub enum Error {
14    /// The format of a URL is invalid.
15    ///
16    /// A [`url::Url`] could be created, but one of the additional constraints imposed by [`Url`]
17    /// can not be met.
18    #[error("The format of URL {url} is invalid because {context}")]
19    UrlInvalidFormat {
20        /// The [`url::Url`] for which one of the [`Url`] constraints can not be met.
21        url: url::Url,
22
23        /// The context in which the error occurred.
24        ///
25        /// This is meant to complete the sentence "The format of URL {url} is invalid because ".
26        context: &'static str,
27    },
28
29    /// A URL can not be parsed.
30    #[error("URL parser error:\n{0}")]
31    UrlParse(#[from] url::ParseError),
32}
33
34/// The connection to a NetHSM device.
35///
36/// Contains the [`Url`] and [`ConnectionSecurity`] for a [`NetHsm`] device.
37#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
38pub struct Connection {
39    pub(crate) url: Url,
40    pub(crate) tls_security: ConnectionSecurity,
41}
42
43impl Connection {
44    /// Creates a new [`Connection`]
45    pub fn new(url: Url, tls_security: ConnectionSecurity) -> Self {
46        Self { url, tls_security }
47    }
48
49    /// Returns a reference to the contained [`Url`].
50    pub fn url(&self) -> &Url {
51        &self.url
52    }
53
54    /// Returns a reference to the contained [`ConnectionSecurity`].
55    pub fn tls_security(&self) -> &ConnectionSecurity {
56        &self.tls_security
57    }
58}
59
60/// The URL used for connecting to a NetHSM instance.
61///
62/// Wraps [`url::Url`] but offers stricter constraints.
63/// The URL
64///
65/// * must use https
66/// * must have a host
67/// * must not contain a password, user or query
68#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
69#[serde(try_from = "String")]
70pub struct Url(url::Url);
71
72impl Url {
73    /// Creates a new Url.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use nethsm::Url;
79    ///
80    /// # fn main() -> testresult::TestResult {
81    /// Url::new("https://example.org/api/v1")?;
82    /// Url::new("https://127.0.0.1:8443/api/v1")?;
83    ///
84    /// // errors when not using https
85    /// assert!(Url::new("http://example.org/api/v1").is_err());
86    ///
87    /// // errors when using query, user or password
88    /// assert!(Url::new("https://example.org/api/v1?something").is_err());
89    /// # Ok(())
90    /// # }
91    /// ```
92    ///
93    /// # Errors
94    ///
95    /// Returns an error if
96    /// * https is not used
97    /// * a host is not defined
98    /// * the URL contains a password, user or query
99    pub fn new(url: &str) -> Result<Self, crate::Error> {
100        let url = url::Url::parse(url).map_err(Error::UrlParse)?;
101        if !url.scheme().eq("https") {
102            Err(Error::UrlInvalidFormat {
103                url,
104                context: "a URL must use TLS",
105            }
106            .into())
107        } else if !url.has_host() {
108            Err(Error::UrlInvalidFormat {
109                url,
110                context: "a URL must have a host component",
111            }
112            .into())
113        } else if url.password().is_some() {
114            Err(Error::UrlInvalidFormat {
115                url,
116                context: "a URL must not have a password component",
117            }
118            .into())
119        } else if !url.username().is_empty() {
120            Err(Error::UrlInvalidFormat {
121                url,
122                context: "a URL must not have a user component",
123            }
124            .into())
125        } else if url.query().is_some() {
126            Err(Error::UrlInvalidFormat {
127                url,
128                context: "a URL must not have a query component",
129            }
130            .into())
131        } else {
132            Ok(Self(url))
133        }
134    }
135}
136
137impl Display for Url {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{}", self.0)
140    }
141}
142
143impl TryFrom<&str> for Url {
144    type Error = crate::Error;
145
146    fn try_from(value: &str) -> Result<Self, crate::Error> {
147        Self::new(value)
148    }
149}
150
151impl TryFrom<String> for Url {
152    type Error = crate::Error;
153
154    fn try_from(value: String) -> Result<Self, crate::Error> {
155        Self::new(&value)
156    }
157}
158
159impl FromStr for Url {
160    type Err = crate::Error;
161
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        Self::new(s)
164    }
165}