use serde_json::{Value, json};
use crate::reflect::{Binding, FieldInfo, MaxOccurs, TypeInfo, TypeKind, VariantInfo};
use crate::registry::Registry;
fn reachable(registry: &Registry) -> Vec<&'static TypeInfo> {
let mut seen: Vec<&'static TypeInfo> = Vec::new();
let mut queue: Vec<&'static TypeInfo> = registry.types().collect();
while let Some(info) = queue.pop() {
if seen.iter().any(|held| std::ptr::eq(*held, info)) {
continue;
}
seen.push(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(inner) = variant.ty {
queue.push(inner());
}
}
}
seen
}
pub fn registry_docs(registry: &Registry) -> Value {
let mut releases: Vec<(&'static str, &'static str, Vec<&'static TypeInfo>)> = Vec::new();
for info in reachable(registry) {
match releases
.iter_mut()
.find(|(package, version, _)| *package == info.package && *version == info.version)
{
Some((_, _, types)) => types.push(info),
None => releases.push((info.package, info.version, vec![info])),
}
}
releases.retain(|(package, version, _)| !package.is_empty() && !version.is_empty());
releases.sort_by_key(|(package, version, _)| (*package, *version));
Value::Array(
releases
.into_iter()
.map(|(package, version, mut types)| {
types.sort_by_key(|info| info.name);
json!({
"package": package,
"version": version,
"schemaVersion": types.first().map(|t| t.schema_version),
"types": types.into_iter().map(type_doc).collect::<Vec<_>>(),
})
})
.collect(),
)
}
pub fn type_doc(info: &'static TypeInfo) -> Value {
let keys = crate::json::json_keys(info);
let fields: Vec<Value> = info
.fields
.iter()
.zip(&keys)
.map(|(field, key)| field_doc(field, key))
.collect();
let variants: Vec<Value> = info.variants.iter().map(variant_doc).collect();
json!({
"name": info.name,
"xmlName": info.xml_name,
"objectType": info.object_type,
"typeKey": info.type_key,
"namespace": info.xml_ns.uri,
"qualifiedType": info.qualified_type,
"contentType": info.content_type,
"kind": match info.kind {
TypeKind::Struct => "struct",
TypeKind::Enumeration => "enumeration",
TypeKind::Choice => "choice",
},
"abstract": info.is_abstract,
"topLevel": info.is_top_level,
"nestable": info.is_nestable,
"open": info.is_open,
"doc": info.doc,
"fields": fields,
"variants": variants,
})
}
fn field_doc(field: &FieldInfo, json_key: &str) -> Value {
let accepted: Vec<Value> = field
.accepted_types()
.into_iter()
.map(|info| {
json!({
"name": info.name,
"qualifiedType": info.qualified_type,
"isObjectReference": info.is_object_ref,
})
})
.collect();
let values: Vec<Value> = field
.ty
.allowed_values()
.iter()
.filter(|variant| !variant.is_other)
.map(|variant| json!({ "value": variant.value, "doc": variant.doc }))
.collect();
json!({
"name": field.xml_name,
"jsonName": json_key,
"namespace": field.xml_ns.uri,
"doc": field.doc,
"binding": match field.binding {
Binding::Attribute => "attribute",
Binding::Element => "element",
Binding::Text => "text",
_ => "any",
},
"minOccurs": field.occurs.min,
"maxOccurs": match field.occurs.max {
MaxOccurs::Bounded(max) => json!(max),
MaxOccurs::Unbounded => Value::Null,
},
"required": field.is_required(),
"xsdList": field.is_list,
"scalar": field.ty.type_info().is_none(),
"type": field.ty.type_info().map(|info| info.name),
"openEnumeration": field.ty.type_info().is_some_and(|info| info.is_open),
"allowedValues": values,
"acceptedTypes": accepted,
})
}
fn variant_doc(variant: &VariantInfo) -> Value {
json!({
"name": variant.name,
"value": if variant.is_other { Value::Null } else { json!(variant.value) },
"xsiType": variant.xsi_type,
"doc": variant.doc,
"isOther": variant.is_other,
})
}