microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
use std::collections::{HashMap, HashSet};

use crate::schema::{
    datum::{Datum, DatumDiscriminator},
    entity::{Entity, EntityPart, EntityPartList},
    DatabaseItemVisitor, Schema,
};

mod detail;
pub(crate) use detail::extract_signature_for;

#[derive(
    Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Debug, serde::Serialize, serde::Deserialize,
)]
pub enum PoDType {
    Unit,
    Bool,
    U8,
    U16,
    U32,
    U64,
    U128,
    I8,
    I16,
    I32,
    I64,
    I128,
    F32,
    F64,
    Char,
    String,
    Bytes,
}

impl std::fmt::Display for PoDType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Unit => "unit",
            Self::Bool => "bool",
            Self::U8 => "u8",
            Self::U16 => "u16",
            Self::U32 => "u32",
            Self::U64 => "u64",
            Self::U128 => "u128",
            Self::I8 => "i8",
            Self::I16 => "i16",
            Self::I32 => "i32",
            Self::I64 => "i64",
            Self::I128 => "i128",
            Self::F32 => "f32",
            Self::F64 => "f64",
            Self::Char => "char",
            Self::String => "string",
            Self::Bytes => "bytes",
        })
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
pub enum TypeSignature {
    PoD(PoDType),
    Option(Box<Self>),
    Enum(Vec<(String, Self)>),
    Struct(Vec<(String, Self)>),
    Tuple(Vec<Self>),
    List(Box<Self>),
    EntityID(String),
    SqlField(String),
}

impl TypeSignature {
    fn normalize(&mut self) {
        match self {
            Self::PoD(_) | Self::EntityID(_) | Self::SqlField(_) => (),
            Self::Option(ts) => ts.normalize(),
            Self::Enum(fs) | Self::Struct(fs) => {
                fs.sort_by_cached_key(|v| v.0.clone());
                for f in fs.iter_mut() {
                    f.1.normalize();
                }
            },
            Self::Tuple(ts) => {
                for t in ts.iter_mut() {
                    t.normalize();
                }
            },
            Self::List(ts) => ts.normalize(),
        }
    }
}

// this is used instead of Debug because we need a stable format
impl std::fmt::Display for TypeSignature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::PoD(pod) => pod.fmt(f),
            Self::Option(ts) => {
                f.write_str("Opt<")?;
                ts.fmt(f)?;
                f.write_str(">")
            },
            Self::Enum(fs) => {
                f.write_str("Enum[")?;
                for (n, ts) in fs {
                    f.write_str(n)?;
                    f.write_str(": ")?;
                    ts.fmt(f)?;
                    f.write_str(",")?;
                }
                f.write_str("]")
            },
            Self::Struct(fs) => {
                f.write_str("Struct[")?;
                for (n, ts) in fs {
                    f.write_str(n)?;
                    f.write_str(": ")?;
                    ts.fmt(f)?;
                    f.write_str(",")?;
                }
                f.write_str("]")
            },
            Self::Tuple(ts) => {
                f.write_str("(")?;
                for t in ts {
                    t.fmt(f)?;
                    f.write_str(",")?;
                }
                f.write_str(")")
            },
            Self::List(ts) => {
                f.write_str("[")?;
                ts.fmt(f)?;
                f.write_str("]")
            },
            Self::EntityID(ename) => {
                f.write_str("ID[")?;
                f.write_str(ename.as_str())?;
                f.write_str("]")
            },
            Self::SqlField(ty) => {
                f.write_str("Sql[")?;
                f.write_str(ty.as_str())?;
                f.write_str("]")
            },
        }
    }
}

struct Collector {
    encountered: HashSet<std::any::TypeId>,
    collected: HashMap<String, TypeSignature>,
    active_entity: Vec<&'static str>,
    active_field: Vec<&'static str>,
}

impl Collector {
    fn iterate_part_list<EPL: EntityPartList>(&mut self) {
        if EPL::IS_EMPTY {
            return;
        }

        self.active_field
            .push(<EPL::ListHead as EntityPart>::part_name());
        <<EPL::ListHead as EntityPart>::Datum as Datum>::accept_discriminator(self);
        self.active_field.pop();
        self.iterate_part_list::<EPL::ListTail>();
    }

    fn active_key(&self) -> String {
        format!(
            "{}.{}",
            self.active_entity.last().expect("no active entity"),
            self.active_field.last().expect("no active field")
        )
    }
}

impl DatumDiscriminator for Collector {
    fn visit_entity_id<E: Entity>(&mut self) {
        self.collected.insert(
            self.active_key(),
            TypeSignature::EntityID(E::entity_name().into()),
        );
    }
    fn visit_bare_field<T: Datum>(&mut self) {
        self.collected.insert(
            self.active_key(),
            TypeSignature::SqlField(T::sql_type().into()),
        );
    }
    fn visit_relation_map<E: Entity>(&mut self) {
        self.visit_idmap::<E>();
    }
    fn visit_relation_range<R: crate::Relation>(&mut self) {
        self.visit_idmap::<R::Range>();
    }
    fn visit_relation_domain<R: crate::Relation>(&mut self) {
        self.visit_idmap::<R::Domain>();
    }
    fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self) {
        self.visit_value::<T>();
    }
    fn visit_value<T: serde::de::DeserializeOwned>(&mut self) {
        log::trace!(
            "collecting type signature for {}",
            std::any::type_name::<T>()
        );

        self.collected
            .insert(self.active_key(), extract_signature_for::<T>());
    }
}

impl DatabaseItemVisitor for Collector {
    fn visit_idmap<T: Entity>(&mut self)
    where
        Self: Sized,
    {
        let tid = std::any::TypeId::of::<T>();
        if !self.encountered.contains(&tid) {
            self.encountered.insert(tid);

            log::trace!("starting processing of entity {}", T::entity_name());
            self.active_entity.push(T::entity_name());
            self.iterate_part_list::<T::Parts>();
            self.active_entity.pop();
            log::trace!("stopping processing of entity {}", T::entity_name());
        }
    }

    fn visit_index<const U: bool, T: Entity, PL: EntityPartList<Entity = T>>(&mut self)
    where
        Self: Sized,
    {
    }
}

pub const TYPE_SIGNATURES_KEY: &str = "type_signatures";

pub type SignatureMap = HashMap<String, TypeSignature>;
pub type BorrowedSignatureMap<'l> = HashMap<&'l str, TypeSignature>;
pub fn collect_type_info<S: Schema>() -> HashMap<String, TypeSignature> {
    log::trace!("collecting type info...");
    let mut c = Collector {
        encountered: Default::default(),
        collected: Default::default(),
        active_field: vec![],
        active_entity: vec![],
    };

    S::accept_item_visitor(&mut c);

    c.collected
}

// ----------------------------------------------------------------------
// serde deserialization hacks below this line
// ----------------------------------------------------------------------

#[derive(Debug)]
enum CollectorError {
    // Collected(std::any::TypeId),
    TypeFeedback(TypeSignature),
    VariantFeedback(&'static str, TypeSignature),
    FieldFeedback(&'static str, TypeSignature),
    InProgress,
}

impl serde::de::Error for CollectorError {
    fn custom<T>(_msg: T) -> Self
    where
        T: std::fmt::Display,
    {
        unreachable!()
    }
}

impl serde::de::StdError for CollectorError {}

impl std::fmt::Display for CollectorError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        <Self as std::fmt::Debug>::fmt(self, f)
    }
}