#[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 target_profiles: &'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 ChoiceForm {
pub suffix: &'static str,
pub code: &'static str,
pub kind: Kind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Member {
pub field: &'static FieldSchema,
pub kind: Kind,
pub choice: Option<&'static str>,
pub sibling: bool,
}
impl FieldSchema {
pub fn choices(&self) -> impl Iterator<Item = ChoiceForm> + '_ {
let variants: &'static [(&'static str, Kind)] = match self.kind {
Kind::Choice(variants) => variants,
_ => &[],
};
variants.iter().filter_map(|(suffix, kind)| {
self.choice_type(suffix).map(|code| ChoiceForm {
suffix,
code,
kind: *kind,
})
})
}
#[must_use]
pub fn choice_type(&self, suffix: &str) -> Option<&'static str> {
self.types
.iter()
.copied()
.find(|code| is_choice_suffix(code, suffix))
}
}
fn is_choice_suffix(code: &str, suffix: &str) -> bool {
let mut code_chars = code.chars();
let mut suffix_chars = suffix.chars();
match (code_chars.next(), suffix_chars.next()) {
(Some(first), Some(upper)) => {
first.to_ascii_uppercase() == upper && code_chars.as_str() == suffix_chars.as_str()
}
_ => false,
}
}
#[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 PrimitiveSchema {
pub name: &'static str,
pub base: Option<&'static str>,
}
impl TypeSchema {
#[must_use]
pub fn member(&self, key: &str) -> Option<Member> {
let (name, sibling) = match key.strip_prefix('_') {
Some(name) => (name, true),
None => (key, false),
};
let member = self.fields.iter().find_map(|field| match field.kind {
Kind::Choice(_) => {
let suffix = name.strip_prefix(field.name)?;
field
.choices()
.find(|form| form.suffix == suffix)
.map(|form| Member {
field,
kind: form.kind,
choice: Some(form.code),
sibling,
})
}
kind => (field.name == name).then_some(Member {
field,
kind,
choice: None,
sibling,
}),
})?;
(!sibling || matches!(member.kind, Kind::Primitive(_))).then_some(member)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Schemas {
pub types: &'static [TypeSchema],
pub resources: &'static [&'static str],
pub primitives: &'static [PrimitiveSchema],
}
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 primitive_base_of(&self, name: &str) -> Option<&'static str> {
self.primitives
.binary_search_by(|p| p.name.cmp(name))
.ok()
.and_then(|i| self.primitives.get(i))
.and_then(|primitive| primitive.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
.member(segment)
.filter(|member| !member.sibling)
.map(|member| (member.field, member.kind))
}