Skip to main content

ainl_runtime/adapters/
graph_patch.rs

1//! Reference [`GraphPatchAdapter`] — built-in fallback for procedural patches (`"graph_patch"`).
2//!
3//! Returns a small JSON summary for hosts; optional [`GraphPatchHostDispatch`] forwards that value.
4
5use std::sync::Arc;
6
7use serde_json::{json, Value};
8
9use super::PatchAdapter;
10use crate::engine::PatchDispatchContext;
11use ainl_memory::AinlNodeType;
12
13/// Optional host hook: receives the same JSON summary as [`GraphPatchAdapter::execute_patch`].
14pub trait GraphPatchHostDispatch: Send + Sync {
15    fn on_patch_dispatch(&self, envelope: Value) -> Result<Value, String>;
16}
17
18/// Reference adapter registered as [`Self::NAME`]. Used as a **fallback** when no label-specific
19/// [`PatchAdapter`] is registered for the procedural patch label.
20pub struct GraphPatchAdapter {
21    host: Option<Arc<dyn GraphPatchHostDispatch>>,
22}
23
24impl GraphPatchAdapter {
25    pub const NAME: &'static str = "graph_patch";
26
27    pub fn new() -> Self {
28        Self { host: None }
29    }
30
31    pub fn with_host(host: Arc<dyn GraphPatchHostDispatch>) -> Self {
32        Self { host: Some(host) }
33    }
34}
35
36impl Default for GraphPatchAdapter {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42fn build_summary(ctx: &PatchDispatchContext<'_>) -> Result<Value, String> {
43    let proc = match &ctx.node.node_type {
44        AinlNodeType::Procedural { procedural } => procedural,
45        _ => {
46            return Err("graph_patch: PatchDispatchContext.node is not procedural".to_string());
47        }
48    };
49    for key in &proc.declared_reads {
50        if !ctx.frame.contains_key(key) {
51            return Err(format!(
52                "graph_patch: declared read {key:?} missing from frame (adapter safety check)"
53            ));
54        }
55    }
56    let mut frame_keys: Vec<String> = ctx.frame.keys().cloned().collect();
57    frame_keys.sort_unstable();
58    Ok(json!({
59        "label": ctx.patch_label,
60        "patch_version": proc.patch_version,
61        "frame_keys": frame_keys,
62    }))
63}
64
65impl PatchAdapter for GraphPatchAdapter {
66    fn name(&self) -> &str {
67        Self::NAME
68    }
69
70    fn execute_patch(&self, ctx: &PatchDispatchContext<'_>) -> Result<Value, String> {
71        let summary = build_summary(ctx)?;
72        if let Some(h) = &self.host {
73            h.on_patch_dispatch(summary)
74        } else {
75            Ok(summary)
76        }
77    }
78}