corium_protocol/
schemaform.rs1use corium_core::{Attribute, Cardinality, EntityId, Partition, Schema, Unique, ValueType};
5use corium_db::Idents;
6use corium_query::edn::Edn;
7use thiserror::Error;
8
9pub const FIRST_ATTR_ID: u64 = 100;
11
12#[derive(Debug, Error, Eq, PartialEq)]
14pub enum SchemaFormError {
15 #[error("attribute definition must be a map: {0}")]
17 NotAMap(String),
18 #[error("attribute requires a :db/ident keyword")]
20 MissingIdent,
21 #[error("unknown or missing :db/valueType {0}")]
23 BadValueType(String),
24 #[error("unknown :db/cardinality {0}")]
26 BadCardinality(String),
27 #[error("unknown :db/unique {0}")]
29 BadUnique(String),
30 #[error("duplicate :db/ident {0}")]
32 DuplicateIdent(String),
33}
34
35fn kw(text: &str) -> Edn {
36 Edn::keyword(text)
37}
38
39pub 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}