summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/id.rs89
1 files changed, 65 insertions, 24 deletions
diff --git a/src/id.rs b/src/id.rs
index 8d71c5b..86cdf8a 100644
--- a/src/id.rs
+++ b/src/id.rs
@@ -1,8 +1,9 @@
+use std::fmt;
+
use rand::{seq::SliceRandom, thread_rng};
use serde::{Deserializer, Serializer};
use sqlx::encode::IsNull;
use sqlx::{Database, Decode, Encode, Type};
-use std::fmt;
// Make IDs that:
//
@@ -58,12 +59,10 @@ const ID_SIZE: usize = 15;
#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct Id<T>(String, T);
-impl<T> fmt::Display for Id<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
+// Implementations of prefix provide type tokens for IDs where a new value can be generated, with a
+// known prefix. Most implementations should also implement Default and be zero-sized types without
+// state, as many of the operations on Ids will discard or re-create type tokens (using
+// `Default::default()`) when converting between an ID type and some external form.
pub trait Prefix {
fn prefix(&self) -> &str;
}
@@ -86,16 +85,57 @@ where
}
}
+impl<T> Id<T> {
+ pub fn as_str(&self) -> &str {
+ let Self(value, _) = self;
+ value
+ }
+
+ fn inner(&self) -> &String {
+ let Self(value, _) = self;
+ value
+ }
+}
+
+// Any ID can be converted to a string. Its type token is discarded during the conversion.
+impl<T> From<Id<T>> for String {
+ fn from(value: Id<T>) -> Self {
+ let Id(value, _) = value;
+ value
+ }
+}
+
+// If the type token implements `Default`, then the corresponding ID type can be converted from a
+// string. A new type token will be generated using `T::default()`.
+impl<T> From<String> for Id<T>
+where
+ T: Default,
+{
+ fn from(value: String) -> Self {
+ Self(value, T::default())
+ }
+}
+
+// IDs are printable, exactly as their contained string is printable.
+impl<T> fmt::Display for Id<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.inner().fmt(f)
+ }
+}
+
+// IDs are serializable, exactly as their contained string is serializable. The type token is
+// discarded during serialization.
impl<T> serde::ser::Serialize for Id<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
- let Self(id, _) = self;
- id.serialize(serializer)
+ self.inner().serialize(serializer)
}
}
+// If the type token implements `Default`, then the corresponding ID type is deserializable as a
+// String. A new type token will be generated using `T::default()` during deserialization.
impl<'de, T> serde::de::Deserialize<'de> for Id<T>
where
T: Default,
@@ -104,16 +144,18 @@ where
where
D: Deserializer<'de>,
{
- let instance = T::default();
let id = String::deserialize(deserializer)?;
- Ok(Self(id, instance))
+ Ok(id.into())
}
}
-// Type is manually implemented so that we can implement Decode to do
-// recover the type token on read. Implementation is otherwise based on
-// `#[derive(sqlx::Type)]` with the `#[sqlx(transparent)]` attribute.
+// An ID is an sqlx database type for any database where String is a database type. In practice,
+// that's all of them.
+//
+// This is largely cribbed from the implementation generated by `#[sqlx::transparent]`, but we
+// implement it by hand here in order to handle the fact that an ID has two contained values,
+// rather than one.
impl<DB, T> Type<DB> for Id<T>
where
DB: Database,
@@ -128,6 +170,8 @@ where
}
}
+// If the type token implements Default, then the corresponding ID type can be decoded as a String
+// from a database record. A new type token will be generated using `T::default()` during decoding.
impl<'r, DB, T> Decode<'r, DB> for Id<T>
where
DB: Database,
@@ -135,12 +179,13 @@ where
T: Default,
{
fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
- let instance = T::default();
let id = String::decode(value)?;
- Ok(Self(id, instance))
+ Ok(id.into())
}
}
+// An ID is encodeable to a database value whenever String is encodeable. The type token is
+// discarded during encoding.
impl<'q, DB, T> Encode<'q, DB> for Id<T>
where
DB: Database,
@@ -150,25 +195,21 @@ where
self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, sqlx::error::BoxDynError> {
- let Self(id, _) = self;
- id.encode(buf)
+ self.inner().encode(buf)
}
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, sqlx::error::BoxDynError> {
- let Self(id, _) = self;
- id.encode_by_ref(buf)
+ self.inner().encode_by_ref(buf)
}
fn produces(&self) -> Option<<DB as Database>::TypeInfo> {
- let Self(id, _) = self;
- id.produces()
+ self.inner().produces()
}
fn size_hint(&self) -> usize {
- let Self(id, _) = self;
- id.size_hint()
+ self.inner().size_hint()
}
}