use std::collections::BTreeSet;
use serde_json::{Map, Number, Value};
use crate::any_xml::AnyXml;
use crate::error::{Error, Result};
use crate::identity;
use crate::naming;
use crate::reflect::{Binding, FieldInfo, ScalarKind, TypeInfo};
use crate::registry::{DynObject, Registry};
use crate::value::{FieldValue, OwnedValue, Reflect, Scalar};
pub const TYPE_KEY: &str = "$type";
pub const TEXT_KEY: &str = "_";
pub const DATA_KEY: &str = "_data";
pub fn qualified_type_of(info: &TypeInfo) -> String {
format!(
"{}{}.{}",
info.package,
naming::compact_version(info.schema_version),
info.object_type
)
}
pub trait ObjectSource {
fn find(&self, uuid: &str) -> Option<&dyn Reflect>;
}
pub struct NoObjects;
impl ObjectSource for NoObjects {
fn find(&self, _uuid: &str) -> Option<&dyn Reflect> {
None
}
}
pub struct ObjectsInMemory<'a> {
by_uuid: Vec<(String, &'a dyn Reflect)>,
}
impl<'a> ObjectsInMemory<'a> {
pub fn new(objects: impl IntoIterator<Item = &'a dyn Reflect>) -> Self {
let by_uuid = objects
.into_iter()
.filter_map(|object| {
identity::uuid_of_reflect(object).map(|uuid| (uuid.to_ascii_lowercase(), object))
})
.collect();
Self { by_uuid }
}
}
impl ObjectSource for ObjectsInMemory<'_> {
fn find(&self, uuid: &str) -> Option<&dyn Reflect> {
let key = uuid.to_ascii_lowercase();
self.by_uuid
.iter()
.find(|(u, _)| *u == key)
.map(|(_, object)| *object)
}
}
pub fn write_value(object: &dyn Reflect) -> Value {
write_value_with(object, &NoObjects)
}
pub fn write_value_with(object: &dyn Reflect, source: &dyn ObjectSource) -> Value {
let mut expanded = BTreeSet::new();
if let Some(uuid) = identity::uuid_of_reflect(object) {
expanded.insert(uuid.to_ascii_lowercase());
}
write_object(object, source, &mut expanded)
}
pub fn write_string(object: &dyn Reflect, pretty: bool) -> Result<String> {
write_string_with(object, &NoObjects, pretty)
}
pub fn write_string_with(object: &dyn Reflect, source: &dyn ObjectSource, pretty: bool) -> Result<String> {
let value = write_value_with(object, source);
let out = if pretty {
serde_json::to_string_pretty(&value)
} else {
serde_json::to_string(&value)
};
out.map_err(|e| Error::other(format!("JSON serialization failed: {e}")))
}
fn write_object(object: &dyn Reflect, source: &dyn ObjectSource, expanded: &mut BTreeSet<String>) -> Value {
let info = object.type_info();
if let Some(any) = object.as_any().downcast_ref::<AnyXml>() {
return write_any_xml(any, info);
}
let mut map = Map::new();
map.insert(TYPE_KEY.to_string(), Value::String(qualified_type_of(info)));
for (field, key) in info.fields.iter().zip(json_keys(info)) {
let Some(value) = object.field(field.name) else {
continue;
};
if value.is_empty() {
continue;
}
map.insert(key, write_field(&value, source, expanded));
}
if info.is_object_ref {
write_data(&mut map, object, source, expanded);
}
Value::Object(map)
}
fn write_data(
map: &mut Map<String, Value>,
reference: &dyn Reflect,
source: &dyn ObjectSource,
expanded: &mut BTreeSet<String>,
) {
let Some(uuid) = identity::uuid_of_reflect(reference).filter(|u| !u.is_empty()) else {
return;
};
if !expanded.insert(uuid.to_ascii_lowercase()) {
return;
}
if let Some(target) = source.find(uuid) {
let value = write_object(target, source, expanded);
map.insert(DATA_KEY.to_string(), value);
}
}
fn write_field(value: &FieldValue<'_>, source: &dyn ObjectSource, expanded: &mut BTreeSet<String>) -> Value {
match value {
FieldValue::Null => Value::Null,
FieldValue::Scalar(scalar) => write_scalar(*scalar),
FieldValue::Object(child) => write_object(*child, source, expanded),
FieldValue::List(items) => Value::Array(
items
.iter()
.map(|item| write_field(item, source, expanded))
.collect(),
),
}
}
fn write_scalar(scalar: Scalar<'_>) -> Value {
match scalar {
Scalar::Bool(v) => Value::Bool(v),
Scalar::Int(v) => Value::Number(Number::from(v)),
Scalar::UInt(v) => Value::Number(Number::from(v)),
Scalar::Double(v) => match Number::from_f64(v) {
Some(number) => Value::Number(number),
None => Value::String(xsd_double(v)),
},
Scalar::Str(v) => Value::String(v.to_string()),
}
}
fn xsd_double(value: f64) -> String {
if value.is_nan() {
"NaN".to_string()
} else if value.is_sign_positive() {
"INF".to_string()
} else {
"-INF".to_string()
}
}
fn parse_xsd_double(text: &str) -> Option<f64> {
match text.trim() {
"NaN" => Some(f64::NAN),
"INF" | "+INF" => Some(f64::INFINITY),
"-INF" => Some(f64::NEG_INFINITY),
other => other.parse::<f64>().ok(),
}
}
fn write_any_xml(any: &AnyXml, info: &TypeInfo) -> Value {
let pairs = |items: &[(String, String)]| {
Value::Object(
items
.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect::<Map<_, _>>(),
)
};
let mut map = Map::new();
map.insert(TYPE_KEY.to_string(), Value::String(qualified_type_of(info)));
if !any.attributes.is_empty() {
map.insert("attributes".to_string(), pairs(&any.attributes));
}
if !any.content.is_empty() {
map.insert("content".to_string(), Value::String(any.content.clone()));
}
if !any.namespaces.is_empty() {
map.insert("namespaces".to_string(), pairs(&any.namespaces));
}
Value::Object(map)
}
pub fn json_keys(info: &TypeInfo) -> Vec<String> {
let mut seen = BTreeSet::new();
info.fields
.iter()
.map(|field| {
if field.binding == Binding::Text {
TEXT_KEY.to_string()
} else if seen.insert(field.xml_name) {
field.xml_name.to_string()
} else {
format!("{}:{}", field.xml_ns.prefix, field.xml_name)
}
})
.collect()
}
pub struct JsonDocument {
pub root: Box<dyn DynObject>,
pub referenced: Vec<Box<dyn DynObject>>,
}
pub fn read_string(text: &str, registry: &Registry) -> Result<JsonDocument> {
let value: Value =
serde_json::from_str(text).map_err(|e| Error::other(format!("JSON deserialization failed: {e}")))?;
read_value(&value, registry)
}
pub fn read_value(value: &Value, registry: &Registry) -> Result<JsonDocument> {
let mut referenced = Vec::new();
let root = read_object(value, None, registry, &mut referenced)?;
Ok(JsonDocument { root, referenced })
}
fn read_object(
value: &Value,
declared: Option<&'static TypeInfo>,
registry: &Registry,
referenced: &mut Vec<Box<dyn DynObject>>,
) -> Result<Box<dyn DynObject>> {
let map = value
.as_object()
.ok_or_else(|| Error::other(format!("expected a JSON object, found {}", kind_of(value))))?;
let qualified = match map.get(TYPE_KEY).and_then(Value::as_str) {
Some(qualified) => qualified.to_string(),
None => {
let info = declared.ok_or_else(|| {
Error::other(format!("a JSON object without `{TYPE_KEY}` and no declared type"))
})?;
if info.is_abstract {
return Err(Error::other(format!(
"`{}` is abstract, so `{TYPE_KEY}` is required to say which subtype this is",
info.name
)));
}
qualified_type_of(info)
}
};
let mut object = registry.create(&qualified)?;
fill(object.as_reflect_mut(), map, registry, referenced)?;
Ok(object)
}
fn fill(
object: &mut dyn Reflect,
map: &Map<String, Value>,
registry: &Registry,
referenced: &mut Vec<Box<dyn DynObject>>,
) -> Result<()> {
let info = object.type_info();
let keys = json_keys(info);
for (key, value) in map {
if key == TYPE_KEY {
continue;
}
if key == DATA_KEY {
let extra = read_object(value, None, registry, referenced)?;
referenced.push(extra);
continue;
}
let Some(index) = find_field(info, &keys, key) else {
continue;
};
let field = &info.fields[index];
let owned = read_field(value, field, registry, referenced)?;
object
.set_field(field.name, owned)
.map_err(|e| Error::other(format!("`{}.{}`: {e}", qualified_type_of(info), field.xml_name)))?;
}
Ok(())
}
fn find_field(info: &TypeInfo, keys: &[String], key: &str) -> Option<usize> {
if let Some(index) = keys.iter().position(|k| k == key) {
return Some(index);
}
let bare = key.rsplit(':').next().unwrap_or(key);
info.fields
.iter()
.position(|f| f.name.eq_ignore_ascii_case(key) || f.xml_name.eq_ignore_ascii_case(bare))
}
#[cfg(feature = "json")]
pub fn read_field_value(
value: &Value,
field: &FieldInfo,
registry: &Registry,
) -> Result<(OwnedValue, Vec<Box<dyn DynObject>>)> {
let mut referenced = Vec::new();
let owned = read_field(value, field, registry, &mut referenced)?;
Ok((owned, referenced))
}
fn read_field(
value: &Value,
field: &FieldInfo,
registry: &Registry,
referenced: &mut Vec<Box<dyn DynObject>>,
) -> Result<OwnedValue> {
let declared = field.ty.type_info();
match value {
Value::Null => Ok(OwnedValue::Null),
Value::Array(items) => {
let mut out = Vec::with_capacity(items.len());
for item in items {
out.push(read_field(item, field, registry, referenced)?);
}
Ok(OwnedValue::List(out))
}
Value::Object(map) => {
if declared.is_some_and(|info| info.name == "AnyXml") {
let stated = map.get(TYPE_KEY).and_then(Value::as_str).unwrap_or_default();
if !stated.is_empty() && stated != "eml.AnyXml" {
let typed = read_object(value, None, registry, referenced)?;
let subtree = AnyXml::of(typed.as_ref()).map_err(|e| {
Error::other(format!("`{stated}` cannot be written as a subtree: {e}"))
})?;
return Ok(OwnedValue::Object(Box::new(subtree)));
}
return Ok(OwnedValue::Object(Box::new(read_any_xml(map))));
}
if let Some(info) = declared
&& info.kind == crate::reflect::TypeKind::Enumeration
{
return Err(Error::other(format!(
"`{}` is an enumeration ({}): it takes one of its values, not an object",
field.xml_name, info.name
)));
}
let object = read_object(value, declared, registry, referenced)?;
Ok(OwnedValue::Object(object.into_reflect()))
}
scalar => read_scalar(scalar, field),
}
}
fn read_scalar(value: &Value, field: &FieldInfo) -> Result<OwnedValue> {
let kind = match field.ty {
crate::reflect::FieldType::Scalar(kind) => Some(kind),
_ => None,
};
let bad = |wanted: &str| {
Error::other(format!(
"`{}` expects {wanted}, found {}",
field.xml_name,
kind_of(value)
))
};
match (value, kind) {
(Value::Bool(v), Some(ScalarKind::Bool) | None) => Ok(OwnedValue::Bool(*v)),
(Value::Bool(v), Some(ScalarKind::String)) => Ok(OwnedValue::Str(v.to_string())),
(Value::Bool(_), _) => Err(bad("a number")),
(Value::String(s), Some(ScalarKind::Bool)) => match s.trim() {
"true" | "1" => Ok(OwnedValue::Bool(true)),
"false" | "0" => Ok(OwnedValue::Bool(false)),
_ => Err(bad("a boolean")),
},
(Value::String(s), Some(ScalarKind::Int)) => s
.trim()
.parse::<i64>()
.map(OwnedValue::Int)
.map_err(|_| bad("an integer")),
(Value::String(s), Some(ScalarKind::UInt)) => s
.trim()
.parse::<u64>()
.map(OwnedValue::UInt)
.map_err(|_| bad("an unsigned integer")),
(Value::String(s), Some(ScalarKind::Double)) => parse_xsd_double(s)
.map(OwnedValue::Double)
.ok_or_else(|| bad("a double")),
(Value::String(s), _) => Ok(OwnedValue::Str(s.clone())),
(Value::Number(n), Some(ScalarKind::Double)) => {
n.as_f64().map(OwnedValue::Double).ok_or_else(|| bad("a double"))
}
(Value::Number(n), Some(ScalarKind::UInt)) => n
.as_u64()
.or_else(|| u64::try_from(n.as_i64()?).ok())
.map(OwnedValue::UInt)
.ok_or_else(|| bad("an unsigned integer")),
(Value::Number(n), Some(ScalarKind::Int)) => n
.as_i64()
.or_else(|| i64::try_from(n.as_u64()?).ok())
.map(OwnedValue::Int)
.ok_or_else(|| bad("an integer")),
(Value::Number(n), Some(ScalarKind::String | ScalarKind::DateTime | ScalarKind::Binary)) => {
Ok(OwnedValue::Str(n.to_string()))
}
(Value::Number(n), _) => {
if let Some(v) = n.as_i64() {
Ok(OwnedValue::Int(v))
} else if let Some(v) = n.as_u64() {
Ok(OwnedValue::UInt(v))
} else {
n.as_f64().map(OwnedValue::Double).ok_or_else(|| bad("a number"))
}
}
_ => Err(bad("a scalar")),
}
}
fn read_any_xml(map: &Map<String, Value>) -> AnyXml {
let pairs = |key: &str| -> Vec<(String, String)> {
map.get(key)
.and_then(Value::as_object)
.map(|m| {
m.iter()
.map(|(k, v)| (k.clone(), v.as_str().unwrap_or_default().to_string()))
.collect()
})
.unwrap_or_default()
};
AnyXml {
attributes: pairs("attributes"),
content: map
.get("content")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
namespaces: pairs("namespaces"),
}
}
fn kind_of(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "a boolean",
Value::Number(_) => "a number",
Value::String(_) => "a string",
Value::Array(_) => "an array",
Value::Object(_) => "an object",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reflect::EnergymlType;
#[test]
fn a_qualified_type_covers_nested_types_too() {
let info = <AnyXml as EnergymlType>::type_info();
assert_eq!(qualified_type_of(info), "eml.AnyXml");
}
#[test]
fn a_foreign_subtree_survives_the_round_trip() {
let any = AnyXml {
attributes: vec![("srsName".to_string(), "EPSG::4326".to_string())],
content: "<gml:origin>0 0</gml:origin>".to_string(),
namespaces: vec![("gml".to_string(), "http://www.opengis.net/gml/3.2".to_string())],
};
let value = write_any_xml(&any, <AnyXml as EnergymlType>::type_info());
let back = read_any_xml(value.as_object().unwrap());
assert_eq!(back, any);
assert_eq!(value[TYPE_KEY], "eml.AnyXml");
}
}