use std::collections::HashSet;
use super::json_capnp;
pub(crate) fn check_flattening_terminates(
root: capnp::schema::StructSchema,
) -> capnp::Result<()> {
let mut acyclic = HashSet::new();
let mut reached = HashSet::new();
let mut worklist = vec![root];
while let Some(schema) = worklist.pop() {
if !reached.insert(schema.get_proto().get_id()) {
continue;
}
let mut path = Vec::new();
check_acyclic(schema, &mut acyclic, &mut path)?;
for field in schema.get_fields()? {
collect_struct_types(field.get_type(), &mut worklist);
}
}
Ok(())
}
fn check_acyclic(
schema: capnp::schema::StructSchema,
acyclic: &mut HashSet<u64>,
path: &mut Vec<u64>,
) -> capnp::Result<()> {
let id = schema.get_proto().get_id();
if acyclic.contains(&id) {
return Ok(());
}
if path.contains(&id) {
return Err(capnp::Error::failed(format!(
"cyclic JSON flattening detected in {}",
schema.get_proto().get_display_name()?.to_str()?
)));
}
path.push(id);
for field in schema.get_fields()? {
if !is_flatten_edge(field)? {
continue;
}
if let capnp::introspect::TypeVariant::Struct(raw) =
field.get_type().which()
{
check_acyclic(capnp::schema::StructSchema::new(raw), acyclic, path)?;
}
}
path.pop();
acyclic.insert(id);
Ok(())
}
fn is_flatten_edge(field: capnp::schema::Field) -> capnp::Result<bool> {
if matches!(
field.get_proto().which()?,
capnp::schema_capnp::field::Group(_)
) {
return Ok(true);
}
Ok(
field
.get_annotations()?
.iter()
.any(|anno| anno.get_id() == json_capnp::flatten::ID),
)
}
fn collect_struct_types(
typ: capnp::introspect::Type,
worklist: &mut Vec<capnp::schema::StructSchema>,
) {
let mut typ = typ;
loop {
match typ.which() {
capnp::introspect::TypeVariant::Struct(raw) => {
worklist.push(capnp::schema::StructSchema::new(raw));
return;
}
capnp::introspect::TypeVariant::List(element) => typ = element,
_ => return,
}
}
}