use std::collections::{BTreeSet, HashSet};
use super::SchemaGraph;
use crate::express::{Attribute, EntityDef};
#[derive(Debug, Clone)]
pub struct ComplexLayout<'s> {
parts: Vec<ComplexPart<'s>>,
types: Vec<&'s str>,
issues: Vec<ComplexIssue>,
}
impl<'s> ComplexLayout<'s> {
#[must_use]
pub fn parts(&self) -> &[ComplexPart<'s>] {
&self.parts
}
#[must_use]
pub fn types(&self) -> &[&'s str] {
&self.types
}
#[must_use]
pub fn issues(&self) -> &[ComplexIssue] {
&self.issues
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.issues.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ComplexPart<'s> {
name: String,
entity: Option<&'s EntityDef>,
slots: Vec<ComplexSlot<'s>>,
}
impl<'s> ComplexPart<'s> {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn entity(&self) -> Option<&'s EntityDef> {
self.entity
}
#[must_use]
pub fn slots(&self) -> &[ComplexSlot<'s>] {
&self.slots
}
}
#[derive(Debug, Clone, Copy)]
pub struct ComplexSlot<'s> {
attribute: &'s Attribute,
derived: bool,
}
impl<'s> ComplexSlot<'s> {
#[must_use]
pub fn attribute(&self) -> &'s Attribute {
self.attribute
}
#[must_use]
pub fn is_derived(&self) -> bool {
self.derived
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ComplexIssue {
UnknownType {
index: usize,
},
OutOfOrder {
index: usize,
},
Duplicate {
index: usize,
},
MissingSupertype {
name: String,
},
}
impl SchemaGraph {
#[must_use]
pub fn resolve_complex(&self, names: &[&str]) -> ComplexLayout<'_> {
let mut issues = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
let mut previous: Option<String> = None;
let entities: Vec<Option<&EntityDef>> = names
.iter()
.enumerate()
.map(|(index, name)| {
let upper = name.to_ascii_uppercase();
let entity = self.entity(name);
if entity.is_none() {
issues.push(ComplexIssue::UnknownType { index });
}
if !seen.insert(upper.clone()) {
issues.push(ComplexIssue::Duplicate { index });
} else if previous.as_ref().is_some_and(|prev| upper <= *prev) {
issues.push(ComplexIssue::OutOfOrder { index });
}
previous = Some(upper);
entity
})
.collect();
let mut types: BTreeSet<&str> = BTreeSet::new();
for entity in entities.iter().flatten() {
types.insert(entity.name.as_str());
types.extend(self.supertypes(&entity.name));
}
let mut missing: Vec<&str> = types
.iter()
.copied()
.filter(|name| !seen.contains(&name.to_ascii_uppercase()))
.collect();
missing.sort_unstable_by_key(|name| name.to_ascii_uppercase());
issues.extend(
missing
.into_iter()
.map(|name| ComplexIssue::MissingSupertype {
name: name.to_owned(),
}),
);
let derived: HashSet<(String, String)> = types
.iter()
.filter_map(|name| self.entity(name))
.flat_map(|entity| self.derivations(entity))
.collect();
let parts = names
.iter()
.zip(entities)
.map(|(name, entity)| ComplexPart {
name: (*name).to_owned(),
entity,
slots: entity.map_or_else(Vec::new, |entity| {
let owner = entity.name.to_ascii_uppercase();
entity
.attributes
.iter()
.map(|attribute| ComplexSlot {
attribute,
derived: derived
.contains(&(owner.clone(), attribute.name.to_ascii_uppercase())),
})
.collect()
}),
})
.collect();
ComplexLayout {
parts,
types: types.into_iter().collect(),
issues,
}
}
fn derivations(&self, entity: &EntityDef) -> Vec<(String, String)> {
entity
.derived
.iter()
.filter_map(|name| {
self.supertypes(&entity.name)
.into_iter()
.filter_map(|ancestor| self.entity(ancestor))
.find(|ancestor| {
ancestor
.attributes
.iter()
.any(|attribute| attribute.name.eq_ignore_ascii_case(name))
})
.map(|ancestor| {
(
ancestor.name.to_ascii_uppercase(),
name.to_ascii_uppercase(),
)
})
})
.collect()
}
}