use super::{GovernedTripwire, TripEffect};
pub fn decode_tripwires(sparql_json: &str) -> anyhow::Result<Vec<GovernedTripwire>> {
let value: serde_json::Value =
serde_json::from_str(sparql_json).map_err(|e| anyhow::anyhow!("not JSON: {e}"))?;
let rows = value
.get("results")
.and_then(|r| r.get("bindings"))
.and_then(|b| b.as_array())
.ok_or_else(|| anyhow::anyhow!("no results.bindings array in SPARQL response"))?;
let mut order: Vec<String> = Vec::new();
let mut acc: std::collections::HashMap<String, GovernedTripwire> =
std::collections::HashMap::new();
let mut conflicts: Vec<&'static str> = Vec::new();
for row in rows {
let get = |key: &str| -> Option<String> {
row.get(key)
.and_then(|b| b.get("value"))
.and_then(|v| v.as_str())
.map(str::to_string)
.filter(|s| !s.is_empty())
};
if get("selector").is_some() || get("predicate").is_some() {
continue;
}
let Some(iri) = get("policy") else { continue };
let Some(glob) = get("appliesTo") else {
continue;
};
if !order.contains(&iri) {
order.push(iri.clone());
acc.insert(
iri.clone(),
GovernedTripwire {
policy: iri.clone(),
name: get("name").unwrap_or_else(|| last_segment(&iri).to_string()),
paths: Vec::new(),
effect: TripEffect::parse(get("effect").as_deref()),
claim: get("claim"),
class: get("constraintClass"),
verification_point: get("verificationPoint"),
backoff_formula: get("backoffFormula"),
conflicts: Vec::new(),
},
);
}
let wire = acc.get_mut(&iri).expect("inserted above");
if !wire.paths.contains(&glob) {
wire.paths.push(glob);
}
let effect = TripEffect::parse(get("effect").as_deref());
if effect != TripEffect::Undeclared && effect != wire.effect {
if wire.effect == TripEffect::Undeclared {
wire.effect = effect;
} else {
conflicts.push("aegis:effect");
}
}
for (slot, key, incoming) in [
(&mut wire.claim, "aegis:claim", get("claim")),
(
&mut wire.class,
"aegis:constraintClass",
get("constraintClass"),
),
(
&mut wire.verification_point,
"aegis:verificationPoint",
get("verificationPoint"),
),
(
&mut wire.backoff_formula,
"aegis:backoffFormula",
get("backoffFormula"),
),
] {
match (&slot, incoming) {
(Some(existing), Some(v)) if **existing != v => conflicts.push(key),
(None, Some(v)) => *slot = Some(v),
_ => {}
}
}
for field in conflicts.drain(..) {
if !wire.conflicts.iter().any(|c| c == field) {
wire.conflicts.push(field.to_string());
}
}
}
Ok(order
.into_iter()
.filter_map(|iri| acc.remove(&iri))
.collect())
}
fn last_segment(iri: &str) -> &str {
iri.rsplit(['/', '#']).next().unwrap_or(iri)
}