⎇ 90e5bd3c7307ac5cce67133f523687b9b1eff48c
crates/ente-brain/src/loader.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
1//! Loader de Cards y Reglas desde archivos JSON.
2//!
3//! Sustituye al antiguo `kcl_loader.rs` (eliminado): la rama KCL invocaba
4//! un subprocess al CLI Go `kcl` que ningún target real tenía instalado y
5//! cuya validación duplicaba `EntityCard::validate()`. La fuente de verdad
6//! del shape de la Card es Rust + serde; en disco se guarda JSON crudo.
7//!
8//! Ergonomía de autoría futura (RON, Dhall, etc.) se añade como ramas
9//! adicionales aquí cuando duela escribir JSON a mano. Hoy: una sola rama.
10
11use crate::rules::Rule;
12use ente_card::EntityCard;
13use std::path::Path;
14use tracing::info;
15
16/// Carga una `EntityCard` desde un archivo JSON. Pasa por
17/// `EntityCard::validate()` antes de devolver — falla rápida.
18pub fn load_card_file(path: &Path) -> anyhow::Result<EntityCard> {
19 info!(path = %path.display(), "cargando Card desde JSON");
20 let raw = std::fs::read_to_string(path)?;
21 let card = extract_card_from_json(&raw)?;
22 card.validate()
23 .map_err(|e| anyhow::anyhow!("Card inválida ({}): {e}", path.display()))?;
24 Ok(card)
25}
26
27/// Extrae una `EntityCard` de JSON. Acepta:
28/// 1. Object directamente serializable como EntityCard.
29/// 2. Object dict con un único valor que sea EntityCard (compat con
30/// salidas de generadores que envuelven en `{"seed": {...}}`).
31pub fn extract_card_from_json(raw: &str) -> anyhow::Result<EntityCard> {
32 let v: serde_json::Value = serde_json::from_str(raw)?;
33 let direct_err = match serde_json::from_value::<EntityCard>(v.clone()) {
34 Ok(c) => return Ok(c),
35 Err(e) => e,
36 };
37 if let serde_json::Value::Object(map) = v {
38 for (_, vv) in map {
39 if let Ok(c) = serde_json::from_value::<EntityCard>(vv) {
40 return Ok(c);
41 }
42 }
43 }
44 // Propagamos el error del intento directo: es el caso típico (JSON top-level
45 // = EntityCard) y su mensaje apunta al campo concreto que rompió.
46 anyhow::bail!("JSON no contiene una EntityCard válida: {direct_err}")
47}
48
49/// Carga reglas desde un archivo JSON.
50pub fn load_rules_file(path: &Path) -> anyhow::Result<Vec<Rule>> {
51 info!(path = %path.display(), "cargando reglas desde JSON");
52 let raw = std::fs::read_to_string(path)?;
53 extract_rules_from_json(&raw)
54}
55
56/// Extrae un `Vec<Rule>` de JSON que puede ser:
57/// 1. Array directo: `[{...}, {...}]`
58/// 2. Object con un campo array: `{"rules": [...]}`
59pub fn extract_rules_from_json(raw: &str) -> anyhow::Result<Vec<Rule>> {
60 let v: serde_json::Value = serde_json::from_str(raw)?;
61 let arr = match v {
62 serde_json::Value::Array(_) => v,
63 serde_json::Value::Object(map) => map
64 .into_values()
65 .find(|x| x.is_array())
66 .ok_or_else(|| anyhow::anyhow!("JSON no contiene ningún array"))?,
67 _ => anyhow::bail!("JSON debe ser array o object con campo array"),
68 };
69 let rules: Vec<Rule> = serde_json::from_value(arr)?;
70 Ok(rules)
71}