scufflecloud_id/
lib.rs

1use std::fmt::Debug;
2
3pub mod exports {
4    pub use {diesel, serde, ulid, uuid};
5}
6
7#[macro_export]
8macro_rules! impl_id {
9    ($vis:vis $type:ident, $prefix:literal) => {
10        #[derive(
11            $crate::exports::diesel::deserialize::FromSqlRow,
12            $crate::exports::diesel::expression::AsExpression,
13            Debug,
14            PartialEq,
15            Eq,
16            Hash,
17            Clone,
18            Copy,
19            Default,
20        )]
21        #[diesel(sql_type = $crate::exports::diesel::sql_types::Uuid)]
22        $vis struct $type($crate::exports::ulid::Ulid);
23
24        impl ::std::convert::From<$type> for $crate::exports::ulid::Ulid {
25            fn from(value: $type) -> Self {
26                value.0
27            }
28        }
29
30        impl ::std::convert::From<$crate::exports::ulid::Ulid> for $type {
31            fn from(value: $crate::exports::ulid::Ulid) -> Self {
32                Self(value)
33            }
34        }
35
36        impl ::std::convert::From<$crate::exports::uuid::Uuid> for $type {
37            fn from(value: $crate::exports::uuid::Uuid) -> Self {
38                Self($crate::exports::ulid::Ulid::from(value))
39            }
40        }
41
42        impl $crate::exports::diesel::deserialize::FromSql<$crate::exports::diesel::sql_types::Uuid, $crate::exports::diesel::pg::Pg> for $type {
43            fn from_sql(bytes: $crate::exports::diesel::pg::PgValue<'_>) -> $crate::exports::diesel::deserialize::Result<Self> {
44                let uuid: $crate::exports::uuid::Uuid = $crate::exports::diesel::deserialize::FromSql::from_sql(bytes)?;
45
46                Ok(Self($crate::exports::ulid::Ulid::from(uuid)))
47            }
48        }
49
50        impl $crate::exports::diesel::serialize::ToSql<$crate::exports::diesel::sql_types::Uuid, $crate::exports::diesel::pg::Pg> for $type {
51            fn to_sql<'b>(&'b self, out: &mut $crate::exports::diesel::serialize::Output<'b, '_, $crate::exports::diesel::pg::Pg>) -> $crate::exports::diesel::serialize::Result {
52                ::std::io::Write::write_all(out, &self.0.to_bytes())
53                    .map(|_| $crate::exports::diesel::serialize::IsNull::No)
54                    .map_err(Into::into)
55            }
56        }
57
58        impl $type {
59            $vis const PREFIX: &'static str = $prefix;
60
61            $vis fn new() -> Self {
62                Self($crate::exports::ulid::Ulid::new())
63            }
64
65            $vis fn ulid(&self) -> $crate::exports::ulid::Ulid {
66                self.0
67            }
68
69            $vis fn from_ulid(ulid: $crate::exports::ulid::Ulid) -> Self {
70                Self(ulid)
71            }
72
73            $vis fn datetime(&self) -> ::std::time::SystemTime {
74                self.0.datetime()
75            }
76        }
77
78        impl ::std::str::FromStr for $type {
79            type Err = $crate::IdParseError;
80
81            fn from_str(s: &str) -> Result<Self, Self::Err> {
82                let id = s.strip_prefix(Self::PREFIX).ok_or($crate::IdParseError::PrefixMismatch(Self::PREFIX))?;
83                let id = $crate::exports::ulid::Ulid::from_str(id)?;
84                Ok(Self::from_ulid(id))
85            }
86        }
87
88        impl ::std::fmt::Display for $type {
89            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
90                write!(f, "{}{}", Self::PREFIX, self.0)
91            }
92        }
93
94        impl ::serde::Serialize for $type {
95            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
96            where
97                S: ::serde::Serializer,
98            {
99                serializer.serialize_str(&self.to_string())
100            }
101        }
102
103        impl<'de> ::serde::Deserialize<'de> for $type {
104            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105            where
106                D: ::serde::Deserializer<'de>,
107            {
108                let s = ::std::string::String::deserialize(deserializer)?;
109                s.parse().map_err(serde::de::Error::custom)
110            }
111        }
112    };
113}
114
115#[derive(Debug, thiserror::Error)]
116pub enum IdParseError {
117    #[error("id doesnt have prefix {0}")]
118    PrefixMismatch(&'static str),
119    #[error("invalid ulid: {0}")]
120    Ulid(#[from] ulid::DecodeError),
121}