use corium_core::{Attribute, Cardinality, EntityId, Partition, Schema, Unique, ValueType};
use corium_db::Idents;
use corium_query::edn::Edn;
use thiserror::Error;
pub const FIRST_ATTR_ID: u64 = 100;
#[derive(Debug, Error, Eq, PartialEq)]
pub enum SchemaFormError {
#[error("attribute definition must be a map: {0}")]
NotAMap(String),
#[error("attribute requires a :db/ident keyword")]
MissingIdent,
#[error("unknown or missing :db/valueType {0}")]
BadValueType(String),
#[error("unknown :db/cardinality {0}")]
BadCardinality(String),
#[error("unknown :db/unique {0}")]
BadUnique(String),
#[error("duplicate :db/ident {0}")]
DuplicateIdent(String),
}
fn kw(text: &str) -> Edn {
Edn::keyword(text)
}
pub fn schema_from_edn(forms: &[Edn]) -> Result<(Schema, Idents), SchemaFormError> {
let mut schema = Schema::default();
let mut idents = Idents::default();
for (index, form) in forms.iter().enumerate() {
if !matches!(form, Edn::Map(_)) {
return Err(SchemaFormError::NotAMap(form.to_string()));
}
let id = EntityId::new(Partition::Db as u32, FIRST_ATTR_ID + index as u64);
let ident = form
.get(&kw("db/ident"))
.and_then(Edn::as_keyword)
.ok_or(SchemaFormError::MissingIdent)?
.clone();
if idents.entid(&ident).is_some() {
return Err(SchemaFormError::DuplicateIdent(ident.to_string()));
}
let value_type_name = form
.get(&kw("db/valueType"))
.and_then(Edn::as_keyword)
.map(|keyword| keyword.name.clone())
.ok_or_else(|| SchemaFormError::BadValueType("<missing>".into()))?;
let value_type = match value_type_name.as_str() {
"string" => ValueType::Str,
"long" => ValueType::Long,
"double" => ValueType::Double,
"boolean" => ValueType::Bool,
"instant" => ValueType::Instant,
"uuid" => ValueType::Uuid,
"keyword" => ValueType::Keyword,
"bytes" => ValueType::Bytes,
"ref" => ValueType::Ref,
other => return Err(SchemaFormError::BadValueType(other.to_owned())),
};
let cardinality = match form
.get(&kw("db/cardinality"))
.and_then(Edn::as_keyword)
.map(|keyword| keyword.name.clone())
{
None => Cardinality::One,
Some(name) => match name.as_str() {
"one" => Cardinality::One,
"many" => Cardinality::Many,
other => return Err(SchemaFormError::BadCardinality(other.to_owned())),
},
};
let unique = match form
.get(&kw("db/unique"))
.and_then(Edn::as_keyword)
.map(|keyword| keyword.name.clone())
{
None => None,
Some(name) => match name.as_str() {
"identity" => Some(Unique::Identity),
"value" => Some(Unique::Value),
other => return Err(SchemaFormError::BadUnique(other.to_owned())),
},
};
let flag = |name: &str| form.get(&kw(name)) == Some(&Edn::Bool(true));
schema.insert(Attribute {
id,
value_type,
cardinality,
unique,
is_component: flag("db/isComponent"),
indexed: flag("db/index") || unique.is_some(),
no_history: flag("db/noHistory"),
});
idents.insert(ident, id);
}
Ok((schema, idents))
}