//! Loader de Cards y Reglas desde archivos JSON. //! //! Sustituye al antiguo `kcl_loader.rs` (eliminado): la rama KCL invocaba //! un subprocess al CLI Go `kcl` que ningún target real tenía instalado y //! cuya validación duplicaba `EntityCard::validate()`. La fuente de verdad //! del shape de la Card es Rust + serde; en disco se guarda JSON crudo. //! //! Ergonomía de autoría futura (RON, Dhall, etc.) se añade como ramas //! adicionales aquí cuando duela escribir JSON a mano. Hoy: una sola rama. use crate::rules::Rule; use ente_card::EntityCard; use std::path::Path; use tracing::info; /// Carga una `EntityCard` desde un archivo JSON. Pasa por /// `EntityCard::validate()` antes de devolver — falla rápida. pub fn load_card_file(path: &Path) -> anyhow::Result { info!(path = %path.display(), "cargando Card desde JSON"); let raw = std::fs::read_to_string(path)?; let card = extract_card_from_json(&raw)?; card.validate() .map_err(|e| anyhow::anyhow!("Card inválida ({}): {e}", path.display()))?; Ok(card) } /// Extrae una `EntityCard` de JSON. Acepta: /// 1. Object directamente serializable como EntityCard. /// 2. Object dict con un único valor que sea EntityCard (compat con /// salidas de generadores que envuelven en `{"seed": {...}}`). pub fn extract_card_from_json(raw: &str) -> anyhow::Result { let v: serde_json::Value = serde_json::from_str(raw)?; let direct_err = match serde_json::from_value::(v.clone()) { Ok(c) => return Ok(c), Err(e) => e, }; if let serde_json::Value::Object(map) = v { for (_, vv) in map { if let Ok(c) = serde_json::from_value::(vv) { return Ok(c); } } } // Propagamos el error del intento directo: es el caso típico (JSON top-level // = EntityCard) y su mensaje apunta al campo concreto que rompió. anyhow::bail!("JSON no contiene una EntityCard válida: {direct_err}") } /// Carga reglas desde un archivo JSON. pub fn load_rules_file(path: &Path) -> anyhow::Result> { info!(path = %path.display(), "cargando reglas desde JSON"); let raw = std::fs::read_to_string(path)?; extract_rules_from_json(&raw) } /// Extrae un `Vec` de JSON que puede ser: /// 1. Array directo: `[{...}, {...}]` /// 2. Object con un campo array: `{"rules": [...]}` pub fn extract_rules_from_json(raw: &str) -> anyhow::Result> { let v: serde_json::Value = serde_json::from_str(raw)?; let arr = match v { serde_json::Value::Array(_) => v, serde_json::Value::Object(map) => map .into_values() .find(|x| x.is_array()) .ok_or_else(|| anyhow::anyhow!("JSON no contiene ningún array"))?, _ => anyhow::bail!("JSON debe ser array o object con campo array"), }; let rules: Vec = serde_json::from_value(arr)?; Ok(rules) }