Skip to main content

yaml_schema/schemas/
format.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// Represents a JSON Schema `format` value for string validation.
5///
6/// Known formats are validated; unknown formats are annotation-only
7/// (they always pass validation).
8#[derive(Clone, PartialEq, Eq)]
9pub enum StringFormat {
10    DateTime,
11    Date,
12    Time,
13    Duration,
14    Email,
15    IdnEmail,
16    Hostname,
17    IdnHostname,
18    Ipv4,
19    Ipv6,
20    Uri,
21    UriReference,
22    Iri,
23    IriReference,
24    Uuid,
25    UriTemplate,
26    JsonPointer,
27    RelativeJsonPointer,
28    Regex,
29    Unknown(String),
30}
31
32impl FromStr for StringFormat {
33    type Err = std::convert::Infallible;
34
35    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
36        Ok(match s {
37            "date-time" => StringFormat::DateTime,
38            "date" => StringFormat::Date,
39            "time" => StringFormat::Time,
40            "duration" => StringFormat::Duration,
41            "email" => StringFormat::Email,
42            "idn-email" => StringFormat::IdnEmail,
43            "hostname" => StringFormat::Hostname,
44            "idn-hostname" => StringFormat::IdnHostname,
45            "ipv4" => StringFormat::Ipv4,
46            "ipv6" => StringFormat::Ipv6,
47            "uri" => StringFormat::Uri,
48            "uri-reference" => StringFormat::UriReference,
49            "iri" => StringFormat::Iri,
50            "iri-reference" => StringFormat::IriReference,
51            "uuid" => StringFormat::Uuid,
52            "uri-template" => StringFormat::UriTemplate,
53            "json-pointer" => StringFormat::JsonPointer,
54            "relative-json-pointer" => StringFormat::RelativeJsonPointer,
55            "regex" => StringFormat::Regex,
56            other => StringFormat::Unknown(other.to_string()),
57        })
58    }
59}
60
61impl fmt::Display for StringFormat {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            StringFormat::DateTime => write!(f, "date-time"),
65            StringFormat::Date => write!(f, "date"),
66            StringFormat::Time => write!(f, "time"),
67            StringFormat::Duration => write!(f, "duration"),
68            StringFormat::Email => write!(f, "email"),
69            StringFormat::IdnEmail => write!(f, "idn-email"),
70            StringFormat::Hostname => write!(f, "hostname"),
71            StringFormat::IdnHostname => write!(f, "idn-hostname"),
72            StringFormat::Ipv4 => write!(f, "ipv4"),
73            StringFormat::Ipv6 => write!(f, "ipv6"),
74            StringFormat::Uri => write!(f, "uri"),
75            StringFormat::UriReference => write!(f, "uri-reference"),
76            StringFormat::Iri => write!(f, "iri"),
77            StringFormat::IriReference => write!(f, "iri-reference"),
78            StringFormat::Uuid => write!(f, "uuid"),
79            StringFormat::UriTemplate => write!(f, "uri-template"),
80            StringFormat::JsonPointer => write!(f, "json-pointer"),
81            StringFormat::RelativeJsonPointer => write!(f, "relative-json-pointer"),
82            StringFormat::Regex => write!(f, "regex"),
83            StringFormat::Unknown(s) => write!(f, "{s}"),
84        }
85    }
86}
87
88impl fmt::Debug for StringFormat {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        match self {
91            StringFormat::Unknown(s) => write!(f, "StringFormat::Unknown({s:?})"),
92            _ => write!(f, "StringFormat::{}", capitalize_variant(self)),
93        }
94    }
95}
96
97fn capitalize_variant(format: &StringFormat) -> &'static str {
98    match format {
99        StringFormat::DateTime => "DateTime",
100        StringFormat::Date => "Date",
101        StringFormat::Time => "Time",
102        StringFormat::Duration => "Duration",
103        StringFormat::Email => "Email",
104        StringFormat::IdnEmail => "IdnEmail",
105        StringFormat::Hostname => "Hostname",
106        StringFormat::IdnHostname => "IdnHostname",
107        StringFormat::Ipv4 => "Ipv4",
108        StringFormat::Ipv6 => "Ipv6",
109        StringFormat::Uri => "Uri",
110        StringFormat::UriReference => "UriReference",
111        StringFormat::Iri => "Iri",
112        StringFormat::IriReference => "IriReference",
113        StringFormat::Uuid => "Uuid",
114        StringFormat::UriTemplate => "UriTemplate",
115        StringFormat::JsonPointer => "JsonPointer",
116        StringFormat::RelativeJsonPointer => "RelativeJsonPointer",
117        StringFormat::Regex => "Regex",
118        StringFormat::Unknown(_) => unreachable!(),
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_from_str_known_formats() {
128        let cases = [
129            ("date-time", StringFormat::DateTime),
130            ("date", StringFormat::Date),
131            ("time", StringFormat::Time),
132            ("duration", StringFormat::Duration),
133            ("email", StringFormat::Email),
134            ("idn-email", StringFormat::IdnEmail),
135            ("hostname", StringFormat::Hostname),
136            ("idn-hostname", StringFormat::IdnHostname),
137            ("ipv4", StringFormat::Ipv4),
138            ("ipv6", StringFormat::Ipv6),
139            ("uri", StringFormat::Uri),
140            ("uri-reference", StringFormat::UriReference),
141            ("iri", StringFormat::Iri),
142            ("iri-reference", StringFormat::IriReference),
143            ("uuid", StringFormat::Uuid),
144            ("uri-template", StringFormat::UriTemplate),
145            ("json-pointer", StringFormat::JsonPointer),
146            ("relative-json-pointer", StringFormat::RelativeJsonPointer),
147            ("regex", StringFormat::Regex),
148        ];
149        for (input, expected) in cases {
150            let parsed: StringFormat = input.parse().unwrap();
151            assert_eq!(parsed, expected, "Failed for input: {input}");
152        }
153    }
154
155    #[test]
156    fn test_from_str_unknown_format() {
157        let parsed: StringFormat = "my-custom-format".parse().unwrap();
158        assert_eq!(
159            parsed,
160            StringFormat::Unknown("my-custom-format".to_string())
161        );
162    }
163
164    #[test]
165    fn test_display_roundtrip() {
166        let cases = [
167            "date-time",
168            "date",
169            "time",
170            "duration",
171            "email",
172            "idn-email",
173            "hostname",
174            "idn-hostname",
175            "ipv4",
176            "ipv6",
177            "uri",
178            "uri-reference",
179            "iri",
180            "iri-reference",
181            "uuid",
182            "uri-template",
183            "json-pointer",
184            "relative-json-pointer",
185            "regex",
186        ];
187        for input in cases {
188            let parsed: StringFormat = input.parse().unwrap();
189            assert_eq!(parsed.to_string(), input);
190        }
191    }
192
193    #[test]
194    fn test_debug_format() {
195        assert_eq!(
196            format!("{:?}", StringFormat::DateTime),
197            "StringFormat::DateTime"
198        );
199        assert_eq!(
200            format!("{:?}", StringFormat::Unknown("custom".to_string())),
201            r#"StringFormat::Unknown("custom")"#
202        );
203    }
204}