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<
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
38pub type HandlerFactory = Arc<dyn Fn(&str) -> Option<HandlerFn> + Send + Sync>;
55
56pub struct RegistryWriter {
70 handler_factory: Option<HandlerFactory>,
71 allowed_prefixes: Option<Vec<String>>,
77}
78
79impl Default for RegistryWriter {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl RegistryWriter {
86 pub fn new() -> Self {
105 Self {
106 handler_factory: None,
107 allowed_prefixes: None,
108 }
109 }
110
111 pub fn with_handler_factory(factory: HandlerFactory) -> Self {
113 Self {
114 handler_factory: Some(factory),
115 allowed_prefixes: None,
116 }
117 }
118
119 pub fn with_allowed_prefixes(mut self, prefixes: Vec<String>) -> Self {
128 self.allowed_prefixes = Some(prefixes);
129 self
130 }
131
132 fn target_allowed(&self, target: &str) -> bool {
140 match self.allowed_prefixes.as_ref() {
141 None => true,
142 Some(prefixes) => {
143 let module_path = target.split(':').next().unwrap_or(target);
144 prefixes
145 .iter()
146 .any(|p| module_path_matches_prefix(module_path, p))
147 }
148 }
149 }
150}
151
152fn module_path_matches_prefix(module_path: &str, prefix: &str) -> bool {
159 let normalized = prefix.trim_end_matches('.');
160 if normalized.is_empty() {
161 return false;
162 }
163 if module_path == normalized {
164 return true;
165 }
166 let mut boundary = String::with_capacity(normalized.len() + 1);
167 boundary.push_str(normalized);
168 boundary.push('.');
169 module_path.starts_with(&boundary)
170}
171
172impl RegistryWriter {
173 pub fn write(
188 &self,
189 modules: &[ScannedModule],
190 registry: &mut Registry,
191 dry_run: bool,
192 verify: bool,
193 verifiers: Option<&[&dyn Verifier]>,
194 ) -> Vec<WriteResult> {
195 let mut results: Vec<WriteResult> = Vec::new();
196
197 for module in modules {
198 if dry_run {
199 results.push(WriteResult::new(module.module_id.clone()));
200 continue;
201 }
202
203 if !self.target_allowed(&module.target) {
204 warn!(
205 module_id = %module.module_id,
206 target = %module.target,
207 "RegistryWriter: target rejected by allowed_prefixes"
208 );
209 results.push(WriteResult::failed(
210 module.module_id.clone(),
211 None,
212 format!(
213 "target '{}' is not in allowed_prefixes — registration refused",
214 module.target
215 ),
216 ));
217 continue;
218 }
219
220 let fm = self.to_function_module(module);
221 let descriptor = apcore::registry::registry::ModuleDescriptor {
223 module_id: module.module_id.clone(),
224 name: Some(module.module_id.clone()),
225 description: module.description.clone(),
226 documentation: module.documentation.clone(),
227 input_schema: module.input_schema.clone(),
228 output_schema: module.output_schema.clone(),
229 version: module.version.clone(),
230 tags: module.tags.clone(),
231 annotations: module.annotations.clone(),
232 examples: module.examples.clone(),
233 metadata: module.metadata.clone(),
234 display: module.display.clone(),
235 sunset_date: None,
236 dependencies: vec![],
237 enabled: true,
238 };
239 if let Err(e) = registry.register(&module.module_id, Box::new(fm), descriptor) {
243 warn!(
244 module_id = %module.module_id,
245 error = %e,
246 "RegistryWriter registration failed"
247 );
248 results.push(WriteResult::failed(
249 module.module_id.clone(),
250 None,
251 format!("Registration failed: {e}"),
252 ));
253 continue;
254 }
255 debug!("Registered module: {}", module.module_id);
256
257 let mut result = WriteResult::new(module.module_id.clone());
258 if verify {
259 result = verify_registry(&result, &module.module_id, registry);
260 }
261 if result.verified {
262 if let Some(vs) = verifiers {
263 let chain_result = run_verifier_chain(vs, "", &module.module_id);
264 if !chain_result.ok {
265 result = WriteResult::failed(
266 result.module_id,
267 result.path,
268 chain_result.error.unwrap_or_default(),
269 );
270 }
271 }
272 }
273 results.push(result);
274 }
275
276 results
277 }
278}
279
280impl RegistryWriter {
281 fn to_function_module(&self, module: &ScannedModule) -> apcore::decorator::FunctionModule {
287 let annotations = module.annotations.clone().unwrap_or_default();
288 let input_schema = module.input_schema.clone();
289 let output_schema = module.output_schema.clone();
290
291 if let Some(factory) = &self.handler_factory {
293 if let Some(handler) = factory(&module.target) {
294 return apcore::decorator::FunctionModule::new::<_, ()>(
295 annotations,
296 input_schema,
297 output_schema,
298 move |inputs: serde_json::Value,
299 ctx: &Context<serde_json::Value>|
300 -> Pin<
301 Box<
302 dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
303 + Send
304 + '_,
305 >,
306 > { handler(inputs, ctx) },
307 );
308 }
309 }
310
311 debug!(
313 module_id = %module.module_id,
314 "RegistryWriter using passthrough handler (no HandlerFactory configured)",
315 );
316 fn passthrough<'a>(
317 inputs: serde_json::Value,
318 _ctx: &'a Context<serde_json::Value>,
319 ) -> Pin<
320 Box<
321 dyn std::future::Future<Output = Result<serde_json::Value, ModuleError>>
322 + Send
323 + 'a,
324 >,
325 > {
326 Box::pin(async move { Ok(inputs) })
327 }
328
329 apcore::decorator::FunctionModule::new::<_, ()>(
330 annotations,
331 input_schema,
332 output_schema,
333 passthrough,
334 )
335 }
336}
337
338fn verify_registry(result: &WriteResult, module_id: &str, registry: &Registry) -> WriteResult {
340 let verifier = RegistryVerifier::new(registry);
341 let vr = verifier.verify("", module_id);
342 if vr.ok {
343 result.clone()
344 } else {
345 WriteResult::failed(module_id.into(), None, vr.error.unwrap_or_default())
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use serde_json::json;
353
354 fn sample_module() -> ScannedModule {
355 ScannedModule::new(
356 "users.get".into(),
357 "Get user".into(),
358 json!({"type": "object"}),
359 json!({"type": "object"}),
360 vec!["users".into()],
361 "app:get_user".into(),
362 )
363 }
364
365 #[test]
366 fn test_write_dry_run() {
367 let writer = RegistryWriter::new();
368 let mut registry = Registry::new();
369 let modules = vec![sample_module()];
370 let results = writer.write(&modules, &mut registry, true, false, None);
371 assert_eq!(results.len(), 1);
372 assert_eq!(results[0].module_id, "users.get");
373 assert!(!registry.has("users.get"));
374 }
375
376 #[test]
377 fn test_write_registers_module() {
378 let writer = RegistryWriter::new();
379 let mut registry = Registry::new();
380 let modules = vec![sample_module()];
381 let results = writer.write(&modules, &mut registry, false, false, None);
382 assert_eq!(results.len(), 1);
383 assert!(registry.has("users.get"));
384 }
385
386 #[test]
387 fn test_write_with_verify() {
388 let writer = RegistryWriter::new();
389 let mut registry = Registry::new();
390 let modules = vec![sample_module()];
391 let results = writer.write(&modules, &mut registry, false, true, None);
392 assert_eq!(results.len(), 1);
393 assert!(results[0].verified);
394 }
395
396 #[test]
397 fn test_write_empty_list() {
398 let writer = RegistryWriter::new();
399 let mut registry = Registry::new();
400 let results = writer.write(&[], &mut registry, false, false, None);
401 assert!(results.is_empty());
402 }
403
404 #[test]
405 fn test_custom_verifier_runs_even_when_verify_false() {
406 use crate::output::types::{Verifier, VerifyResult};
410
411 struct AlwaysFail;
412 impl Verifier for AlwaysFail {
413 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
414 VerifyResult::fail("custom verifier failed".into())
415 }
416 }
417
418 let writer = RegistryWriter::new();
419 let mut registry = Registry::new();
420 let modules = vec![sample_module()];
421 let failing_verifier = AlwaysFail;
422 let verifiers: &[&dyn Verifier] = &[&failing_verifier];
423 let results = writer.write(&modules, &mut registry, false, false, Some(verifiers));
425 assert_eq!(results.len(), 1);
426 assert!(registry.has("users.get"));
428 assert!(
430 !results[0].verified,
431 "custom verifier must run even when verify=false; result: {:?}",
432 results[0]
433 );
434 assert!(
435 results[0]
436 .verification_error
437 .as_deref()
438 .unwrap_or("")
439 .contains("custom verifier failed"),
440 "verification_error should contain the custom verifier message"
441 );
442 }
443
444 #[test]
445 fn test_write_multiple_modules() {
446 let writer = RegistryWriter::new();
447 let mut registry = Registry::new();
448 let modules = vec![
449 ScannedModule::new(
450 "mod.a".into(),
451 "A".into(),
452 json!({"type": "object"}),
453 json!({"type": "object"}),
454 vec![],
455 "app:a".into(),
456 ),
457 ScannedModule::new(
458 "mod.b".into(),
459 "B".into(),
460 json!({"type": "object"}),
461 json!({"type": "object"}),
462 vec![],
463 "app:b".into(),
464 ),
465 ];
466 let results = writer.write(&modules, &mut registry, false, false, None);
467 assert_eq!(results.len(), 2);
468 assert!(registry.has("mod.a"));
469 assert!(registry.has("mod.b"));
470 assert!(results[0].verified);
471 assert!(results[1].verified);
472 }
473
474 #[test]
478 fn test_allowed_prefixes_rejects_non_matching_target() {
479 let writer =
483 RegistryWriter::new().with_allowed_prefixes(vec!["app".into(), "myapp".into()]);
484 let mut registry = Registry::new();
485 let allowed = sample_module(); let denied = ScannedModule::new(
487 "evil.module".into(),
488 "Forged target".into(),
489 json!({"type": "object"}),
490 json!({"type": "object"}),
491 vec![],
492 "evil:run_attacker_code".into(),
493 );
494 let results = writer.write(&[allowed, denied], &mut registry, false, false, None);
495 assert_eq!(results.len(), 2);
496 assert!(registry.has("users.get"));
498 assert!(results[0].verified);
499 assert!(!registry.has("evil.module"));
501 assert!(!results[1].verified);
502 let err = results[1].verification_error.as_deref().unwrap_or("");
503 assert!(
504 err.contains("allowed_prefixes"),
505 "rejection message should mention allowed_prefixes: got {err:?}"
506 );
507 }
508
509 #[test]
514 fn test_target_allowed_boundary_aware() {
515 let writer = RegistryWriter::new().with_allowed_prefixes(vec!["myapp".into()]);
516 assert!(writer.target_allowed("myapp:fn"));
518 assert!(writer.target_allowed("myapp.foo:fn"));
520 assert!(writer.target_allowed("myapp.foo.bar:fn"));
521 assert!(!writer.target_allowed("myappx.evil:fn"));
523 assert!(!writer.target_allowed("myappx:fn"));
524 assert!(!writer.target_allowed("other:fn"));
526
527 let writer2 = RegistryWriter::new().with_allowed_prefixes(vec!["myapp.foo".into()]);
529 assert!(writer2.target_allowed("myapp.foo:fn"));
530 assert!(writer2.target_allowed("myapp.foo.bar:fn"));
531 assert!(!writer2.target_allowed("myapp.foobar:fn"));
532 assert!(!writer2.target_allowed("myapp:fn"));
533
534 let writer3 = RegistryWriter::new().with_allowed_prefixes(vec!["myapp.".into()]);
536 assert!(writer3.target_allowed("myapp:fn"));
537 let writer4 = RegistryWriter::new().with_allowed_prefixes(vec!["".into()]);
538 assert!(!writer4.target_allowed("anything:fn"));
539 }
540
541 #[test]
542 fn test_allowed_prefixes_default_none_admits_everything() {
543 let writer = RegistryWriter::new();
547 let mut registry = Registry::new();
548 let module = ScannedModule::new(
549 "any.module".into(),
550 "Any target".into(),
551 json!({"type": "object"}),
552 json!({"type": "object"}),
553 vec![],
554 "anything-goes:func".into(),
555 );
556 let results = writer.write(&[module], &mut registry, false, false, None);
557 assert_eq!(results.len(), 1);
558 assert!(registry.has("any.module"));
559 }
560}