gjson pathing for stem paths
This commit is contained in:
@ -265,12 +265,12 @@ impl Database {
|
||||
String::from(""),
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
&mut inner_map,
|
||||
Vec::new(),
|
||||
&mut errors,
|
||||
);
|
||||
if !inner_map.is_empty() {
|
||||
println!("SCHEMA: {} STEMS: {:?}", schema_id, inner_map.keys());
|
||||
db_stems.insert(schema_id, inner_map);
|
||||
}
|
||||
}
|
||||
@ -288,11 +288,12 @@ impl Database {
|
||||
db: &Database,
|
||||
root_schema_id: &str,
|
||||
schema: &Schema,
|
||||
mut current_path: String,
|
||||
current_path: String,
|
||||
parent_type: Option<String>,
|
||||
property_name: Option<String>,
|
||||
is_root: bool,
|
||||
is_polymorphic: bool,
|
||||
inner_map: &mut HashMap<String, Arc<Stem>>,
|
||||
seen_entities: Vec<String>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
let mut is_entity = false;
|
||||
@ -323,6 +324,12 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
if is_entity {
|
||||
if seen_entities.contains(&entity_type) {
|
||||
return; // Break cyclical schemas!
|
||||
}
|
||||
}
|
||||
|
||||
let mut relation_col = None;
|
||||
if is_entity {
|
||||
if let (Some(pt), Some(prop)) = (&parent_type, &property_name) {
|
||||
@ -344,46 +351,21 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
let mut final_path = current_path.clone();
|
||||
if is_polymorphic && !final_path.is_empty() && !final_path.ends_with(&entity_type) {
|
||||
if final_path.ends_with(".#") {
|
||||
final_path = format!("{}(type==\"{}\")", final_path, entity_type);
|
||||
} else {
|
||||
final_path = format!("{}#(type==\"{}\")", final_path, entity_type);
|
||||
}
|
||||
}
|
||||
|
||||
let stem = Stem {
|
||||
r#type: entity_type.clone(),
|
||||
relation: relation_col,
|
||||
schema: Arc::new(schema.clone()),
|
||||
};
|
||||
|
||||
let mut branch_path = if is_root {
|
||||
String::new()
|
||||
} else if current_path.is_empty() {
|
||||
entity_type.clone()
|
||||
} else {
|
||||
format!("{}/{}", current_path, entity_type)
|
||||
};
|
||||
|
||||
// DEDUPLICATION: If we just recursed into the EXACT same entity type definition,
|
||||
// do not append again and do not re-register the stem.
|
||||
let already_registered =
|
||||
if current_path == entity_type || current_path.ends_with(&format!("/{}", entity_type)) {
|
||||
branch_path = current_path.clone();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !already_registered {
|
||||
if inner_map.contains_key(&branch_path) {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "STEM_COLLISION".to_string(),
|
||||
message: format!("The stem path `{}` resolves to multiple Entity boundaries. This usually occurs during un-wrapped $family or oneOf polymorphic schemas where multiple Entities are directly assigned to the same property. To fix this, encapsulate the polymorphic branch.", branch_path),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: root_schema_id.to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
inner_map.insert(branch_path.clone(), Arc::new(stem));
|
||||
}
|
||||
|
||||
// Update current_path for structural children
|
||||
current_path = branch_path;
|
||||
inner_map.insert(final_path, Arc::new(stem));
|
||||
}
|
||||
|
||||
let next_parent = if is_entity {
|
||||
@ -392,34 +374,22 @@ impl Database {
|
||||
parent_type.clone()
|
||||
};
|
||||
|
||||
let pass_seen = if is_entity {
|
||||
let mut ns = seen_entities.clone();
|
||||
ns.push(entity_type.clone());
|
||||
ns
|
||||
} else {
|
||||
seen_entities.clone()
|
||||
};
|
||||
|
||||
// Properties branch
|
||||
if let Some(props) = &schema.obj.properties {
|
||||
for (k, v) in props {
|
||||
// Bypass target and source properties if we are in a relationship
|
||||
if let Some(parent_str) = &next_parent {
|
||||
if let Some(pt) = db.types.get(parent_str) {
|
||||
if pt.relationship && (k == "target" || k == "source") {
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
v,
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
Some(k.clone()),
|
||||
false,
|
||||
inner_map,
|
||||
errors,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Standard Property Pathing
|
||||
let next_path = if current_path.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{}/{}", current_path, k)
|
||||
format!("{}.{}", current_path, k)
|
||||
};
|
||||
|
||||
Self::discover_stems(
|
||||
@ -431,6 +401,7 @@ impl Database {
|
||||
Some(k.clone()),
|
||||
false,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
@ -438,15 +409,22 @@ impl Database {
|
||||
|
||||
// Array Item branch
|
||||
if let Some(items) = &schema.obj.items {
|
||||
let next_path = if current_path.is_empty() {
|
||||
String::from("#")
|
||||
} else {
|
||||
format!("{}.#", current_path)
|
||||
};
|
||||
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
items,
|
||||
current_path.clone(),
|
||||
next_path,
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
false, // Arrays themselves aren't polymorphic branches, their items might be
|
||||
false,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
@ -463,8 +441,9 @@ impl Database {
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
false,
|
||||
is_polymorphic,
|
||||
inner_map,
|
||||
seen_entities.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
@ -481,8 +460,9 @@ impl Database {
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
false,
|
||||
true,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
@ -496,8 +476,9 @@ impl Database {
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
false,
|
||||
is_polymorphic,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
|
||||
21
src/lib.rs
21
src/lib.rs
@ -112,7 +112,7 @@ pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_stems() -> JsonB {
|
||||
use serde_json::{Map, Value};
|
||||
use serde_json::Value;
|
||||
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
@ -121,9 +121,24 @@ pub fn jspg_stems() -> JsonB {
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
JsonB(serde_json::to_value(&engine.database.stems).unwrap_or(Value::Object(Map::new())))
|
||||
let mut result_arr = Vec::new();
|
||||
for (schema_name, stems_map) in &engine.database.stems {
|
||||
let mut stems_arr = Vec::new();
|
||||
for (path_key, stem_arc) in stems_map {
|
||||
stems_arr.push(serde_json::json!({
|
||||
"path": path_key,
|
||||
"type": stem_arc.r#type,
|
||||
"relation": stem_arc.relation
|
||||
}));
|
||||
}
|
||||
result_arr.push(serde_json::json!({
|
||||
"schema": schema_name,
|
||||
"stems": stems_arr
|
||||
}));
|
||||
}
|
||||
JsonB(serde_json::to_value(result_arr).unwrap_or(Value::Array(Vec::new())))
|
||||
}
|
||||
None => JsonB(Value::Object(Map::new())),
|
||||
None => JsonB(Value::Array(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user