kittycad_modeling_cmds/
id.rs1use schemars::JsonSchema;
2use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize};
3use std::fmt;
4use std::str::FromStr;
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, Hash, Ord, PartialOrd, Eq, PartialEq, JsonSchema, Serialize)]
9#[cfg_attr(test, derive(Default))]
10#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
11#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
12pub struct ModelingCmdId(pub Uuid);
13
14impl AsRef<Uuid> for ModelingCmdId {
15 fn as_ref(&self) -> &Uuid {
16 &self.0
17 }
18}
19
20#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn modeling_cmd_id_from_bson() {
30 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
31 struct Id {
32 id: ModelingCmdId,
33 }
34
35 let id_before = Id {
37 id: ModelingCmdId("f09fc20f-40d4-4a73-92fa-05d53baaabac".parse().unwrap()),
38 };
39 let bytes = bson::to_vec(&id_before).unwrap();
40 let id_after = bson::from_reader(bytes.as_slice()).unwrap();
41 assert_eq!(id_before, id_after);
42 }
43}
44
45struct UuidVisitor;
46
47impl<'de> Visitor<'de> for UuidVisitor {
48 type Value = ModelingCmdId;
49
50 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
51 formatter.write_str("expected a string for uuid")
52 }
53
54 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
55 where
56 E: de::Error,
57 {
58 ModelingCmdId::from_str(value).map_err(|e| de::Error::custom(e.to_string()))
59 }
60
61 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
62 where
63 E: de::Error,
64 {
65 Uuid::from_slice(v)
66 .map_err(|e| de::Error::custom(e.to_string()))
67 .map(ModelingCmdId)
68 }
69
70 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
71 where
72 E: de::Error,
73 {
74 Uuid::from_slice(v)
75 .map_err(|e| de::Error::custom(e.to_string()))
76 .map(ModelingCmdId)
77 }
78
79 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
80 where
81 E: de::Error,
82 {
83 Uuid::from_slice(v.as_slice())
84 .map_err(|e| de::Error::custom(e.to_string()))
85 .map(ModelingCmdId)
86 }
87}
88
89impl<'de> Deserialize<'de> for ModelingCmdId {
90 fn deserialize<D>(deserializer: D) -> Result<ModelingCmdId, D::Error>
91 where
92 D: Deserializer<'de>,
93 {
94 deserializer.deserialize_any(UuidVisitor)
95 }
96}
97
98const ERR_GROUP_LENGTH: &str = "----";
100
101impl std::str::FromStr for ModelingCmdId {
102 type Err = uuid::Error;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 let s2 = if s.len() == 32 { ERR_GROUP_LENGTH } else { s };
117 Uuid::from_str(s2).map(Self)
118 }
119}
120
121impl From<Uuid> for ModelingCmdId {
122 fn from(uuid: Uuid) -> Self {
123 Self(uuid)
124 }
125}
126
127impl From<ModelingCmdId> for Uuid {
128 fn from(id: ModelingCmdId) -> Self {
129 id.0
130 }
131}
132
133impl std::fmt::Display for ModelingCmdId {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 self.0.fmt(f)
136 }
137}
138
139#[test]
140fn smoke_test() {
141 use std::str::FromStr;
142 assert_eq!(
143 ModelingCmdId::from_str("00000000-0000-0000-0000-000000000000"),
144 Ok(ModelingCmdId(
145 Uuid::from_str("00000000-0000-0000-0000-000000000000").unwrap()
146 ))
147 );
148}
149
150#[test]
151fn requires_hyphens() {
152 use std::str::FromStr;
153 assert_ne!(
154 ModelingCmdId::from_str("00000000000000000000000000000000"),
155 Ok(ModelingCmdId(
156 Uuid::from_str("00000000-0000-0000-0000-000000000000").unwrap()
157 ))
158 );
159}