seamantic 0.0.1

A library to enhance SeaORM
Documentation
//! Typed IDs for use as primary keys

use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;

use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};

/// The internal representation used by the database
pub type SeaOrmRepr = i64;

/// An opaque type representing a row ID
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Id<T> {
	id: SeaOrmRepr,
	#[cfg_attr(feature = "serde", serde(skip_serializing, default))]
	_phantom: PhantomData<T>,
}

// Manual implementation since `T: Clone` is not required
impl<T> Clone for Id<T> {
	fn clone(&self) -> Self {
		*self
	}
}

// Manual implementation since `T: Copy` is not required
impl<T> Copy for Id<T> {}

// Manual implementation since `T: PartialEq` is not required
impl<T> PartialEq for Id<T> {
	fn eq(&self, other: &Self) -> bool {
		self.id == other.id
	}
}

// Manual implementation since `T: Eq` is not required
impl<T> Eq for Id<T> {}

// Manual implementation since `T: PartialOrd` is not required
impl<T> PartialOrd for Id<T> {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

// Manual implementation since `T: Ord` is not required
impl<T> Ord for Id<T> {
	fn cmp(&self, other: &Self) -> Ordering {
		self.id.cmp(&other.id)
	}
}

// Manual implementation since `T: Hash` is not required
impl<T> Hash for Id<T> {
	fn hash<H: Hasher>(&self, state: &mut H) {
		self.id.hash(state);
	}
}

impl<T> Id<T> {
	/// Allows the conversion from a raw value to [Id], though the use is discouraged.
	pub fn from_raw(raw: SeaOrmRepr) -> Self {
		Self {
			id: raw,
			_phantom: PhantomData,
		}
	}

	/// Allows extracting the raw value, though the use is discouraged.
	pub fn into_raw(self) -> SeaOrmRepr {
		self.id
	}
}

impl<T> fmt::Debug for Id<T> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("Id")
			.field("T", &core::any::type_name::<T>())
			.field("id", &self.id)
			.finish()
	}
}

impl<T> ValueType for Id<T> {
	fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
		<SeaOrmRepr as ValueType>::try_from(v).map(|id| Self {
			id,
			_phantom: PhantomData,
		})
	}

	fn type_name() -> String {
		format!("Id<{}>", &core::any::type_name::<T>())
	}

	fn array_type() -> ArrayType {
		SeaOrmRepr::array_type()
	}

	fn column_type() -> ColumnType {
		SeaOrmRepr::column_type()
	}
}

impl<T> From<Id<T>> for Value {
	fn from(value: Id<T>) -> Self {
		value.id.into()
	}
}

impl<T> TryGetable for Id<T> {
	fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
		SeaOrmRepr::try_get_by(res, index).map(|id| Self {
			id,
			_phantom: PhantomData,
		})
	}
}

impl<T> TryFromU64 for Id<T> {
	fn try_from_u64(n: u64) -> Result<Self, DbErr> {
		SeaOrmRepr::try_from_u64(n).map(|id| Self {
			id,
			_phantom: PhantomData,
		})
	}
}

impl<T> Nullable for Id<T> {
	fn null() -> Value {
		SeaOrmRepr::null()
	}
}

#[cfg(test)]
mod tests {
	use sea_orm::{
		ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
		PrimaryKeyTrait,
	};

	use super::Id;

	#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
	#[sea_orm(table_name = "ids")]
	pub struct Model {
		#[sea_orm(primary_key, auto_increment = false)]
		id: Id<Model>,
		nullable: Option<Id<Model>>,
	}

	impl ActiveModelBehavior for ActiveModel {}

	#[derive(Debug, EnumIter, DeriveRelation)]
	pub enum Relation {}

	#[test]
	#[cfg(feature = "serde")]
	fn test_serde() {
		let id: Id<()> = Id::from_raw(1234);
		serde_test::assert_tokens(&id, &[serde_test::Token::I64(1234)]);
	}
}