nethsm/
nethsm_sdk.rs

1use std::fmt::Display;
2
3use log::Level;
4use nethsm_sdk_rs::models::{Switch, UnattendedBootConfig};
5use serde::{Deserialize, Serialize};
6use ureq::Response;
7
8/// A representation of a message body in an HTTP response
9///
10/// This type allows us to deserialize the message body when the NetHSM API triggers the return of a
11/// [`nethsm_sdk_rs::apis::Error::Ureq`].
12#[derive(Debug, Deserialize)]
13pub struct Message {
14    message: String,
15}
16
17impl From<Response> for Message {
18    fn from(value: Response) -> Self {
19        if let Ok(message) = value.into_json() {
20            message
21        } else {
22            Message {
23                message: "Deserialization error (no message in body)".to_string(),
24            }
25        }
26    }
27}
28
29impl Display for Message {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.write_str(&self.message)
32    }
33}
34
35#[derive(Debug)]
36pub struct ApiErrorMessage {
37    pub status_code: u16,
38    pub message: Message,
39}
40
41impl From<(u16, Message)> for ApiErrorMessage {
42    fn from(value: (u16, Message)) -> Self {
43        Self {
44            status_code: value.0,
45            message: value.1,
46        }
47    }
48}
49
50impl Display for ApiErrorMessage {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.write_str(&format!(
53            "{} (status code {})",
54            self.message, self.status_code
55        ))
56    }
57}
58
59/// A helper Error for more readable output for [`nethsm_sdk_rs::apis::Error`]
60///
61/// This type allows us to create more readable output for [`nethsm_sdk_rs::apis::Error::Ureq`] and
62/// reuse the upstream handling otherwise.
63pub struct NetHsmApiError<T> {
64    error: Option<nethsm_sdk_rs::apis::Error<T>>,
65    message: Option<String>,
66}
67
68impl<T> From<nethsm_sdk_rs::apis::Error<T>> for NetHsmApiError<T> {
69    fn from(value: nethsm_sdk_rs::apis::Error<T>) -> Self {
70        match value {
71            nethsm_sdk_rs::apis::Error::Ureq(error) => match error {
72                nethsm_sdk_rs::ureq::Error::Status(code, response) => Self {
73                    error: None,
74                    message: Some(ApiErrorMessage::from((code, response.into())).to_string()),
75                },
76                nethsm_sdk_rs::ureq::Error::Transport(transport) => Self {
77                    error: None,
78                    message: Some(format!("{transport}")),
79                },
80            },
81            nethsm_sdk_rs::apis::Error::ResponseError(resp) => Self {
82                error: None,
83                message: Some(format!(
84                    "Status code: {}: {}",
85                    resp.status,
86                    // First, try to deserialize the response as a `Message` object,
87                    // which is commonly returned by a majority of failures
88                    serde_json::from_slice::<Message>(&resp.content)
89                        .map(|m| m.message)
90                        // if that fails, as a last resort, try to return the response verbatim.
91                        .unwrap_or_else(|_| String::from_utf8_lossy(&resp.content).into())
92                )),
93            },
94            _ => Self {
95                error: Some(value),
96                message: None,
97            },
98        }
99    }
100}
101
102impl<T> Display for NetHsmApiError<T> {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        if let Some(message) = self.message.as_ref() {
105            write!(f, "{message}")?;
106        } else if let Some(error) = self.error.as_ref() {
107            write!(f, "{error}")?;
108        }
109        Ok(())
110    }
111}
112
113/// The NetHSM boot mode
114///
115/// Defines in which state the NetHSM is in during boot after provisioning (see
116/// [`crate::NetHsm::provision`]) and whether an unlock passphrase has to be provided for it to be
117/// of state [`crate::SystemState::Operational`].
118#[derive(
119    Clone,
120    Copy,
121    Debug,
122    strum::Display,
123    strum::EnumString,
124    strum::EnumIter,
125    strum::IntoStaticStr,
126    Eq,
127    PartialEq,
128)]
129#[strum(ascii_case_insensitive)]
130pub enum BootMode {
131    /// The device boots into state [`crate::SystemState::Locked`] and an unlock passphrase has to
132    /// be provided
133    Attended,
134    /// The device boots into state [`crate::SystemState::Operational`] and no unlock passphrase
135    /// has to be provided
136    Unattended,
137}
138
139impl From<UnattendedBootConfig> for BootMode {
140    fn from(value: UnattendedBootConfig) -> Self {
141        match value.status {
142            Switch::On => BootMode::Unattended,
143            Switch::Off => BootMode::Attended,
144        }
145    }
146}
147
148impl From<BootMode> for UnattendedBootConfig {
149    fn from(value: BootMode) -> Self {
150        match value {
151            BootMode::Unattended => UnattendedBootConfig { status: Switch::On },
152            BootMode::Attended => UnattendedBootConfig {
153                status: Switch::Off,
154            },
155        }
156    }
157}
158
159/// A device log level
160#[derive(
161    Clone,
162    Copy,
163    Debug,
164    Default,
165    Deserialize,
166    strum::Display,
167    strum::EnumString,
168    strum::EnumIter,
169    strum::IntoStaticStr,
170    Eq,
171    Hash,
172    Ord,
173    PartialEq,
174    PartialOrd,
175    Serialize,
176)]
177#[strum(ascii_case_insensitive)]
178pub enum LogLevel {
179    /// Show debug, error, warning and info messages
180    Debug,
181
182    /// Show error, warning and info messages
183    Error,
184
185    /// Show info messages
186    #[default]
187    Info,
188
189    /// Show warning and info messages
190    Warning,
191}
192
193impl From<LogLevel> for nethsm_sdk_rs::models::LogLevel {
194    fn from(value: LogLevel) -> Self {
195        match value {
196            LogLevel::Debug => Self::Debug,
197            LogLevel::Error => Self::Error,
198            LogLevel::Info => Self::Info,
199            LogLevel::Warning => Self::Warning,
200        }
201    }
202}
203
204impl From<Level> for LogLevel {
205    /// Creates a new [`LogLevel`] from a [`Level`].
206    ///
207    /// # Note
208    ///
209    /// Creates a [`LogLevel::Debug`] from a [`Level::Trace`], as there is no equivalent level.
210    fn from(value: Level) -> Self {
211        match value {
212            Level::Trace => Self::Debug,
213            Level::Debug => Self::Debug,
214            Level::Error => Self::Error,
215            Level::Info => Self::Info,
216            Level::Warn => Self::Warning,
217        }
218    }
219}
220
221/// The algorithm type of a key used for TLS
222#[derive(
223    Clone,
224    Copy,
225    Debug,
226    Default,
227    Deserialize,
228    strum::Display,
229    strum::EnumString,
230    strum::EnumIter,
231    strum::IntoStaticStr,
232    Eq,
233    Hash,
234    Ord,
235    PartialEq,
236    PartialOrd,
237    Serialize,
238)]
239#[strum(ascii_case_insensitive)]
240pub enum TlsKeyType {
241    /// A Montgomery curve key over a prime field for the prime number 2^255-19
242    Curve25519,
243
244    /// An elliptic-curve key over a prime field for a prime of size 224 bit
245    EcP224,
246
247    /// An elliptic-curve key over a prime field for a prime of size 256 bit
248    EcP256,
249
250    /// An elliptic-curve key over a prime field for a prime of size 384 bit
251    EcP384,
252
253    /// An elliptic-curve key over a prime field for a prime of size 521 bit
254    EcP521,
255
256    /// An RSA key
257    #[default]
258    Rsa,
259}
260
261impl From<TlsKeyType> for nethsm_sdk_rs::models::TlsKeyType {
262    fn from(value: TlsKeyType) -> Self {
263        match value {
264            TlsKeyType::Curve25519 => Self::Curve25519,
265            TlsKeyType::EcP224 => Self::EcP224,
266            TlsKeyType::EcP256 => Self::EcP256,
267            TlsKeyType::EcP384 => Self::EcP384,
268            TlsKeyType::EcP521 => Self::EcP521,
269            TlsKeyType::Rsa => Self::Rsa,
270        }
271    }
272}
273
274/// The role of a user on a NetHSM device
275#[derive(
276    Clone,
277    Copy,
278    Debug,
279    Default,
280    Deserialize,
281    strum::Display,
282    strum::EnumString,
283    strum::EnumIter,
284    strum::IntoStaticStr,
285    Eq,
286    PartialEq,
287    Ord,
288    PartialOrd,
289    Hash,
290    Serialize,
291)]
292#[strum(ascii_case_insensitive)]
293pub enum UserRole {
294    /// A role for administrating a device, its users and keys
295    Administrator,
296    /// A role for creating backups of a device
297    Backup,
298    /// A role for reading metrics of a device
299    Metrics,
300    /// A role for using one or more keys of a device
301    #[default]
302    Operator,
303}
304
305impl From<UserRole> for nethsm_sdk_rs::models::UserRole {
306    fn from(value: UserRole) -> Self {
307        match value {
308            UserRole::Administrator => Self::Administrator,
309            UserRole::Backup => Self::Backup,
310            UserRole::Metrics => Self::Metrics,
311            UserRole::Operator => Self::Operator,
312        }
313    }
314}
315
316impl From<nethsm_sdk_rs::models::UserRole> for UserRole {
317    fn from(value: nethsm_sdk_rs::models::UserRole) -> Self {
318        match value {
319            nethsm_sdk_rs::models::UserRole::Administrator => Self::Administrator,
320            nethsm_sdk_rs::models::UserRole::Backup => Self::Backup,
321            nethsm_sdk_rs::models::UserRole::Metrics => Self::Metrics,
322            nethsm_sdk_rs::models::UserRole::Operator => Self::Operator,
323        }
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use std::str::FromStr;
330
331    use rstest::rstest;
332    use testresult::TestResult;
333
334    use super::*;
335
336    #[rstest]
337    #[case("rsa", Some(TlsKeyType::Rsa))]
338    #[case("curve25519", Some(TlsKeyType::Curve25519))]
339    #[case("ecp256", Some(TlsKeyType::EcP256))]
340    #[case("ecp384", Some(TlsKeyType::EcP384))]
341    #[case("ecp521", Some(TlsKeyType::EcP521))]
342    #[case("foo", None)]
343    fn tlskeytype_fromstr(#[case] input: &str, #[case] expected: Option<TlsKeyType>) -> TestResult {
344        if let Some(expected) = expected {
345            assert_eq!(TlsKeyType::from_str(input)?, expected);
346        } else {
347            assert!(TlsKeyType::from_str(input).is_err());
348        }
349        Ok(())
350    }
351
352    #[rstest]
353    #[case("administrator", Some(UserRole::Administrator))]
354    #[case("backup", Some(UserRole::Backup))]
355    #[case("metrics", Some(UserRole::Metrics))]
356    #[case("operator", Some(UserRole::Operator))]
357    #[case("foo", None)]
358    fn userrole_fromstr(#[case] input: &str, #[case] expected: Option<UserRole>) -> TestResult {
359        if let Some(expected) = expected {
360            assert_eq!(UserRole::from_str(input)?, expected);
361        } else {
362            assert!(UserRole::from_str(input).is_err());
363        }
364        Ok(())
365    }
366}