Skip to main content

ainl_runtime/adapters/
mod.rs

1//! Patch adapter registry and reference [`GraphPatchAdapter`] for procedural patch dispatch.
2
3mod graph_patch;
4
5pub use graph_patch::{GraphPatchAdapter, GraphPatchHostDispatch};
6
7use std::collections::HashMap;
8
9use crate::engine::PatchDispatchContext;
10
11/// Label-keyed procedural patch executor. Register via [`crate::AinlRuntime::register_adapter`].
12///
13/// Dispatch: [`crate::AinlRuntime`] resolves an adapter by procedural `label` first, then falls
14/// back to [`GraphPatchAdapter::NAME`] when registered via [`crate::AinlRuntime::register_default_patch_adapters`].
15pub trait PatchAdapter: Send + Sync {
16    /// Label this adapter handles (matched against the procedural patch `label`).
17    fn name(&self) -> &str;
18
19    /// Execute the patch. Returns a JSON value the host can inspect.
20    ///
21    /// Non-fatal at the runtime layer: on [`Err`], the runtime logs and continues as a metadata
22    /// dispatch (fitness update still proceeds when applicable).
23    fn execute_patch(&self, ctx: &PatchDispatchContext<'_>) -> Result<serde_json::Value, String>;
24}
25
26#[derive(Default)]
27pub struct AdapterRegistry {
28    adapters: HashMap<String, Box<dyn PatchAdapter>>,
29}
30
31impl AdapterRegistry {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn register(&mut self, adapter: impl PatchAdapter + 'static) {
37        self.adapters
38            .insert(adapter.name().to_string(), Box::new(adapter));
39    }
40
41    pub fn get(&self, label: &str) -> Option<&dyn PatchAdapter> {
42        self.adapters.get(label).map(|boxed| boxed.as_ref())
43    }
44
45    pub fn registered_names(&self) -> Vec<&str> {
46        let mut names: Vec<&str> = self.adapters.keys().map(|s| s.as_str()).collect();
47        names.sort_unstable();
48        names
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.adapters.is_empty()
53    }
54}