#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Boolean,
Integer,
Decimal,
Text,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Attribute,
Primitive(ValueKind),
Complex(&'static str),
Resource,
Xhtml,
Choice(&'static [(&'static str, Kind)]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldSchema {
pub name: &'static str,
pub path: &'static str,
pub kind: Kind,
pub min: u32,
pub max: Option<u32>,
pub many: bool,
pub types: &'static [&'static str],
pub content_reference: Option<&'static str>,
pub is_summary: bool,
pub is_modifier: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeSchema {
pub name: &'static str,
pub path: &'static str,
pub fields: &'static [FieldSchema],
pub base: Option<&'static str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Schemas {
pub types: &'static [TypeSchema],
pub resources: &'static [&'static str],
}
impl Schemas {
#[must_use]
pub fn type_named(&self, name: &str) -> Option<&'static TypeSchema> {
self.types
.binary_search_by(|t| t.name.cmp(name))
.ok()
.and_then(|i| self.types.get(i))
}
#[must_use]
pub fn base_of(&self, name: &str) -> Option<&'static str> {
self.type_named(name).and_then(|schema| schema.base)
}
#[must_use]
pub fn specializes(&self, name: &str, ancestor: &str) -> bool {
if name == ancestor {
return true;
}
let mut current = self.base_of(name);
for _ in 0..self.types.len() {
match current {
Some(base) if base == ancestor => return true,
Some(base) => current = self.base_of(base),
None => return false,
}
}
false
}
#[must_use]
pub fn element(&self, path: &str) -> Option<(&'static TypeSchema, &'static FieldSchema)> {
let (schema, field, _) = self.resolve(path)?;
Some((schema, field))
}
#[must_use]
pub fn type_of(&self, path: &str) -> Option<&'static TypeSchema> {
let Some((_, _, kind)) = self.resolve(path) else {
return self.type_named(path).filter(|schema| schema.path == path);
};
match kind {
Kind::Complex(name) => self.type_named(name),
_ => None,
}
}
fn resolve(&self, path: &str) -> Option<(&'static TypeSchema, &'static FieldSchema, Kind)> {
let (root, rest) = path.split_once('.')?;
let mut schema = self.type_named(root).filter(|found| found.path == root)?;
let mut segments = rest.split('.').peekable();
while let Some(segment) = segments.next() {
let (field, kind) = field_named(schema, segment)?;
if segments.peek().is_none() {
return Some((schema, field, kind));
}
let Kind::Complex(name) = kind else {
return None;
};
schema = self.type_named(name)?;
}
None
}
#[must_use]
pub fn is_resource(&self, name: &str) -> bool {
self.resources.binary_search(&name).is_ok()
}
}
fn field_named(schema: &'static TypeSchema, segment: &str) -> Option<(&'static FieldSchema, Kind)> {
let named = schema
.fields
.iter()
.find(|field| field.name == segment || segment.strip_suffix("[x]") == Some(field.name));
if let Some(field) = named {
return Some((field, field.kind));
}
schema.fields.iter().find_map(|field| {
let Kind::Choice(variants) = field.kind else {
return None;
};
let suffix = segment.strip_prefix(field.name)?;
variants
.iter()
.find(|(candidate, _)| *candidate == suffix)
.map(|(_, kind)| (field, *kind))
})
}