Last active
January 19, 2025 15:15
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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