//! Provider registry: selects concrete providers for core resources (e.g. FileBox). //! SSOT (Single Source of Truth) for provider selection via ProviderFactory registration. use std::sync::{Arc, Mutex, OnceLock}; use crate::boxes::file::provider::FileIo; use crate::boxes::file::core_ro::CoreRoFileIo; #[allow(dead_code)] pub enum FileBoxMode { Auto, CoreRo, PluginOnly } /// Factory for creating FileIo providers pub trait ProviderFactory: Send + Sync { fn box_name(&self) -> &str; fn create_provider(&self) -> Arc; fn is_available(&self) -> bool; fn priority(&self) -> i32 { 0 // Default priority (higher = preferred) } } /// Global registry of provider factories static PROVIDER_FACTORIES: OnceLock>>> = OnceLock::new(); /// Register a provider factory (called by builtin/dynamic loaders) pub fn register_provider_factory(factory: Arc) { let registry = PROVIDER_FACTORIES.get_or_init(|| Mutex::new(Vec::new())); registry.lock().unwrap().push(factory); } /// Built‑in ring‑1 FileBox provider (core‑ro) — always available, lowest priority struct CoreRoFileProviderFactory; impl ProviderFactory for CoreRoFileProviderFactory { fn box_name(&self) -> &str { "FileBox" } fn create_provider(&self) -> Arc { Arc::new(CoreRoFileIo::new()) } fn is_available(&self) -> bool { true } fn priority(&self) -> i32 { -100 } // ring‑1: lower than any plugin/provider } /// Ensure ring‑1 (core‑ro) provider is present in the registry fn ensure_builtin_file_provider_registered() { let reg = PROVIDER_FACTORIES.get_or_init(|| Mutex::new(Vec::new())); let mut guard = reg.lock().unwrap(); // If at least one FileBox provider exists, we still keep ring‑1 present for safety; avoid duplicates by checking any core‑ro present by priority let has_core_ro = guard.iter().any(|f| f.box_name() == "FileBox" && f.priority() <= -100); if !has_core_ro { guard.push(Arc::new(CoreRoFileProviderFactory)); } } /// Read FileBox mode from environment variables #[allow(dead_code)] pub fn read_filebox_mode_from_env() -> FileBoxMode { match std::env::var("NYASH_FILEBOX_MODE").unwrap_or_else(|_| "auto".to_string()).as_str() { "core-ro" => FileBoxMode::CoreRo, "plugin-only" => FileBoxMode::PluginOnly, _ => { if std::env::var("NYASH_DISABLE_PLUGINS").as_deref() == Ok("1") { FileBoxMode::CoreRo } else { FileBoxMode::Auto } } } } /// Select provider based on mode and registered factories (SSOT) #[allow(dead_code)] pub fn select_file_provider(mode: FileBoxMode) -> Arc { let quiet_pipe = crate::config::env::env_bool("NYASH_JSON_ONLY"); // Always ensure ring‑1 (core‑ro) exists before inspecting registry ensure_builtin_file_provider_registered(); let registry = PROVIDER_FACTORIES.get(); match mode { FileBoxMode::Auto => { // Try: dynamic/builtin (by priority) → core-ro fallback if let Some(reg) = registry { let mut factories: Vec<_> = reg.lock().unwrap() .iter() .filter(|f| f.box_name() == "FileBox" && f.is_available()) .cloned() .collect(); // Sort by priority (descending) factories.sort_by(|a, b| b.priority().cmp(&a.priority())); if let Some(factory) = factories.first() { if !quiet_pipe { eprintln!("[provider-registry] FileBox: using registered provider (priority={})", factory.priority()); } return factory.create_provider(); } } // Fallback policy // Allow a narrow, explicit carve‑out: // - When JSON‑only pipeline is active (quiet structured I/O), or // - When NYASH_FILEBOX_ALLOW_FALLBACK=1 is set, // always use core‑ro provider even if Fail‑Fast is ON. let allow_fb_override = crate::config::env::env_bool("NYASH_JSON_ONLY") || crate::config::env::env_bool("NYASH_FILEBOX_ALLOW_FALLBACK"); if crate::config::env::fail_fast() && !allow_fb_override { eprintln!("[failfast/provider/filebox:auto-fallback-blocked]"); panic!("Fail-Fast: FileBox provider fallback is disabled (NYASH_FAIL_FAST=0 or NYASH_FILEBOX_ALLOW_FALLBACK=1 to override)"); } else { if !quiet_pipe { eprintln!( "[provider-registry] FileBox: using core-ro fallback{}", if allow_fb_override { " (override)" } else { "" } ); } Arc::new(CoreRoFileIo::new()) } } FileBoxMode::PluginOnly => { // Try only registered providers, Fail-Fast if none available if let Some(reg) = registry { let mut factories: Vec<_> = reg.lock().unwrap() .iter() .filter(|f| f.box_name() == "FileBox" && f.is_available()) .cloned() .collect(); factories.sort_by(|a, b| b.priority().cmp(&a.priority())); if let Some(factory) = factories.first() { if !quiet_pipe { eprintln!("[provider-registry] FileBox: using plugin-only provider (priority={})", factory.priority()); } return factory.create_provider(); } } panic!("FileBox plugin-only mode: no provider registered. Set NYASH_FILEBOX_MODE=auto or NYASH_FILEBOX_MODE=core-ro to use fallback."); } FileBoxMode::CoreRo => { // Always use core-ro, ignore registry if !quiet_pipe { eprintln!("[provider-registry] FileBox: using core-ro (forced)"); } Arc::new(CoreRoFileIo::new()) } } }