1use std::{fmt::Display, sync::LazyLock};
17
18use regex::Regex;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21use utoipa::ToSchema;
22
23pub trait Id {
25 fn from_usize(val: usize) -> Self;
27 fn as_usize(&self) -> usize;
29}
30
31#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, ToSchema, Serialize)]
33#[serde(transparent)]
34pub struct Hostname(String);
35
36#[derive(Debug, Error)]
38pub enum HostnameError {
39 #[error("Hostname is empty")]
41 Empty,
42 #[error("Hostname is too long: {0} characters (maximum is 253)")]
44 TooLong(usize),
45 #[error("Invalid hostname: {0}")]
47 Invalid(String),
48}
49
50pub const HOSTNAME_REGEX: &str = r"^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]))*$";
52
53impl Hostname {
54 pub fn new(hostname: String) -> Result<Self, HostnameError> {
60 Self::validate(&hostname)?;
61 Ok(Self(hostname))
62 }
63
64 fn validate(hostname: &str) -> Result<(), HostnameError> {
65 if hostname.len() > 253 {
66 return Err(HostnameError::TooLong(hostname.len()));
67 }
68
69 static RE: LazyLock<Regex> =
70 LazyLock::new(|| Regex::new(HOSTNAME_REGEX).expect("valid regex"));
71 if !RE.is_match(hostname) {
72 return Err(HostnameError::Invalid(hostname.to_string()));
73 }
74
75 Ok(())
76 }
77}
78
79impl Display for Hostname {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "{}", self.0)
82 }
83}
84
85impl From<Hostname> for String {
86 fn from(value: Hostname) -> Self {
87 value.0
88 }
89}
90
91impl TryFrom<String> for Hostname {
92 type Error = HostnameError;
93
94 fn try_from(value: String) -> Result<Self, Self::Error> {
95 Self::new(value)
96 }
97}
98
99impl TryFrom<&str> for Hostname {
100 type Error = HostnameError;
101
102 fn try_from(value: &str) -> Result<Self, Self::Error> {
103 Self::new(value.to_string())
104 }
105}
106
107impl<'de> Deserialize<'de> for Hostname {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: serde::Deserializer<'de>,
112 {
113 let s = String::deserialize(deserializer)?;
114 Hostname::new(s).map_err(serde::de::Error::custom)
115 }
116}