use std::collections::{BTreeMap, BTreeSet};
use crate::ontology::extract::ggplot2_core::{self, CoreRow, Evidence, Ggplot2Stats};
use crate::ontology::rdf::{iri, Graph};
use crate::ontology::shapes::{self, NodeShape, Report, Severity};
pub const SHAPE: &str = "ggplot2-core";
pub const PLANT_ROW: &str = "geom_ggplot2_core_plant";
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct Ggplot2CoreReport {
pub ggplot2: Option<String>,
pub domain: Vec<String>,
pub bound: usize,
pub proven: Vec<String>,
pub not_proven: Vec<String>,
}
impl From<&Ggplot2Stats> for Ggplot2CoreReport {
fn from(s: &Ggplot2Stats) -> Self {
Self {
ggplot2: s.ggplot2.clone(),
domain: s.domain.iter().cloned().collect(),
bound: s.bound,
proven: s.proven.iter().cloned().collect(),
not_proven: s.not_proven.iter().cloned().collect(),
}
}
}
fn plant() -> CoreRow {
CoreRow {
row: 1,
name: PLANT_ROW.into(),
concept: "Geom".into(),
aes: vec!["x".into(), "y".into()],
}
}
fn evidenced(name: &str) -> Evidence {
let one = |v: &str| BTreeMap::from([(name.to_string(), v.to_string())]);
let many = |v: &str| BTreeMap::from([(name.to_string(), vec![v.to_string()])]);
Evidence {
contracts: one("plant-contract"),
fixtures: many("plant/expected.json"),
surface: one("plant/surface.json"),
receipts: many("plant/receipt.json"),
}
}
fn run(shape: &NodeShape, version: &str, ev: &Evidence) -> Report {
let mut g = Graph::new();
ggplot2_core::apply(&mut g, version, &[plant()], ev);
shapes::validate(&g, std::slice::from_ref(shape))
}
fn plant_fires(shapes: &[NodeShape], version: Option<&str>) -> bool {
let (Some(shape), Some(version)) = (shapes.iter().find(|s| s.id == SHAPE), version) else {
return false;
};
let bare = run(shape, version, &Evidence::default());
let refused = !bare.results.is_empty()
&& bare.results.iter().all(|r| {
r.severity == Severity::Violation
&& r.focus == iri("plot", PLANT_ROW)
&& r.component == "maxCount"
&& r.path.as_deref() == Some(ggplot2_core::plot("notProven").as_str())
});
refused
&& run(shape, version, &evidenced(PLANT_ROW))
.results
.is_empty()
}
#[must_use]
pub fn declared(shapes: &[NodeShape]) -> bool {
shapes.iter().any(|s| s.id == SHAPE)
}
#[must_use]
pub fn control(shapes: &[NodeShape], stats: &Ggplot2Stats, scoped: bool) -> Option<String> {
if !declared(shapes) && (scoped || stats.domain.is_empty()) {
return None;
}
let fired = plant_fires(shapes, stats.ggplot2.as_deref());
Some(if fired { "fired" } else { "not-fired" }.to_string())
}
#[must_use]
pub fn wiring_holds(report: &Report, stats: &Ggplot2Stats) -> bool {
let p = ggplot2_core::plot("notProven");
let flagged: BTreeSet<&str> = report
.results
.iter()
.filter(|r| r.shape == SHAPE && r.path.as_deref() == Some(p.as_str()))
.map(|r| r.focus.as_str())
.collect();
let owning: BTreeSet<String> = stats.not_proven.iter().map(|n| iri("plot", n)).collect();
flagged == owning.iter().map(String::as_str).collect()
}
#[cfg(test)]
#[path = "ggplot2_core_gate_tests.rs"]
mod tests;