1use std::pin::Pin;
11use std::sync::Arc;
12
13use tracing::{debug, warn};
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
23pub type HandlerFn = Arc<
30 dyn for<'a> Fn(
31 serde_json::Value,
32 &'a Context<serde_json::Value>,
33 ) -> Pin<
34 Box<
35 dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
36 + Send
37 + 'a,
38 >,
39 > + Send
40 + Sync,
41>;
42
43pub type HandlerFactory = Arc<dyn Fn(&str) -> Option<HandlerFn> + Send + Sync>;
60
61pub struct RegistryWriter {
75 handler_factory: Option<HandlerFactory>,
76 allowed_prefixes: Option<Vec<String>>,
82}
83
84impl Default for RegistryWriter {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl RegistryWriter {
91 pub fn new() -> Self {
110 Self {
111 handler_factory: None,
112 allowed_prefixes: None,
113 }
114 }
115
116 pub fn with_handler_factory(factory: HandlerFactory) -> Self {
118 Self {
119 handler_factory: Some(factory),
120 allowed_prefixes: None,
121 }
122 }
123
124 pub fn with_allowed_prefixes(mut self, prefixes: Vec<String>) -> Self {
133 self.allowed_prefixes = Some(prefixes);
134 self
135 }
136
137 fn target_allowed(&self, target: &str) -> bool {
140 match self.allowed_prefixes.as_ref() {
141 None => true,
142 Some(prefixes) => prefixes.iter().any(|p| target.starts_with(p.as_str())),
143 }
144 }
145}
146
147impl RegistryWriter {
148 pub fn write(
163 &self,
164 modules: &[ScannedModule],
165 registry: &mut Registry,
166 dry_run: bool,
167 verify: bool,
168 verifiers: Option<&[&dyn Verifier]>,
169 ) -> Vec<WriteResult> {
170 let mut results: Vec<WriteResult> = Vec::new();
171
172 for module in modules {
173 if dry_run {
174 results.push(WriteResult::new(module.module_id.clone()));
175 continue;
176 }
177
178 if !self.target_allowed(&module.target) {
179 warn!(
180 module_id = %module.module_id,
181 target = %module.target,
182 "RegistryWriter: target rejected by allowed_prefixes"
183 );
184 results.push(WriteResult::failed(
185 module.module_id.clone(),
186 None,
187 format!(
188 "target '{}' is not in allowed_prefixes — registration refused",
189 module.target
190 ),
191 ));
192 continue;
193 }
194
195 let fm = self.to_function_module(module);
196 let descriptor = apcore::registry::registry::ModuleDescriptor {
198 module_id: module.module_id.clone(),
199 name: Some(module.module_id.clone()),
200 description: module.description.clone(),
201 documentation: module.documentation.clone(),
202 input_schema: module.input_schema.clone(),
203 output_schema: module.output_schema.clone(),
204 version: module.version.clone(),
205 tags: module.tags.clone(),
206 annotations: module.annotations.clone(),
207 examples: module.examples.clone(),
208 metadata: module.metadata.clone(),
209 display: module.display.clone(),
210 sunset_date: None,
211 dependencies: vec![],
212 enabled: true,
213 };
214 if let Err(e) = registry.register(&module.module_id, Box::new(fm), descriptor) {
218 warn!(
219 module_id = %module.module_id,
220 error = %e,
221 "RegistryWriter registration failed"
222 );
223 results.push(WriteResult::failed(
224 module.module_id.clone(),
225 None,
226 format!("Registration failed: {e}"),
227 ));
228 continue;
229 }
230 debug!("Registered module: {}", module.module_id);
231
232 let mut result = WriteResult::new(module.module_id.clone());
233 if verify {
234 result = verify_registry(&result, &module.module_id, registry);
235 }
236 if result.verified {
237 if let Some(vs) = verifiers {
238 let chain_result = run_verifier_chain(vs, "", &module.module_id);
239 if !chain_result.ok {
240 result = WriteResult::failed(
241 result.module_id,
242 result.path,
243 chain_result.error.unwrap_or_default(),
244 );
245 }
246 }
247 }
248 results.push(result);
249 }
250
251 results
252 }
253}
254
255impl RegistryWriter {
256 fn to_function_module(&self, module: &ScannedModule) -> apcore::decorator::FunctionModule {
262 let annotations = module.annotations.clone().unwrap_or_default();
263 let input_schema = module.input_schema.clone();
264 let output_schema = module.output_schema.clone();
265
266 if let Some(factory) = &self.handler_factory {
268 if let Some(handler) = factory(&module.target) {
269 return apcore::decorator::FunctionModule::new::<_, ()>(
270 annotations,
271 input_schema,
272 output_schema,
273 move |inputs: serde_json::Value,
274 ctx: &Context<serde_json::Value>|
275 -> Pin<
276 Box<
277 dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
278 + Send
279 + '_,
280 >,
281 > { handler(inputs, ctx) },
282 );
283 }
284 }
285
286 debug!(
288 module_id = %module.module_id,
289 "RegistryWriter using passthrough handler (no HandlerFactory configured)",
290 );
291 fn passthrough<'a>(
292 inputs: serde_json::Value,
293 _ctx: &'a Context<serde_json::Value>,
294 ) -> Pin<
295 Box<
296 dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
297 + Send
298 + 'a,
299 >,
300 > {
301 Box::pin(async move { Ok(inputs) })
302 }
303
304 apcore::decorator::FunctionModule::new::<_, ()>(
305 annotations,
306 input_schema,
307 output_schema,
308 passthrough,
309 )
310 }
311}
312
313fn verify_registry(result: &WriteResult, module_id: &str, registry: &Registry) -> WriteResult {
315 let verifier = RegistryVerifier::new(registry);
316 let vr = verifier.verify("", module_id);
317 if vr.ok {
318 result.clone()
319 } else {
320 WriteResult::failed(module_id.into(), None, vr.error.unwrap_or_default())
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use serde_json::json;
328
329 fn sample_module() -> ScannedModule {
330 ScannedModule::new(
331 "users.get".into(),
332 "Get user".into(),
333 json!({"type": "object"}),
334 json!({"type": "object"}),
335 vec!["users".into()],
336 "app:get_user".into(),
337 )
338 }
339
340 #[test]
341 fn test_write_dry_run() {
342 let writer = RegistryWriter::new();
343 let mut registry = Registry::new();
344 let modules = vec![sample_module()];
345 let results = writer.write(&modules, &mut registry, true, false, None);
346 assert_eq!(results.len(), 1);
347 assert_eq!(results[0].module_id, "users.get");
348 assert!(!registry.has("users.get"));
349 }
350
351 #[test]
352 fn test_write_registers_module() {
353 let writer = RegistryWriter::new();
354 let mut registry = Registry::new();
355 let modules = vec![sample_module()];
356 let results = writer.write(&modules, &mut registry, false, false, None);
357 assert_eq!(results.len(), 1);
358 assert!(registry.has("users.get"));
359 }
360
361 #[test]
362 fn test_write_with_verify() {
363 let writer = RegistryWriter::new();
364 let mut registry = Registry::new();
365 let modules = vec![sample_module()];
366 let results = writer.write(&modules, &mut registry, false, true, None);
367 assert_eq!(results.len(), 1);
368 assert!(results[0].verified);
369 }
370
371 #[test]
372 fn test_write_empty_list() {
373 let writer = RegistryWriter::new();
374 let mut registry = Registry::new();
375 let results = writer.write(&[], &mut registry, false, false, None);
376 assert!(results.is_empty());
377 }
378
379 #[test]
380 fn test_custom_verifier_runs_even_when_verify_false() {
381 use crate::output::types::{Verifier, VerifyResult};
385
386 struct AlwaysFail;
387 impl Verifier for AlwaysFail {
388 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
389 VerifyResult::fail("custom verifier failed".into())
390 }
391 }
392
393 let writer = RegistryWriter::new();
394 let mut registry = Registry::new();
395 let modules = vec![sample_module()];
396 let failing_verifier = AlwaysFail;
397 let verifiers: &[&dyn Verifier] = &[&failing_verifier];
398 let results = writer.write(&modules, &mut registry, false, false, Some(verifiers));
400 assert_eq!(results.len(), 1);
401 assert!(registry.has("users.get"));
403 assert!(
405 !results[0].verified,
406 "custom verifier must run even when verify=false; result: {:?}",
407 results[0]
408 );
409 assert!(
410 results[0]
411 .verification_error
412 .as_deref()
413 .unwrap_or("")
414 .contains("custom verifier failed"),
415 "verification_error should contain the custom verifier message"
416 );
417 }
418
419 #[test]
420 fn test_write_multiple_modules() {
421 let writer = RegistryWriter::new();
422 let mut registry = Registry::new();
423 let modules = vec![
424 ScannedModule::new(
425 "mod.a".into(),
426 "A".into(),
427 json!({"type": "object"}),
428 json!({"type": "object"}),
429 vec![],
430 "app:a".into(),
431 ),
432 ScannedModule::new(
433 "mod.b".into(),
434 "B".into(),
435 json!({"type": "object"}),
436 json!({"type": "object"}),
437 vec![],
438 "app:b".into(),
439 ),
440 ];
441 let results = writer.write(&modules, &mut registry, false, false, None);
442 assert_eq!(results.len(), 2);
443 assert!(registry.has("mod.a"));
444 assert!(registry.has("mod.b"));
445 assert!(results[0].verified);
446 assert!(results[1].verified);
447 }
448
449 #[test]
453 fn test_allowed_prefixes_rejects_non_matching_target() {
454 let writer =
455 RegistryWriter::new().with_allowed_prefixes(vec!["app:".into(), "myapp:".into()]);
456 let mut registry = Registry::new();
457 let allowed = sample_module(); let denied = ScannedModule::new(
459 "evil.module".into(),
460 "Forged target".into(),
461 json!({"type": "object"}),
462 json!({"type": "object"}),
463 vec![],
464 "evil:run_attacker_code".into(),
465 );
466 let results = writer.write(&[allowed, denied], &mut registry, false, false, None);
467 assert_eq!(results.len(), 2);
468 assert!(registry.has("users.get"));
470 assert!(results[0].verified);
471 assert!(!registry.has("evil.module"));
473 assert!(!results[1].verified);
474 let err = results[1].verification_error.as_deref().unwrap_or("");
475 assert!(
476 err.contains("allowed_prefixes"),
477 "rejection message should mention allowed_prefixes: got {err:?}"
478 );
479 }
480
481 #[test]
482 fn test_allowed_prefixes_default_none_admits_everything() {
483 let writer = RegistryWriter::new();
487 let mut registry = Registry::new();
488 let module = ScannedModule::new(
489 "any.module".into(),
490 "Any target".into(),
491 json!({"type": "object"}),
492 json!({"type": "object"}),
493 vec![],
494 "anything-goes:func".into(),
495 );
496 let results = writer.write(&[module], &mut registry, false, false, None);
497 assert_eq!(results.len(), 1);
498 assert!(registry.has("any.module"));
499 }
500}