agentic_graph_spec/
lib.rs1#![warn(missing_docs)]
8
9mod agx;
10mod canonical;
11mod parse;
12mod plan;
13mod validate;
14
15pub use agx::{AgxCall, AgxError, ParsedExpression, parse_expression};
16pub use canonical::{CanonicalError, canonical_json, graph_digest};
17pub use parse::{ParseError, load, parse};
18pub use plan::{PlanError, graph_effective_edges, plan_graph, topological_order};
19pub use validate::{validate, validate_path};
20
21use serde::{Deserialize, Serialize};
22use serde_json::{Map, Value};
23
24pub const AGS_VERSION: &str = "1.0";
26pub const SUPPORT_VERSION: &str = "1.0.4";
28pub type Document = Map<String, Value>;
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct Finding {
34 pub code: String,
36 pub severity: String,
38 pub message: String,
40 #[serde(skip_serializing_if = "String::is_empty")]
41 pub pointer: String,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ValidationReport {
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub document: Option<Document>,
51 pub findings: Vec<Finding>,
53 pub errors: Vec<Finding>,
55 pub warnings: Vec<Finding>,
57 pub ok: bool,
59}
60
61impl ValidationReport {
62 fn new(document: Document) -> Self {
63 Self {
64 document: Some(document),
65 findings: vec![],
66 errors: vec![],
67 warnings: vec![],
68 ok: true,
69 }
70 }
71
72 fn add(
73 &mut self,
74 code: &str,
75 severity: &str,
76 message: impl Into<String>,
77 pointer: impl Into<String>,
78 ) {
79 let finding = Finding {
80 code: code.into(),
81 severity: severity.into(),
82 message: message.into(),
83 pointer: pointer.into(),
84 };
85 self.findings.push(finding.clone());
86 if severity == "error" {
87 self.errors.push(finding);
88 self.ok = false;
89 } else {
90 self.warnings.push(finding);
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct EffectiveEdge {
98 pub from: String,
100 pub to: String,
102 pub kind: String,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub when: Option<String>,
107}
108
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct GraphPlan {
112 pub graph_id: String,
114 pub graph_digest: String,
116 pub order: Vec<String>,
118 pub entrypoints: Vec<String>,
120 pub effective_edges: Vec<EffectiveEdge>,
122 pub reachable: Vec<String>,
124 pub unreachable: Vec<String>,
126 pub tier_histogram: std::collections::BTreeMap<String, usize>,
128 pub worst_case_node_executions: u64,
130 pub executable: bool,
132 pub unsupported_features: Vec<String>,
134}
135
136pub(crate) fn object(value: Option<&Value>) -> &Map<String, Value> {
137 value.and_then(Value::as_object).unwrap_or_else(|| {
138 static EMPTY: std::sync::LazyLock<Map<String, Value>> = std::sync::LazyLock::new(Map::new);
139 &EMPTY
140 })
141}
142
143pub(crate) fn strings(value: Option<&Value>) -> Vec<String> {
144 value
145 .and_then(Value::as_array)
146 .into_iter()
147 .flatten()
148 .filter_map(Value::as_str)
149 .map(str::to_owned)
150 .collect()
151}