Skip to content

Instantly share code, notes, and snippets.

@dklassen
Last active January 19, 2025 15:15
Show Gist options
  • Save dklassen/e75dca9fd38d2be3f8e7bec486af2668 to your computer and use it in GitHub Desktop.
Save dklassen/e75dca9fd38d2be3f8e7bec486af2668 to your computer and use it in GitHub Desktop.
Had a problem defining constraints to represent Sqlite Column constraints. Started to fool around with PhantomData and was curious to see if I could achieve what I wanted roughly with typing. This is what the end result was: using Phantom Data to encode stateful logic around adding and transitioning constraints.
use std::collections::HashSet as Set;
use std::marker::PhantomData;
// Marker traits for states
pub trait KeyState {}
pub struct InvalidState;
impl KeyState for InvalidState {}
pub trait UsableState {}
impl UsableState for NoState {}
impl UsableState for DefaultState {}
impl UsableState for PrimaryKeyState {}
impl UsableState for ForeignKeyState {}
pub struct NoState;
impl KeyState for NoState {}
pub struct DefaultState;
impl KeyState for DefaultState {}
pub struct PrimaryKeyState;
impl KeyState for PrimaryKeyState {}
pub struct ForeignKeyState;
impl KeyState for ForeignKeyState {}
// Constraint types
pub trait Constraint {}
pub struct AutoIncrement;
impl Constraint for AutoIncrement {}
pub struct Default {
pub value: &'static str,
}
impl Constraint for Default {}
pub struct ForeignKey {
pub table: &'static str,
pub column: &'static str,
}
impl Constraint for ForeignKey {}
pub struct PrimaryKey;
impl Constraint for PrimaryKey {}
pub struct Unique;
impl Constraint for Unique {}
// Transition trait
pub trait Transition<Constraint> {
type NextState;
}
impl Transition<ForeignKey> for NoState {
type NextState = ForeignKeyState;
}
impl Transition<ForeignKey> for DefaultState {
type NextState = InvalidState;
}
impl Transition<ForeignKey> for PrimaryKeyState {
type NextState = InvalidState;
}
impl Transition<PrimaryKey> for ForeignKeyState {
type NextState = InvalidState;
}
impl Transition<PrimaryKey> for NoState {
type NextState = PrimaryKeyState;
}
impl Transition<Unique> for NoState {
type NextState = DefaultState;
}
impl Transition<AutoIncrement> for NoState {
type NextState = DefaultState;
}
impl Transition<Default> for NoState {
type NextState = DefaultState;
}
impl Transition<Default> for DefaultState {
type NextState = DefaultState;
}
impl Transition<Unique> for DefaultState {
type NextState = DefaultState;
}
impl Transition<AutoIncrement> for DefaultState {
type NextState = DefaultState;
}
#[derive(Eq, PartialEq, Hash)]
pub enum ConstraintType {
Unique,
PrimaryKey,
AutoIncrement,
Default(&'static str),
ForeignKey(&'static str, &'static str),
}
impl From<Unique> for ConstraintType {
fn from(_: Unique) -> Self {
ConstraintType::Unique
}
}
impl From<PrimaryKey> for ConstraintType {
fn from(_: PrimaryKey) -> Self {
ConstraintType::PrimaryKey
}
}
impl From<AutoIncrement> for ConstraintType {
fn from(_: AutoIncrement) -> Self {
ConstraintType::AutoIncrement
}
}
impl From<Default> for ConstraintType {
fn from(constraint: Default) -> Self {
ConstraintType::Default(constraint.value)
}
}
impl From<ForeignKey> for ConstraintType {
fn from(constraint: ForeignKey) -> Self {
ConstraintType::ForeignKey(constraint.table, constraint.column)
}
}
// Constraints struct
pub struct Constraints<State: KeyState = NoState> {
constraints: Set<ConstraintType>,
_state: PhantomData<State>,
}
impl Constraints {
pub fn new() -> Constraints {
Constraints {
constraints: Set::new(),
_state: PhantomData,
}
}
}
impl<State: KeyState> Constraints<State> {
pub fn add_constraint<C>(
mut self,
constraint: C,
) -> Constraints<<State as Transition<C>>::NextState>
where
State: Transition<C>,
<State as Transition<C>>::NextState: KeyState + UsableState,
C: Constraint + Into<ConstraintType>,
{
self.constraints.insert(constraint.into());
Constraints {
constraints: self.constraints,
_state: PhantomData,
}
}
}
///
/// ```
/// use money::constraints::*;
/// let constraints = Constraints::default().add_constraint(Unique);
/// ```
/// ```
/// use money::constraints::*;
/// let constraints = Constraints::default()
/// .add_constraint(Unique)
/// .add_constraint(Default { value: "1" })
/// .add_constraint(AutoIncrement);
/// ```
/// ```compile_fail
/// use money::constraints::*;
/// let constraints = Constraints::default().add_constraint(ConstraintType::Unique).add_constraint(ConstraintType::ForeignKey);
/// ```
/// ```compile_fail
/// use money::constraints::*;
/// let constraints = Constraints::default().add_constraint(Unique).add_constraint(PrimaryKey);
/// ```
///
/// ```compile_fail
/// use money::constraints::*;
/// let constraints = Constraints::default().add_constraint(ForeignKey).add_constraint(PrimaryKey);
/// ```
///
/// ```compile_fail
/// use money::constraints::*;
/// let constraints = Constraints::default().add_constraint(PrimaryKey).add_constraint(ForeignKey);
/// ```
///
impl Constraints {
pub fn default() -> Constraints<NoState> {
Constraints::new()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment