Skip to main content

made_core/value_objects/delivery/
host_kind.rs

1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use crate::error::DomainError;
6
7const MAX_LEN: usize = 64;
8
9/// Which sort of host a destination is, as the operator configured it.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
11#[serde(transparent)]
12pub struct HostKind(String);
13
14impl HostKind {
15    /// Construct a validated value.
16    pub fn new(raw: impl Into<String>) -> Result<Self, DomainError> {
17        let raw = raw.into();
18        let value = raw.trim();
19        if value.is_empty() {
20            return Err(DomainError::EmptyField { field: "host_kind" });
21        }
22        if value.len() > MAX_LEN {
23            return Err(DomainError::FieldTooLong {
24                field: "host_kind",
25                actual: value.len(),
26                max: MAX_LEN,
27            });
28        }
29        if value
30            .chars()
31            .any(|character| character.is_control() && character != '\n' && character != '\t')
32        {
33            return Err(DomainError::InvalidCharacters { field: "host_kind" });
34        }
35        Ok(Self(value.to_owned()))
36    }
37
38    #[must_use]
39    pub fn as_str(&self) -> &str {
40        &self.0
41    }
42}
43
44impl fmt::Display for HostKind {
45    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46        formatter.write_str(&self.0)
47    }
48}
49
50// Deserialisation goes through the constructor: a stored or host-supplied
51// value must satisfy the same invariants as one built in process.
52impl<'de> Deserialize<'de> for HostKind {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: Deserializer<'de>,
56    {
57        Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn empty_and_oversized_values_are_refused() {
67        assert!(HostKind::new("  ").is_err());
68        assert!(HostKind::new("x".repeat(MAX_LEN + 1)).is_err());
69        assert_eq!(HostKind::new("  value  ").unwrap().as_str(), "value");
70    }
71
72    #[test]
73    fn serde_reuses_constructor_validation() {
74        assert!(serde_json::from_str::<HostKind>("\"\"").is_err());
75        assert_eq!(
76            serde_json::from_str::<HostKind>("\"value\"")
77                .unwrap()
78                .as_str(),
79            "value"
80        );
81    }
82}