fire_postgres/
uid.rs

1use crate::table::column::{ColumnData, ColumnKind, ColumnType, FromDataError};
2
3use std::borrow::Cow;
4use std::fmt;
5use std::str::FromStr;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use base64::engine::{general_purpose::URL_SAFE_NO_PAD, Engine};
9use base64::DecodeError;
10use rand::{rngs::OsRng, RngCore};
11
12use serde::de::{Deserializer, Error};
13use serde::ser::Serializer;
14use serde::{Deserialize, Serialize};
15
16/// A UniqueId that can be used within a database.
17/// Is not cryptographically secure and could be bruteforced.
18///
19/// Contains 10bytes
20/// - 0..5 are seconds since the UNIX_EPOCH
21/// - 5..10 are random
22#[derive(Clone, Copy, PartialEq, Eq, Hash)]
23// graphql
24#[cfg_attr(feature = "graphql", derive(juniper::GraphQLScalar))]
25#[cfg_attr(feature = "graphql", graphql(with = graphql))]
26pub struct UniqueId([u8; 10]);
27
28impl UniqueId {
29	pub fn new() -> Self {
30		let secs_bytes = SystemTime::now()
31			.duration_since(UNIX_EPOCH)
32			.expect("SystemTime before UNIX EPOCH!")
33			.as_secs()
34			.to_be_bytes();
35
36		let mut bytes = [0u8; 10];
37		bytes[..5].copy_from_slice(&secs_bytes[3..8]);
38
39		OsRng.fill_bytes(&mut bytes[5..]);
40
41		Self(bytes)
42	}
43
44	/// This creates a unique id with it's raw content
45	/// making it able to be called in a const context.
46	pub const fn from_raw(inner: [u8; 10]) -> Self {
47		Self(inner)
48	}
49
50	pub fn from_slice_unchecked(slice: &[u8]) -> Self {
51		let mut bytes = [0u8; 10];
52		bytes.copy_from_slice(slice);
53		Self(bytes)
54	}
55
56	pub fn to_b64(&self) -> String {
57		URL_SAFE_NO_PAD.encode(self.0)
58	}
59
60	/// If the string is not 14 bytes long returns InvalidLength
61	pub fn parse_from_b64<T>(b64: T) -> Result<Self, DecodeError>
62	where
63		T: AsRef<[u8]>,
64	{
65		if b64.as_ref().len() != 14 {
66			return Err(DecodeError::InvalidLength(b64.as_ref().len()));
67		}
68
69		let mut bytes = [0u8; 10];
70		URL_SAFE_NO_PAD
71			.decode_slice_unchecked(b64, &mut bytes)
72			.map(|n| assert_eq!(n, bytes.len()))
73			.map(|_| Self(bytes))
74	}
75
76	pub fn from_bytes(bytes: [u8; 10]) -> Self {
77		Self(bytes)
78	}
79
80	pub fn into_bytes(self) -> [u8; 10] {
81		self.0
82	}
83
84	pub fn since_unix_secs(&self) -> u64 {
85		let mut bytes = [0u8; 8];
86		bytes[3..].copy_from_slice(&self.0[..5]);
87		u64::from_be_bytes(bytes)
88	}
89
90	pub fn as_slice(&self) -> &[u8] {
91		&self.0
92	}
93}
94
95impl fmt::Debug for UniqueId {
96	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97		f.debug_tuple("UniqueId").field(&self.to_b64()).finish()
98	}
99}
100
101impl fmt::Display for UniqueId {
102	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103		self.to_b64().fmt(f)
104	}
105}
106
107impl FromStr for UniqueId {
108	type Err = DecodeError;
109
110	fn from_str(s: &str) -> Result<Self, Self::Err> {
111		Self::parse_from_b64(s)
112	}
113}
114
115impl From<DecodeError> for FromDataError {
116	fn from(e: DecodeError) -> Self {
117		Self::CustomString(format!("uniqueid decode error {:?}", e))
118	}
119}
120
121impl ColumnType for UniqueId {
122	fn column_kind() -> ColumnKind {
123		ColumnKind::FixedText(14)
124	}
125
126	fn to_data(&self) -> ColumnData<'_> {
127		ColumnData::Text(self.to_b64().into())
128	}
129
130	fn from_data(data: ColumnData) -> Result<Self, FromDataError> {
131		match data {
132			ColumnData::Text(s) if s.len() == 14 => {
133				Ok(Self::parse_from_b64(s.as_str())?)
134			}
135			_ => Err(FromDataError::ExpectedType(
136				"char with 14 chars for unique id",
137			)),
138		}
139	}
140}
141// SERDE
142
143impl Serialize for UniqueId {
144	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
145	where
146		S: Serializer,
147	{
148		serializer.serialize_str(&self.to_b64())
149	}
150}
151
152impl<'de> Deserialize<'de> for UniqueId {
153	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154	where
155		D: Deserializer<'de>,
156	{
157		let s: Cow<'_, str> = Deserialize::deserialize(deserializer)?;
158		let s = s.as_ref();
159		if s.len() == 14 {
160			UniqueId::parse_from_b64(s).map_err(D::Error::custom)
161		} else {
162			Err(D::Error::custom(
163				"expected string with exactly 14 characters",
164			))
165		}
166	}
167}
168
169#[cfg(feature = "protobuf")]
170mod protobuf {
171	use super::*;
172
173	use fire_protobuf::{
174		bytes::BytesWrite,
175		decode::{DecodeError, DecodeMessage, FieldKind},
176		encode::{
177			EncodeError, EncodeMessage, FieldOpt, MessageEncoder, SizeBuilder,
178		},
179		WireType,
180	};
181
182	impl EncodeMessage for UniqueId {
183		const WIRE_TYPE: WireType = WireType::Len;
184
185		fn is_default(&self) -> bool {
186			false
187		}
188
189		fn encoded_size(
190			&mut self,
191			field: Option<FieldOpt>,
192			builder: &mut SizeBuilder,
193		) -> Result<(), EncodeError> {
194			self.0.encoded_size(field, builder)
195		}
196
197		fn encode<B>(
198			&mut self,
199			field: Option<FieldOpt>,
200			encoder: &mut MessageEncoder<B>,
201		) -> Result<(), EncodeError>
202		where
203			B: BytesWrite,
204		{
205			self.0.encode(field, encoder)
206		}
207	}
208
209	impl<'m> DecodeMessage<'m> for UniqueId {
210		const WIRE_TYPE: WireType = WireType::Len;
211
212		fn decode_default() -> Self {
213			Self::from_raw([0; 10])
214		}
215
216		fn merge(
217			&mut self,
218			kind: FieldKind<'m>,
219			is_field: bool,
220		) -> Result<(), DecodeError> {
221			self.0.merge(kind, is_field)
222		}
223	}
224}
225
226#[cfg(feature = "graphql")]
227mod graphql {
228	use super::*;
229
230	use juniper::{
231		Value, ScalarValue, InputValue, ScalarToken, ParseScalarResult
232	};
233
234	pub(crate) fn to_output<S: ScalarValue>(v: &UniqueId) -> Value<S> {
235		Value::scalar(v.to_string())
236	}
237
238	pub(crate) fn from_input<S: ScalarValue>(
239		v: &InputValue<S>
240	) -> Result<UniqueId, String> {
241		v.as_string_value()
242			.ok_or("Expected a string")?
243			.parse()
244			.map_err(|e: DecodeError| e.to_string())
245	}
246
247	pub(crate) fn parse_token<S: ScalarValue>(
248		value: ScalarToken<'_>
249	) -> ParseScalarResult<S> {
250		<String as juniper::ParseScalarValue<S>>::from_str(value)
251	}
252}
253
254#[cfg(test)]
255mod tests {
256
257	use super::*;
258	use serde_json::{from_str, from_value, Value};
259
260	// abcdefghijklmnopqrstuvwxyz
261
262	#[test]
263	fn serde_test() {
264		let s = "\"AGCGeWIDTlipbg\"";
265		let d: UniqueId = from_str(s).unwrap();
266		assert_eq!(d.to_string(), "AGCGeWIDTlipbg");
267
268		let v = Value::String("AGCGeWIDTlipbg".into());
269		let d: UniqueId = from_value(v).unwrap();
270		assert_eq!(d.to_string(), "AGCGeWIDTlipbg");
271	}
272}