Skip to main content

apcore_toolkit/output/
registry_writer.rs

1// Registry writer for direct module registration.
2//
3// Converts ScannedModule instances into apcore Module implementations
4// and registers them directly into an apcore Registry.
5//
6// Framework adapters provide a `HandlerFactory` to resolve targets to real
7// async handlers. Without a factory, modules are registered with a passthrough
8// handler that echoes inputs (useful for schema-only registration).
9
10use std::pin::Pin;
11use std::sync::Arc;
12
13use tracing::debug;
14
15use apcore::context::Context;
16use apcore::errors::ModuleError;
17use apcore::Registry;
18
19use crate::output::types::{Verifier, WriteResult};
20use crate::output::verifiers::{run_verifier_chain, RegistryVerifier};
21use crate::types::ScannedModule;
22
23/// Async handler function type for registered modules.
24pub type HandlerFn = Arc<
25    dyn for<'a> Fn(
26            serde_json::Value,
27            &'a Context<serde_json::Value>,
28        ) -> Pin<
29            Box<
30                dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
31                    + Send
32                    + 'a,
33            >,
34        > + Send
35        + Sync,
36>;
37
38/// Factory that resolves a `target` string to an async handler.
39///
40/// Framework adapters implement this to map target strings (e.g., `"myapp:get_user"`)
41/// to actual handler functions. For example, an Axum adapter might look up the
42/// handler in a route table; a generic adapter might use a dynamic dispatch map.
43///
44/// ```ignore
45/// let factory: HandlerFactory = Arc::new(|target: &str| {
46///     let handler = lookup_handler(target);
47///     Some(Arc::new(move |inputs, _ctx| {
48///         let h = handler.clone();
49///         Box::pin(async move { h.call(inputs).await })
50///     }))
51/// });
52/// let writer = RegistryWriter::with_handler_factory(factory);
53/// ```
54pub type HandlerFactory = Arc<dyn Fn(&str) -> Option<HandlerFn> + Send + Sync>;
55
56/// Registers ScannedModule instances directly into an apcore Registry.
57///
58/// This is the default writer used when no output_format is specified.
59/// Instead of writing files, it registers modules directly for immediate use.
60///
61/// ## Handler Resolution
62///
63/// By default (`RegistryWriter::new()`), modules are registered with a passthrough
64/// handler that returns inputs unchanged — useful for schema-only registration
65/// where execution is handled elsewhere.
66///
67/// For executable modules, use `RegistryWriter::with_handler_factory(factory)` to
68/// provide a [`HandlerFactory`] that resolves target strings to real handlers.
69pub struct RegistryWriter {
70    handler_factory: Option<HandlerFactory>,
71}
72
73impl Default for RegistryWriter {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl RegistryWriter {
80    /// Create a RegistryWriter with passthrough handlers (schema-only registration).
81    pub fn new() -> Self {
82        Self {
83            handler_factory: None,
84        }
85    }
86
87    /// Create a RegistryWriter with a custom handler factory for target resolution.
88    pub fn with_handler_factory(factory: HandlerFactory) -> Self {
89        Self {
90            handler_factory: Some(factory),
91        }
92    }
93}
94
95impl RegistryWriter {
96    /// Register scanned modules into the registry.
97    ///
98    /// - `registry`: The apcore Registry to register modules into.
99    /// - `dry_run`: If true, skip registration and return results only.
100    /// - `verify`: If true, verify modules are retrievable after registration.
101    /// - `verifiers`: Optional custom verifiers run after the built-in check.
102    pub fn write(
103        &self,
104        modules: &[ScannedModule],
105        registry: &mut Registry,
106        dry_run: bool,
107        verify: bool,
108        verifiers: Option<&[&dyn Verifier]>,
109    ) -> Vec<WriteResult> {
110        let mut results: Vec<WriteResult> = Vec::new();
111
112        for module in modules {
113            if dry_run {
114                results.push(WriteResult::new(module.module_id.clone()));
115                continue;
116            }
117
118            let fm = self.to_function_module(module);
119            // Register with a descriptor
120            let descriptor = apcore::registry::registry::ModuleDescriptor {
121                name: module.module_id.clone(),
122                annotations: module.annotations.clone().unwrap_or_default(),
123                input_schema: module.input_schema.clone(),
124                output_schema: module.output_schema.clone(),
125                enabled: true,
126                tags: module.tags.clone(),
127                dependencies: vec![],
128            };
129            if let Err(e) = registry.register(&module.module_id, Box::new(fm), descriptor) {
130                results.push(WriteResult::failed(
131                    module.module_id.clone(),
132                    None,
133                    format!("Registration failed: {e}"),
134                ));
135                continue;
136            }
137            debug!("Registered module: {}", module.module_id);
138
139            let mut result = WriteResult::new(module.module_id.clone());
140            if verify {
141                result = verify_registry(&result, &module.module_id, registry);
142            }
143            if result.verified {
144                if let Some(vs) = verifiers {
145                    let chain_result = run_verifier_chain(vs, "", &module.module_id);
146                    if !chain_result.ok {
147                        result = WriteResult::failed(
148                            result.module_id,
149                            result.path,
150                            chain_result.error.unwrap_or_default(),
151                        );
152                    }
153                }
154            }
155            results.push(result);
156        }
157
158        results
159    }
160}
161
162impl RegistryWriter {
163    /// Convert a ScannedModule to an apcore FunctionModule.
164    ///
165    /// If a handler factory is configured and resolves the target, uses the
166    /// resolved handler. Otherwise falls back to a passthrough handler that
167    /// returns inputs unchanged.
168    fn to_function_module(&self, module: &ScannedModule) -> apcore::decorator::FunctionModule {
169        let annotations = module.annotations.clone().unwrap_or_default();
170        let input_schema = module.input_schema.clone();
171        let output_schema = module.output_schema.clone();
172
173        // Try to resolve the target via the handler factory
174        if let Some(factory) = &self.handler_factory {
175            if let Some(handler) = factory(&module.target) {
176                return apcore::decorator::FunctionModule::new::<_, ()>(
177                    annotations,
178                    input_schema,
179                    output_schema,
180                    move |inputs: serde_json::Value,
181                          ctx: &Context<serde_json::Value>|
182                          -> Pin<
183                        Box<
184                            dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
185                                + Send
186                                + '_,
187                        >,
188                    > { handler(inputs, ctx) },
189                );
190            }
191        }
192
193        // Fallback: passthrough handler (schema-only registration)
194        fn passthrough<'a>(
195            inputs: serde_json::Value,
196            _ctx: &'a Context<serde_json::Value>,
197        ) -> Pin<
198            Box<
199                dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
200                    + Send
201                    + 'a,
202            >,
203        > {
204            Box::pin(async move { Ok(inputs) })
205        }
206
207        apcore::decorator::FunctionModule::new::<_, ()>(
208            annotations,
209            input_schema,
210            output_schema,
211            passthrough,
212        )
213    }
214}
215
216/// Verify that a module was successfully registered and is retrievable.
217fn verify_registry(result: &WriteResult, module_id: &str, registry: &Registry) -> WriteResult {
218    let verifier = RegistryVerifier::new(registry);
219    let vr = verifier.verify("", module_id);
220    if vr.ok {
221        result.clone()
222    } else {
223        WriteResult::failed(module_id.into(), None, vr.error.unwrap_or_default())
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use serde_json::json;
231
232    fn sample_module() -> ScannedModule {
233        ScannedModule::new(
234            "users.get".into(),
235            "Get user".into(),
236            json!({"type": "object"}),
237            json!({"type": "object"}),
238            vec!["users".into()],
239            "app:get_user".into(),
240        )
241    }
242
243    #[test]
244    fn test_write_dry_run() {
245        let writer = RegistryWriter::new();
246        let mut registry = Registry::new();
247        let modules = vec![sample_module()];
248        let results = writer.write(&modules, &mut registry, true, false, None);
249        assert_eq!(results.len(), 1);
250        assert_eq!(results[0].module_id, "users.get");
251        assert!(!registry.has("users.get"));
252    }
253
254    #[test]
255    fn test_write_registers_module() {
256        let writer = RegistryWriter::new();
257        let mut registry = Registry::new();
258        let modules = vec![sample_module()];
259        let results = writer.write(&modules, &mut registry, false, false, None);
260        assert_eq!(results.len(), 1);
261        assert!(registry.has("users.get"));
262    }
263
264    #[test]
265    fn test_write_with_verify() {
266        let writer = RegistryWriter::new();
267        let mut registry = Registry::new();
268        let modules = vec![sample_module()];
269        let results = writer.write(&modules, &mut registry, false, true, None);
270        assert_eq!(results.len(), 1);
271        assert!(results[0].verified);
272    }
273
274    #[test]
275    fn test_write_empty_list() {
276        let writer = RegistryWriter::new();
277        let mut registry = Registry::new();
278        let results = writer.write(&[], &mut registry, false, false, None);
279        assert!(results.is_empty());
280    }
281
282    #[test]
283    fn test_write_multiple_modules() {
284        let writer = RegistryWriter::new();
285        let mut registry = Registry::new();
286        let modules = vec![
287            ScannedModule::new(
288                "mod.a".into(),
289                "A".into(),
290                json!({"type": "object"}),
291                json!({"type": "object"}),
292                vec![],
293                "app:a".into(),
294            ),
295            ScannedModule::new(
296                "mod.b".into(),
297                "B".into(),
298                json!({"type": "object"}),
299                json!({"type": "object"}),
300                vec![],
301                "app:b".into(),
302            ),
303        ];
304        let results = writer.write(&modules, &mut registry, false, false, None);
305        assert_eq!(results.len(), 2);
306        assert!(registry.has("mod.a"));
307        assert!(registry.has("mod.b"));
308        assert!(results[0].verified);
309        assert!(results[1].verified);
310    }
311}