architect_api/utils/
uuid_val.rs

1/// Implement a named wrapper type around a UUID
2#[macro_export]
3macro_rules! uuid_val {
4    ($name:ident, $ns:ident) => {
5        uuid_val!($name, $ns, {});
6    };
7    ($name:ident, $ns:ident, { $($key:expr_2021 => $value:expr_2021),* $(,)? }) => {
8        /// Wrapper type around a UUIDv5 for a given namespace.  These types are
9        /// parseable from either the UUIDv5 string representation, or from the
10        /// name itself, as they are 1-1.
11        #[derive(
12            Debug,
13            Clone,
14            Copy,
15            Hash,
16            PartialEq,
17            Eq,
18            PartialOrd,
19            Ord,
20            serde::Serialize,
21            serde_with::DeserializeFromStr,
22        )]
23        #[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
24        pub struct $name(pub uuid::Uuid);
25
26        impl std::fmt::Display for $name {
27            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28                write!(f, "{}", self.0)
29            }
30        }
31
32        impl std::str::FromStr for $name {
33            type Err = std::convert::Infallible;
34
35            fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
36                $(
37                    if s == $key {
38                        return Ok($value);
39                    }
40                )*
41                match s.parse::<uuid::Uuid>() {
42                    Ok(uuid) => Ok(Self(uuid)),
43                    Err(_) => Ok(Self::from(s)),
44                }
45            }
46        }
47
48        /// Implement From<AsRef<str>> for a UUIDv5, using a given namespace
49        impl<S: AsRef<str>> From<S> for $name {
50            fn from(s: S) -> Self {
51                Self(Uuid::new_v5(&$ns, s.as_ref().as_bytes()))
52            }
53        }
54
55        impl std::ops::Deref for $name {
56            type Target = uuid::Uuid;
57
58            fn deref(&self) -> &Self::Target {
59                &self.0
60            }
61        }
62
63        impl std::borrow::Borrow<uuid::Uuid> for $name {
64            fn borrow(&self) -> &uuid::Uuid {
65                &self.0
66            }
67        }
68
69        #[cfg(feature = "sqlx")]
70        impl<'q> sqlx::Encode<'q, sqlx::Postgres> for $name {
71            fn encode_by_ref(
72                &self,
73                buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
74            ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
75                self.0.encode(buf)
76            }
77        }
78
79        #[cfg(feature = "sqlx")]
80        impl<'r> sqlx::Decode<'r, sqlx::Postgres> for $name {
81            fn decode(
82                value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
83            ) -> Result<Self, sqlx::error::BoxDynError> {
84                let value: String = sqlx::Decode::<'r, sqlx::Postgres>::decode(value)?;
85                Ok($name::from(&value))
86            }
87        }
88
89        #[cfg(feature = "sqlx")]
90        impl sqlx::Type<sqlx::Postgres> for $name {
91            fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
92                Uuid::type_info()
93            }
94        }
95
96        #[cfg(feature = "juniper")]
97        impl $name {
98            #[allow(clippy::wrong_self_convention)]
99            fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
100                juniper::Value::scalar(self.0.to_string())
101            }
102
103            fn from_input<S>(v: &juniper::InputValue<S>) -> Result<Self, String>
104            where
105                S: juniper::ScalarValue,
106            {
107                v.as_string_value()
108                    .map(|s| <Self as std::str::FromStr>::from_str(s))
109                    .ok_or_else(|| format!("Expected `String`, found: {v}"))?
110                    .map(|uuid| Self(*uuid))
111                    .map_err(|e| e.to_string())
112            }
113
114            fn parse_token<S>(
115                value: juniper::ScalarToken<'_>,
116            ) -> juniper::ParseScalarResult<S>
117            where
118                S: juniper::ScalarValue,
119            {
120                <String as juniper::ParseScalarValue<S>>::from_str(value)
121            }
122        }
123
124        impl schemars::JsonSchema for $name {
125            fn schema_name() -> String {
126                format!("{}", stringify!($name)).to_string()
127            }
128
129            fn json_schema(
130                r#gen: &mut schemars::r#gen::SchemaGenerator,
131            ) -> schemars::schema::Schema {
132                uuid::Uuid::json_schema(r#gen)
133            }
134        }
135
136        #[cfg(feature = "rusqlite")]
137        impl rusqlite::ToSql for $name {
138            fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
139                use rusqlite::types::{ToSqlOutput, Value};
140                Ok(ToSqlOutput::Owned(Value::Text(self.to_string())))
141            }
142        }
143
144        #[cfg(feature = "tokio-postgres")]
145        impl tokio_postgres::types::ToSql for $name {
146            tokio_postgres::types::to_sql_checked!();
147
148            fn to_sql(
149                &self,
150                ty: &tokio_postgres::types::Type,
151                out: &mut bytes::BytesMut,
152            ) -> Result<
153                tokio_postgres::types::IsNull,
154                Box<dyn std::error::Error + Sync + Send>,
155            > {
156                self.0.to_sql(ty, out)
157            }
158
159            fn accepts(ty: &tokio_postgres::types::Type) -> bool {
160                Uuid::accepts(ty)
161            }
162        }
163
164        #[cfg(feature = "tokio-postgres")]
165        impl<'a> tokio_postgres::types::FromSql<'a> for $name {
166            fn from_sql(
167                ty: &tokio_postgres::types::Type,
168                raw: &'a [u8],
169            ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
170                Uuid::from_sql(ty, raw).map($name)
171            }
172
173            fn accepts(ty: &tokio_postgres::types::Type) -> bool {
174                <Uuid as postgres_types::FromSql>::accepts(ty)
175            }
176        }
177    };
178}