use std::collections::HashSet;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct PidSchemaIndex {
paths: HashSet<String>,
}
impl PidSchemaIndex {
pub fn from_schema_file(path: &Path) -> Result<Self, std::io::Error> {
let content = std::fs::read_to_string(path)?;
let schema: serde_json::Value = serde_json::from_str(&content)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
Ok(Self::from_json(&schema))
}
pub fn from_json(schema: &serde_json::Value) -> Self {
let mut paths = HashSet::new();
if let Some(fields) = schema.get("fields").and_then(|v| v.as_object()) {
for (key, value) in fields {
paths.insert(key.clone());
Self::collect_children(key, value, &mut paths);
}
}
paths.insert(String::new()); Self { paths }
}
pub fn has_group(&self, source_path: &str) -> bool {
if self.paths.contains(source_path) {
return true;
}
let prefix = format!("{source_path}_");
self.paths.iter().any(|p| p.starts_with(&prefix))
}
fn collect_children(prefix: &str, node: &serde_json::Value, paths: &mut HashSet<String>) {
if let Some(children) = node.get("children").and_then(|v| v.as_object()) {
for (key, value) in children {
let path = format!("{prefix}.{key}");
paths.insert(path.clone());
Self::collect_children(&path, value, paths);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_json_basic() {
let schema = serde_json::json!({
"fields": {
"sg2": { "segments": {} },
"sg4": {
"children": {
"sg5_z16": { "segments": {} },
"sg8_z98": {
"children": {
"sg10": { "segments": {} }
}
},
"sg12": { "segments": {} }
}
}
}
});
let index = PidSchemaIndex::from_json(&schema);
assert!(index.has_group(""));
assert!(index.has_group("sg2"));
assert!(index.has_group("sg4"));
assert!(index.has_group("sg4.sg5_z16"));
assert!(index.has_group("sg4.sg8_z98"));
assert!(index.has_group("sg4.sg12"));
assert!(index.has_group("sg4.sg8_z98.sg10"));
assert!(!index.has_group("sg4.sg5_z17"));
assert!(!index.has_group("sg4.sg8_zd7"));
assert!(!index.has_group("sg3"));
assert!(index.has_group("sg4.sg5")); assert!(index.has_group("sg4.sg8")); }
#[test]
fn test_variant_suffix_match() {
let schema = serde_json::json!({
"fields": {
"sg4": {
"children": {
"sg12_vy": { "segments": {} },
"sg12_dp": { "segments": {} }
}
}
}
});
let index = PidSchemaIndex::from_json(&schema);
assert!(index.has_group("sg4.sg12_vy"));
assert!(index.has_group("sg4.sg12_dp"));
assert!(index.has_group("sg4.sg12"));
assert!(!index.has_group("sg4.sg11"));
}
#[test]
fn test_empty_schema() {
let schema = serde_json::json!({ "fields": {} });
let index = PidSchemaIndex::from_json(&schema);
assert!(index.has_group("")); assert!(!index.has_group("sg4"));
}
#[test]
fn test_load_real_schema() {
let path = Path::new(
"../../crates/mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json",
);
if !path.exists() {
eprintln!("schema not found, skipping");
return;
}
let index = PidSchemaIndex::from_schema_file(path).unwrap();
assert!(index.has_group("sg4.sg5_z16"));
assert!(index.has_group("sg4.sg5_z22"));
assert!(!index.has_group("sg4.sg5_z17"));
assert!(!index.has_group("sg4.sg5_z18"));
assert!(index.has_group("sg2"));
}
}