use std::collections::HashMap;
use quick_xml::events::BytesStart;
use crate::error::{Error, Result};
use crate::identifiers::QualifiedType;
use crate::identity::{ObjectKey, parse_content_type};
use crate::naming;
use crate::reflect::TypeInfo;
use crate::relations::{RelationsIndex, TypeRelations};
use crate::validate::Validate;
use crate::value::Reflect;
use crate::xml::{XmlDeserialize, XmlReader, XmlSerialize};
pub trait DynObject: Reflect + XmlSerialize + Validate + Send + Sync {
fn as_reflect(&self) -> &dyn Reflect;
fn as_reflect_mut(&mut self) -> &mut dyn Reflect;
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect>;
fn clone_box(&self) -> Box<dyn DynObject>;
fn as_xml(&self) -> &dyn XmlSerialize;
fn as_validate(&self) -> &dyn Validate;
}
impl<T: Reflect + XmlSerialize + Validate + Clone + Send + Sync> DynObject for T {
fn as_reflect(&self) -> &dyn Reflect {
self
}
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
self
}
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
self
}
fn clone_box(&self) -> Box<dyn DynObject> {
Box::new(self.clone())
}
fn as_xml(&self) -> &dyn XmlSerialize {
self
}
fn as_validate(&self) -> &dyn Validate {
self
}
}
#[derive(Clone, Copy)]
pub struct TypeEntry {
pub info: &'static TypeInfo,
pub make_default: fn() -> Box<dyn DynObject>,
pub read_xml: fn(&mut XmlReader<'_>, &BytesStart<'_>) -> Result<Box<dyn DynObject>>,
}
impl TypeEntry {
pub fn of<T>() -> TypeEntry
where
T: DynObject + XmlDeserialize + Default + crate::reflect::EnergymlType + 'static,
{
TypeEntry {
info: <T as crate::reflect::EnergymlType>::type_info(),
make_default: || Box::new(T::default()),
read_xml: |r, start| Ok(Box::new(T::xml_read(r, start)?)),
}
}
}
#[derive(Default)]
pub struct Registry {
entries: Vec<TypeEntry>,
by_qualified_type: HashMap<String, usize>,
by_content_type: HashMap<String, usize>,
by_package_version_name: HashMap<(String, String, String), usize>,
by_type_key: HashMap<String, Vec<usize>>,
absent: Vec<AbsentVersion>,
relations: RelationsIndex,
}
struct AbsentVersion {
package: String,
compact: Vec<String>,
prerelease: Option<String>,
why: String,
}
impl AbsentVersion {
fn names(&self, package: &str, version: &str) -> bool {
if !self.package.eq_ignore_ascii_case(package.trim()) {
return false;
}
let (release, pre) = naming::split_prerelease(version);
let same_drop = match (pre, self.prerelease.as_deref()) {
(None, None) => true,
(Some(a), Some(b)) => naming::prerelease_eq(a, b),
_ => false,
};
same_drop
&& self
.compact
.iter()
.any(|c| *c == naming::compact_version(release))
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ReadMode {
Strict,
#[default]
Tolerant,
Lenient,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Departures {
pub defaulted: Vec<crate::xml::Defaulted>,
pub repaired: Vec<crate::xml::Repaired>,
}
impl Departures {
pub fn is_empty(&self) -> bool {
self.defaulted.is_empty() && self.repaired.is_empty()
}
pub fn lines(&self) -> Vec<String> {
self.defaulted
.iter()
.map(ToString::to_string)
.chain(self.repaired.iter().map(ToString::to_string))
.collect()
}
}
fn claim(map: &mut HashMap<String, usize>, key: String, idx: usize, entries: &[TypeEntry], is_drop: bool) {
if is_drop
&& let Some(held) = map.get(&key)
&& !naming::is_prerelease(entries[*held].info.version)
{
return;
}
map.insert(key, idx);
}
fn read_entry(
registry: &Registry,
entry: &TypeEntry,
bytes: &[u8],
mode: ReadMode,
) -> Result<(Box<dyn DynObject>, Departures)> {
let mut cursor = bytes;
let mut reader = XmlReader::new(&mut cursor);
reader = match mode {
ReadMode::Strict => reader.strict(),
ReadMode::Tolerant => reader,
ReadMode::Lenient => reader.lenient(),
};
let root = reader.read_root()?;
let entry = registry.substitute(entry, &root)?;
let object = (entry.read_xml)(&mut reader, &root)?;
Ok((
object,
Departures {
defaulted: reader.take_defaulted(),
repaired: reader.take_repaired(),
},
))
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, entry: TypeEntry) {
let idx = self.entries.len();
let info = entry.info;
self.entries.push(entry);
let drop = naming::is_prerelease(info.version);
claim(
&mut self.by_qualified_type,
normalize_qualified_type(&computed_qualified_type(info)),
idx,
&self.entries,
drop,
);
if let Some(qt) = info.qualified_type {
claim(
&mut self.by_qualified_type,
normalize_qualified_type(qt),
idx,
&self.entries,
drop,
);
}
if let Some(ct) = info.content_type {
claim(
&mut self.by_content_type,
normalize_content_type(ct),
idx,
&self.entries,
drop,
);
}
if naming::compact_version(info.version) != naming::compact_version(info.schema_version) {
claim(
&mut self.by_qualified_type,
normalize_qualified_type(&format!(
"{}{}.{}",
info.package,
naming::compact_version(info.version),
info.object_type
)),
idx,
&self.entries,
drop,
);
if let Some(ct) = info.content_type
&& let Ok(parts) = parse_content_type(ct)
{
claim(
&mut self.by_content_type,
normalize_content_type(&format!(
"application/x-{}+xml;version={};type={}",
parts.domain, info.version, parts.object_type
)),
idx,
&self.entries,
drop,
);
}
}
self.by_package_version_name.insert(
(
info.package.to_ascii_lowercase(),
info.version.to_string(),
info.name.to_ascii_lowercase(),
),
idx,
);
self.by_type_key
.entry(info.type_key.to_ascii_lowercase())
.or_default()
.push(idx);
}
pub fn types_by_key(&self, type_key: &str) -> Vec<&'static TypeInfo> {
let key = naming::strip_obj_prefix(type_key.trim()).to_ascii_lowercase();
self.by_type_key
.get(&key)
.map(|idx| idx.iter().map(|i| self.entries[*i].info).collect())
.unwrap_or_default()
}
pub fn find_type_loose(&self, name: &str) -> Vec<&'static TypeInfo> {
self.types_by_key(name)
}
pub fn make_object_ref(
&self,
package: &str,
version: &str,
key: &ObjectKey<'_>,
) -> Result<Box<dyn DynObject>> {
let entry = self
.object_ref_type(package, version)
.ok_or_else(|| Error::UnknownType(format!("{package}/{version} DataObjectReference")))?;
let mut object = (entry.make_default)();
crate::identity::set_target_reflect(object.as_reflect_mut(), key)?;
Ok(object)
}
pub fn object_ref_type(&self, package: &str, version: &str) -> Option<&TypeEntry> {
self.entries.iter().find(|e| {
e.info.is_object_ref
&& e.info.package.eq_ignore_ascii_case(package)
&& naming::version_eq(e.info.version, version)
})
}
pub fn types(&self) -> impl Iterator<Item = &'static TypeInfo> + '_ {
self.entries.iter().map(|e| e.info)
}
pub fn top_level_types(&self) -> impl Iterator<Item = &'static TypeInfo> + '_ {
self.entries.iter().map(|e| e.info).filter(|i| i.is_top_level)
}
pub fn register_relations(&mut self, tables: &'static [TypeRelations]) {
self.relations.add(tables);
}
pub fn relations_of(&self, info: &TypeInfo) -> Option<&'static TypeRelations> {
self.relations.of(info)
}
pub fn relations_for(&self, qualified_type: &str) -> Option<&'static TypeRelations> {
self.relations.for_qualified(qualified_type)
}
pub fn is_container(&self, qualified_type: &str) -> bool {
self.relations_for(qualified_type)
.is_some_and(|t| t.is_container())
}
pub fn may_be_contained(&self, qualified_type: &str) -> bool {
self.relations.may_be_contained(qualified_type)
}
pub fn relations(&self) -> impl Iterator<Item = &'static TypeRelations> + '_ {
self.relations.iter()
}
pub fn by_qualified_type(&self, qualified_type: &str) -> Option<&TypeEntry> {
self.by_qualified_type
.get(&normalize_qualified_type(qualified_type))
.map(|i| &self.entries[*i])
}
pub fn by_content_type(&self, content_type: &str) -> Option<&TypeEntry> {
self.by_content_type
.get(&normalize_content_type(content_type))
.map(|i| &self.entries[*i])
}
pub fn find_type(&self, name: &str) -> Option<&'static TypeInfo> {
if let Some(entry) = self.by_qualified_type(name) {
return Some(entry.info);
}
let name = name.trim();
if name.is_empty() {
return None;
}
if name.contains('.') {
let wanted = normalize_qualified_type(name);
self.walk(|info| normalize_qualified_type(&computed_qualified_type(info)) == wanted)
} else {
self.walk(|info| info.name.eq_ignore_ascii_case(name) || info.xml_name.eq_ignore_ascii_case(name))
}
}
fn walk(&self, wanted: impl Fn(&'static TypeInfo) -> bool) -> Option<&'static TypeInfo> {
let mut seen: Vec<&'static TypeInfo> = Vec::new();
let mut queue: Vec<&'static TypeInfo> = self.types().collect();
while let Some(info) = queue.pop() {
if seen.iter().any(|known| std::ptr::eq(*known, info)) {
continue;
}
seen.push(info);
if wanted(info) {
return Some(info);
}
for field in info.fields {
if let Some(inner) = field.ty.type_info() {
queue.push(inner);
}
}
for variant in info.variants {
if let Some(ty) = variant.ty {
queue.push(ty());
}
}
for subtype in info.subtypes {
queue.push(subtype());
}
if let Some(base) = info.base {
queue.push(base());
}
}
None
}
pub fn by_name(&self, package: &str, version: &str, name: &str) -> Option<&TypeEntry> {
self.by_package_version_name
.get(&(
package.to_ascii_lowercase(),
version.to_string(),
name.to_ascii_lowercase(),
))
.map(|i| &self.entries[*i])
}
fn substitute<'e>(&'e self, entry: &'e TypeEntry, root: &BytesStart<'_>) -> Result<&'e TypeEntry> {
let Some(named) = crate::xml::xsi_type(root)? else {
return Ok(entry);
};
let local = naming::strip_obj_prefix(crate::xml::local_part(&named));
let names = |info: &'static TypeInfo| {
[info.xml_name, info.name, info.object_type]
.iter()
.any(|name| naming::strip_obj_prefix(name).eq_ignore_ascii_case(local))
};
if names(entry.info) {
return Ok(entry);
}
let descends = |info: &'static TypeInfo| {
let mut at = info.base.map(|base| base());
while let Some(ancestor) = at {
if std::ptr::eq(ancestor, entry.info) {
return true;
}
at = ancestor.base.map(|base| base());
}
false
};
if let Some(found) = self.entries.iter().find(|candidate| {
candidate.info.package == entry.info.package
&& candidate.info.version == entry.info.version
&& names(candidate.info)
&& descends(candidate.info)
}) {
return Ok(found);
}
Err(Error::UnknownType(format!(
"the root says `xsi:type=\"{named}\"`, which is no subtype of `{}`",
entry.info.qualified_type.unwrap_or(entry.info.name)
)))
}
pub fn declare_absent(&mut self, package: &str, version: &str, compact: &[&str], why: impl Into<String>) {
let (release, prerelease) = naming::split_prerelease(version);
let mut spellings = vec![naming::compact_version(release)];
spellings.extend(compact.iter().map(|c| naming::compact_version(c)));
spellings.dedup();
self.absent.push(AbsentVersion {
package: package.trim().to_ascii_lowercase(),
compact: spellings,
prerelease: prerelease.map(str::to_string),
why: why.into(),
});
}
pub fn absent(&self, package: &str, version: &str) -> Option<&str> {
self.absent
.iter()
.find(|row| row.names(package, version))
.map(|row| row.why.as_str())
}
pub fn absent_qualified_type(&self, qualified_type: &str) -> Option<&str> {
let parts = crate::identity::parse_qualified_type(qualified_type).ok()?;
self.absent
.iter()
.find(|row| {
row.prerelease.is_none()
&& row.package.eq_ignore_ascii_case(parts.domain)
&& row.compact.iter().any(|c| c == parts.version)
})
.map(|row| row.why.as_str())
}
pub fn absent_content_type(&self, content_type: &str) -> Option<&str> {
let parts = parse_content_type(content_type).ok()?;
self.absent(parts.domain, parts.version)
}
fn missing_qualified_type(&self, qualified_type: &str) -> Error {
match self.absent_qualified_type(qualified_type) {
Some(why) => Error::VersionAbsent(why.to_string()),
None => Error::UnknownType(qualified_type.to_string()),
}
}
fn missing_content_type(&self, content_type: &str) -> Error {
match self.absent_content_type(content_type) {
Some(why) => Error::VersionAbsent(why.to_string()),
None => Error::UnknownType(content_type.to_string()),
}
}
pub fn create(&self, qualified_type: &str) -> Result<Box<dyn DynObject>> {
let entry = self
.by_qualified_type(qualified_type)
.ok_or_else(|| self.missing_qualified_type(qualified_type))?;
Ok((entry.make_default)())
}
pub fn read_xml(&self, content_type: &str, bytes: &[u8]) -> Result<Box<dyn DynObject>> {
Ok(self.read_xml_as(content_type, bytes, ReadMode::Tolerant)?.0)
}
pub fn read_xml_as(
&self,
content_type: &str,
bytes: &[u8],
mode: ReadMode,
) -> Result<(Box<dyn DynObject>, Departures)> {
let entry = self
.by_content_type(content_type)
.ok_or_else(|| self.missing_content_type(content_type))?;
read_entry(self, entry, bytes, mode)
}
pub fn read_xml_as_named(
&self,
info: &'static TypeInfo,
bytes: &[u8],
mode: ReadMode,
) -> Result<(Box<dyn DynObject>, Departures)> {
let entry = self
.by_name(info.package, info.version, info.name)
.ok_or_else(|| Error::UnknownType(format!("{} {} {}", info.package, info.version, info.name)))?;
read_entry(self, entry, bytes, mode)
}
pub fn read_xml_lenient(
&self,
content_type: &str,
bytes: &[u8],
) -> Result<(Box<dyn DynObject>, Vec<crate::xml::Defaulted>)> {
let (object, departures) = self.read_xml_repairing(content_type, bytes)?;
Ok((object, departures.defaulted))
}
pub fn read_xml_repairing(
&self,
content_type: &str,
bytes: &[u8],
) -> Result<(Box<dyn DynObject>, Departures)> {
self.read_xml_as(content_type, bytes, ReadMode::Lenient)
}
pub fn entry_of(&self, object: &dyn Reflect) -> Option<&TypeEntry> {
let info = object.type_info();
self.by_name(info.package, info.version, info.name)
}
#[cfg(feature = "json")]
pub fn write_json(&self, object: &dyn Reflect, pretty: bool) -> Result<String> {
crate::json::write_string(object, pretty)
}
#[cfg(feature = "json")]
pub fn read_json(&self, text: &str) -> Result<Box<dyn DynObject>> {
Ok(crate::json::read_string(text, self)?.root)
}
#[cfg(feature = "json")]
pub fn read_json_document(&self, text: &str) -> Result<crate::json::JsonDocument> {
crate::json::read_string(text, self)
}
}
pub fn to_part(object: &dyn DynObject, opts: crate::xml::XmlWriteOptions) -> Result<String> {
let info = object.as_reflect().type_info();
let namespace = crate::ns::Namespace::well_known(info.package)
.ok_or_else(|| Error::UnknownType(format!("no namespace for package `{}`", info.package)))?;
let mut declare = vec![namespace];
for extra in [crate::ns::EML, crate::ns::XSI] {
if !declare.iter().any(|n| n.uri == extra.uri) {
declare.push(extra);
}
}
match info.root_element {
Some(element) => {
let typed = Substituted {
inner: object.as_xml(),
xsi_type: format!("{}:{}", namespace.prefix, info.xml_name),
};
crate::xml::to_xml_string(&typed, namespace, element, &declare, opts)
}
None => crate::xml::to_xml_string(object.as_xml(), namespace, info.xml_name, &declare, opts),
}
}
struct Substituted<'a> {
inner: &'a dyn XmlSerialize,
xsi_type: String,
}
impl XmlSerialize for Substituted<'_> {
fn xml_attributes(&self, tag: &mut quick_xml::events::BytesStart<'_>) -> Result<()> {
crate::xml::push_xsi_type(tag, &self.xsi_type);
self.inner.xml_attributes(tag)
}
fn xml_children(&self, w: &mut crate::xml::XmlWriter<'_>) -> Result<()> {
self.inner.xml_children(w)
}
fn xml_text(&self) -> Option<std::borrow::Cow<'_, str>> {
self.inner.xml_text()
}
}
fn computed_qualified_type(info: &'static TypeInfo) -> String {
format!(
"{}{}.{}",
info.package,
naming::compact_version(info.schema_version),
info.object_type
)
}
pub(crate) fn normalize_qualified_type(value: &str) -> String {
match QualifiedType::parse(value) {
Ok(qt) => format!(
"{}|{}|{}",
qt.domain.to_ascii_lowercase(),
qt.version,
naming::strip_obj_prefix(&qt.object_type).to_ascii_lowercase()
),
Err(_) => value.trim().to_ascii_lowercase(),
}
}
fn normalize_content_type(value: &str) -> String {
match parse_content_type(value) {
Ok(parts) => format!(
"{}|{}|{}",
parts.domain.to_ascii_lowercase(),
naming::compact_version(parts.version),
naming::strip_obj_prefix(parts.object_type).to_ascii_lowercase()
),
Err(_) => value.trim().to_ascii_lowercase(),
}
}