Skip to main content

corium_protocol/
schemaform.rs

1//! Boundary conversion from Datomic-style EDN attribute maps to the engine
2//! schema model, used by `CreateDatabase`.
3
4use corium_core::{Attribute, Cardinality, EntityId, Partition, Schema, Unique, ValueType};
5use corium_db::Idents;
6use corium_query::edn::Edn;
7use thiserror::Error;
8
9/// Sequence of the first installable attribute entity in the db partition.
10pub const FIRST_ATTR_ID: u64 = 100;
11
12/// Schema form conversion failure.
13#[derive(Debug, Error, Eq, PartialEq)]
14pub enum SchemaFormError {
15    /// Attribute definition is not a map.
16    #[error("attribute definition must be a map: {0}")]
17    NotAMap(String),
18    /// `:db/ident` missing or not a keyword.
19    #[error("attribute requires a :db/ident keyword")]
20    MissingIdent,
21    /// `:db/valueType` missing or unknown.
22    #[error("unknown or missing :db/valueType {0}")]
23    BadValueType(String),
24    /// `:db/cardinality` unknown.
25    #[error("unknown :db/cardinality {0}")]
26    BadCardinality(String),
27    /// `:db/unique` unknown.
28    #[error("unknown :db/unique {0}")]
29    BadUnique(String),
30    /// Two attributes share one ident.
31    #[error("duplicate :db/ident {0}")]
32    DuplicateIdent(String),
33}
34
35fn kw(text: &str) -> Edn {
36    Edn::keyword(text)
37}
38
39/// Parses Datomic-style attribute maps into a schema and ident registry.
40///
41/// Attribute entity ids are assigned sequentially in the `Db` partition
42/// starting at [`FIRST_ATTR_ID`].
43///
44/// # Errors
45/// Returns [`SchemaFormError`] for malformed attribute definitions.
46pub fn schema_from_edn(forms: &[Edn]) -> Result<(Schema, Idents), SchemaFormError> {
47    let mut schema = Schema::default();
48    let mut idents = Idents::default();
49    for (index, form) in forms.iter().enumerate() {
50        if !matches!(form, Edn::Map(_)) {
51            return Err(SchemaFormError::NotAMap(form.to_string()));
52        }
53        let id = EntityId::new(Partition::Db as u32, FIRST_ATTR_ID + index as u64);
54        let ident = form
55            .get(&kw("db/ident"))
56            .and_then(Edn::as_keyword)
57            .ok_or(SchemaFormError::MissingIdent)?
58            .clone();
59        if idents.entid(&ident).is_some() {
60            return Err(SchemaFormError::DuplicateIdent(ident.to_string()));
61        }
62        let value_type_name = form
63            .get(&kw("db/valueType"))
64            .and_then(Edn::as_keyword)
65            .map(|keyword| keyword.name.clone())
66            .ok_or_else(|| SchemaFormError::BadValueType("<missing>".into()))?;
67        let value_type = match value_type_name.as_str() {
68            "string" => ValueType::Str,
69            "long" => ValueType::Long,
70            "double" => ValueType::Double,
71            "boolean" => ValueType::Bool,
72            "instant" => ValueType::Instant,
73            "uuid" => ValueType::Uuid,
74            "keyword" => ValueType::Keyword,
75            "bytes" => ValueType::Bytes,
76            "ref" => ValueType::Ref,
77            other => return Err(SchemaFormError::BadValueType(other.to_owned())),
78        };
79        let cardinality = match form
80            .get(&kw("db/cardinality"))
81            .and_then(Edn::as_keyword)
82            .map(|keyword| keyword.name.clone())
83        {
84            None => Cardinality::One,
85            Some(name) => match name.as_str() {
86                "one" => Cardinality::One,
87                "many" => Cardinality::Many,
88                other => return Err(SchemaFormError::BadCardinality(other.to_owned())),
89            },
90        };
91        let unique = match form
92            .get(&kw("db/unique"))
93            .and_then(Edn::as_keyword)
94            .map(|keyword| keyword.name.clone())
95        {
96            None => None,
97            Some(name) => match name.as_str() {
98                "identity" => Some(Unique::Identity),
99                "value" => Some(Unique::Value),
100                other => return Err(SchemaFormError::BadUnique(other.to_owned())),
101            },
102        };
103        let flag = |name: &str| form.get(&kw(name)) == Some(&Edn::Bool(true));
104        schema.insert(Attribute {
105            id,
106            value_type,
107            cardinality,
108            unique,
109            is_component: flag("db/isComponent"),
110            indexed: flag("db/index") || unique.is_some(),
111            no_history: flag("db/noHistory"),
112        });
113        idents.insert(ident, id);
114    }
115    Ok((schema, idents))
116}