siderite_validation/types/
uuid.rs1use serde::{Deserialize, Serialize};
4
5use crate::context::ValidationContext;
6use crate::dump::Dump;
7use crate::error::FieldError;
8use crate::rules;
9use crate::schema::{Schema, SchemaObject, SchemaRegistry};
10use crate::validate::Validate;
11use serde_json::Value;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(try_from = "String", into = "String")]
20pub struct Uuid(::uuid::Uuid);
21
22impl Uuid {
23 pub fn new(value: impl AsRef<str>) -> Result<Self, FieldError> {
25 rules::parse_uuid(value.as_ref()).map(Self)
26 }
27}
28
29crate::types::impl_wrapper!(Uuid => ::uuid::Uuid);
30
31impl TryFrom<String> for Uuid {
32 type Error = FieldError;
33
34 fn try_from(value: String) -> Result<Self, Self::Error> {
35 Self::new(value)
36 }
37}
38
39impl TryFrom<&str> for Uuid {
40 type Error = FieldError;
41
42 fn try_from(value: &str) -> Result<Self, Self::Error> {
43 Self::new(value)
44 }
45}
46
47impl From<Uuid> for String {
48 fn from(value: Uuid) -> Self {
49 value.0.to_string()
50 }
51}
52
53impl Validate for Uuid {
54 fn prepare(input: &mut Value, ctx: &mut ValidationContext) {
55 if super::prepare_as_string(input, ctx) {
56 ctx.check(Self::new(input.as_str().unwrap_or_default()).map(|_| ()));
57 }
58 }
59}
60
61impl Schema for Uuid {
62 fn schema(_: &mut SchemaRegistry) -> SchemaObject {
63 SchemaObject::of_type("string").with("format", "uuid")
64 }
65}
66
67impl Dump for Uuid {}
68
69#[cfg(test)]
70mod tests {
71 #![allow(clippy::unwrap_used)]
72
73 use super::*;
74 use crate::context::ValidationContext;
75 use crate::dump::{Dump, DumpOptions};
76 use crate::schema::schema_for;
77 use crate::types::prepare_codes;
78 use serde_json::json;
79
80 const HYPHENATED: &str = "550e8400-e29b-41d4-a716-446655440000";
81 const SIMPLE: &str = "550e8400e29b41d4a716446655440000";
82
83 #[test]
84 fn uuid_construction() {
85 let uuid = Uuid::new(HYPHENATED).unwrap();
86 assert_eq!(uuid.to_string(), HYPHENATED);
87 assert_eq!(Uuid::new(SIMPLE).unwrap().to_string(), HYPHENATED);
88 assert!(Uuid::new("550E8400-E29B-41D4-A716-446655440000").is_ok());
89 assert_eq!(Uuid::new("not-a-uuid").unwrap_err().code, "uuid_parsing");
90 assert_eq!(
91 Uuid::new("urn:uuid:550e8400-e29b-41d4-a716-446655440000")
92 .unwrap_err()
93 .code,
94 "uuid_parsing"
95 );
96 }
97
98 #[test]
99 fn uuid_serde_round_trip_and_reject() {
100 let uuid = Uuid::new(SIMPLE).unwrap();
101 let encoded = serde_json::to_string(&uuid).unwrap();
102 assert_eq!(encoded, format!("\"{HYPHENATED}\""));
103 let round: Uuid = serde_json::from_str(&encoded).unwrap();
104 assert_eq!(round, uuid);
105 assert!(serde_json::from_str::<Uuid>("\"nope\"").is_err());
106 }
107
108 #[test]
109 fn uuid_prepare_lax_and_strict() {
110 let (out, codes) = prepare_codes::<Uuid>(json!(HYPHENATED), ValidationContext::new());
111 assert!(codes.is_empty());
112 assert_eq!(out, json!(HYPHENATED));
113 assert_eq!(
114 prepare_codes::<Uuid>(json!(1), ValidationContext::new()).1,
115 ["string_type"]
116 );
117 assert_eq!(
118 prepare_codes::<Uuid>(json!(1), ValidationContext::new().with_strict(true)).1,
119 ["string_type"]
120 );
121 assert_eq!(
122 prepare_codes::<Uuid>(json!("nope"), ValidationContext::new()).1,
123 ["uuid_parsing"]
124 );
125 }
126
127 #[test]
128 fn uuid_schema_dump_and_parse_value() {
129 assert_eq!(
130 schema_for::<Uuid>().0.into_value(),
131 json!({"type": "string", "format": "uuid"})
132 );
133 let uuid = Uuid::new(HYPHENATED).unwrap();
134 assert_eq!(uuid.dump(&DumpOptions::new()).unwrap(), json!(HYPHENATED));
135 let parsed = crate::parse_value::<Uuid>(json!(SIMPLE), ValidationContext::new()).unwrap();
136 assert_eq!(parsed.to_string(), HYPHENATED);
137 let err = crate::parse_value::<Uuid>(json!("nope"), ValidationContext::new()).unwrap_err();
138 assert_eq!(err.errors[0].code, "uuid_parsing");
139 }
140}