chuchi_postgres_types/
uid.rs1use std::fmt;
2use std::str::FromStr;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use base64::engine::{general_purpose::URL_SAFE_NO_PAD, Engine};
6use base64::DecodeError;
7use rand::{rngs::OsRng, RngCore};
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
18#[cfg_attr(feature = "juniper", graphql(with = graphql))]
19pub struct UniqueId([u8; 10]);
20
21impl UniqueId {
22 pub fn new() -> Self {
23 let secs_bytes = SystemTime::now()
24 .duration_since(UNIX_EPOCH)
25 .expect("SystemTime before UNIX EPOCH!")
26 .as_secs()
27 .to_be_bytes();
28
29 let mut bytes = [0u8; 10];
30 bytes[..5].copy_from_slice(&secs_bytes[3..8]);
31
32 OsRng.fill_bytes(&mut bytes[5..]);
33
34 Self(bytes)
35 }
36
37 pub const fn from_raw(inner: [u8; 10]) -> Self {
40 Self(inner)
41 }
42
43 pub fn from_slice_unchecked(slice: &[u8]) -> Self {
44 let mut bytes = [0u8; 10];
45 bytes.copy_from_slice(slice);
46 Self(bytes)
47 }
48
49 pub fn to_b64(&self) -> String {
50 URL_SAFE_NO_PAD.encode(self.0)
51 }
52
53 pub fn parse_from_b64<T>(b64: T) -> Result<Self, DecodeError>
55 where
56 T: AsRef<[u8]>,
57 {
58 if b64.as_ref().len() != 14 {
59 return Err(DecodeError::InvalidLength(b64.as_ref().len()));
60 }
61
62 let mut bytes = [0u8; 10];
63 URL_SAFE_NO_PAD
64 .decode_slice_unchecked(b64, &mut bytes)
65 .map(|n| assert_eq!(n, bytes.len()))
66 .map(|_| Self(bytes))
67 }
68
69 pub fn from_bytes(bytes: [u8; 10]) -> Self {
70 Self(bytes)
71 }
72
73 pub fn into_bytes(self) -> [u8; 10] {
74 self.0
75 }
76
77 pub fn since_unix_secs(&self) -> u64 {
78 let mut bytes = [0u8; 8];
79 bytes[3..].copy_from_slice(&self.0[..5]);
80 u64::from_be_bytes(bytes)
81 }
82
83 pub fn as_slice(&self) -> &[u8] {
84 &self.0
85 }
86}
87
88impl fmt::Debug for UniqueId {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.debug_tuple("UniqueId").field(&self.to_b64()).finish()
91 }
92}
93
94impl fmt::Display for UniqueId {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 self.to_b64().fmt(f)
97 }
98}
99
100impl FromStr for UniqueId {
101 type Err = DecodeError;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 Self::parse_from_b64(s)
105 }
106}
107
108#[cfg(feature = "serde")]
111mod impl_serde {
112 use super::*;
113
114 use std::borrow::Cow;
115
116 use serde::de::{Deserializer, Error};
117 use serde::ser::Serializer;
118 use serde::{Deserialize, Serialize};
119
120 impl Serialize for UniqueId {
121 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122 where
123 S: Serializer,
124 {
125 serializer.serialize_str(&self.to_b64())
126 }
127 }
128
129 impl<'de> Deserialize<'de> for UniqueId {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: Deserializer<'de>,
133 {
134 let s: Cow<'_, str> = Deserialize::deserialize(deserializer)?;
135 let s = s.as_ref();
136 if s.len() == 14 {
137 UniqueId::parse_from_b64(s).map_err(D::Error::custom)
138 } else {
139 Err(D::Error::custom(
140 "expected string with exactly 14 characters",
141 ))
142 }
143 }
144 }
145}
146
147#[cfg(feature = "postgres")]
148mod postgres {
149 use bytes::BytesMut;
150 use postgres_types::{to_sql_checked, FromSql, IsNull, ToSql, Type};
151
152 use super::*;
153
154 impl ToSql for UniqueId {
155 fn to_sql(
156 &self,
157 ty: &Type,
158 out: &mut BytesMut,
159 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
160 where
161 Self: Sized,
162 {
163 self.to_b64().to_sql(ty, out)
164 }
165
166 fn accepts(ty: &Type) -> bool
167 where
168 Self: Sized,
169 {
170 <&str as ToSql>::accepts(ty)
171 }
172
173 to_sql_checked!();
174 }
175
176 impl<'r> FromSql<'r> for UniqueId {
177 fn from_sql(
178 ty: &Type,
179 raw: &'r [u8],
180 ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
181 let s = <&str as FromSql>::from_sql(ty, raw)?;
182 UniqueId::parse_from_b64(s).map_err(Into::into)
183 }
184
185 fn accepts(ty: &Type) -> bool {
186 <&str as FromSql>::accepts(ty)
187 }
188 }
189}
190
191#[cfg(feature = "protobuf")]
192mod protobuf {
193 use super::*;
194
195 use protopuffer::{
196 bytes::BytesWrite,
197 decode::{DecodeError, DecodeMessage, FieldKind},
198 encode::{
199 EncodeError, EncodeMessage, FieldOpt, MessageEncoder, SizeBuilder,
200 },
201 WireType,
202 };
203
204 impl EncodeMessage for UniqueId {
205 const WIRE_TYPE: WireType = WireType::Len;
206
207 fn is_default(&self) -> bool {
208 false
209 }
210
211 fn encoded_size(
212 &mut self,
213 field: Option<FieldOpt>,
214 builder: &mut SizeBuilder,
215 ) -> Result<(), EncodeError> {
216 self.0.encoded_size(field, builder)
217 }
218
219 fn encode<B>(
220 &mut self,
221 field: Option<FieldOpt>,
222 encoder: &mut MessageEncoder<B>,
223 ) -> Result<(), EncodeError>
224 where
225 B: BytesWrite,
226 {
227 self.0.encode(field, encoder)
228 }
229 }
230
231 impl<'m> DecodeMessage<'m> for UniqueId {
232 const WIRE_TYPE: WireType = WireType::Len;
233
234 fn decode_default() -> Self {
235 Self::from_raw([0; 10])
236 }
237
238 fn merge(
239 &mut self,
240 kind: FieldKind<'m>,
241 is_field: bool,
242 ) -> Result<(), DecodeError> {
243 self.0.merge(kind, is_field)
244 }
245 }
246}
247
248#[cfg(feature = "juniper")]
249mod graphql {
250 use super::*;
251
252 use juniper::{
253 InputValue, ParseScalarResult, ScalarToken, ScalarValue, Value,
254 };
255
256 pub(crate) fn to_output<S: ScalarValue>(v: &UniqueId) -> Value<S> {
257 Value::scalar(v.to_string())
258 }
259
260 pub(crate) fn from_input<S: ScalarValue>(
261 v: &InputValue<S>,
262 ) -> Result<UniqueId, String> {
263 v.as_string_value()
264 .ok_or("Expected a string")?
265 .parse()
266 .map_err(|e: DecodeError| e.to_string())
267 }
268
269 pub(crate) fn parse_token<S: ScalarValue>(
270 value: ScalarToken<'_>,
271 ) -> ParseScalarResult<S> {
272 <String as juniper::ParseScalarValue<S>>::from_str(value)
273 }
274}
275
276#[cfg(all(test, feature = "serde"))]
277mod tests {
278
279 use super::*;
280 use serde_json::{from_str, from_value, Value};
281
282 #[test]
285 fn serde_test() {
286 let s = "\"AGCGeWIDTlipbg\"";
287 let d: UniqueId = from_str(s).unwrap();
288 assert_eq!(d.to_string(), "AGCGeWIDTlipbg");
289
290 let v = Value::String("AGCGeWIDTlipbg".into());
291 let d: UniqueId = from_value(v).unwrap();
292 assert_eq!(d.to_string(), "AGCGeWIDTlipbg");
293 }
294}