Files
brahman/crates/protocol/brahman-cards/src/readers.rs
T
sergio b83d40a833 refactor(naming): A1 — ente→arje, vista→revista, pluma→fana
Rename batch de la Fase A del PLAN_MACRO:
- 25 crates ente-* → arje-* (protocol/init/runtime/compat). El linaje
  arje (init Linux) queda con prefijo coherente.
- vista → revista (revista-core + revista-web).
- pluma → fana (fana-md + fana-md-reader-web). fana absorbe el linaje
  markdown de pluma; será el writer DAG editor (prioridad alta).

Cambios:
- git mv de 29 crate dirs + 2 SDDs
- package/lib/bin names + path refs + imports .rs reescritos
- workspace Cargo.toml + comentarios de sección
- SDDs de init/runtime/compat/protocol actualizados a arje-
- SDD de revista + SDD de fana (reescrito: writer DAG editor)
- docs/STATUS.md, ROADMAP.md, PLAN_MACRO.md, arje-boot.md,
  arje-replace-systemd.md actualizados
- docs/changelog/akasha.md → chasqui.md

scripts/rename-fase-a.py idempotente (--dry-run soportado).
cargo check --workspace verde.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:10:14 +00:00

169 lines
5.8 KiB
Rust

//! Readers V1: tres formatos JSON ya existentes en el monorepo.
//!
//! Cada reader implementa:
//! - `can_read`: heurística estructural para decidir si el JSON es
//! suyo. No requiere flag explícito en el input — los inputs legacy
//! no los tienen.
//! - `read`: deserializa el JSON al tipo del crate origen (sin tocarlo)
//! y lo envuelve en [`Card`] derivando los campos del wrapper.
//!
//! Convenciones para derivar el wrapper:
//! - `id`: del campo `id` del input (cada formato lo expone). Si es
//! ULID se serializa a string canónico.
//! - `label`: del campo `label`.
//! - `lineage`: del campo `lineage` cuando existe (Ente/Monad).
//! - `extensions`: campos JSON desconocidos respecto a la struct del
//! crate origen. Hoy lo mantenemos vacío (los crates origen ya
//! tienen sus propios `extensions` internos via `#[serde(flatten)]`)
//! — no duplicamos. Si en el futuro queremos mover el "extras" del
//! crate origen al wrapper, esta es la palanca.
use serde_json::Value;
use crate::{Card, CardBody, CardLoadError, CardReader, EnteCard, MonadManifest, UiModuleSpec, CARD_SCHEMA_VERSION};
// ============================================================================
// Ente (brahman-card)
// ============================================================================
/// Reader para el shape JSON de [`brahman_card::Card`].
///
/// Heurística de detección: el input tiene `payload` Y `supervision`
/// — son los campos requeridos del schema Ente que ningún otro
/// formato del monorepo tiene.
pub struct EnteJsonReader;
impl CardReader for EnteJsonReader {
fn name(&self) -> &'static str {
"ente-json"
}
fn can_read(&self, input: &Value) -> bool {
let obj = match input.as_object() {
Some(o) => o,
None => return false,
};
obj.contains_key("payload") && obj.contains_key("supervision")
}
fn read(&self, input: Value) -> Result<Card, CardLoadError> {
let id = pull_string(&input, "id").unwrap_or_default();
let label = pull_string(&input, "label").unwrap_or_default();
let lineage = pull_string(&input, "lineage");
let ente: EnteCard =
serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
reader: "ente-json",
message: e.to_string(),
})?;
Ok(Card {
id,
schema_version: CARD_SCHEMA_VERSION,
lineage,
label,
extensions: Default::default(),
body: CardBody::Ente(ente),
})
}
}
// ============================================================================
// Monad (chasqui-card)
// ============================================================================
/// Reader para el shape JSON de [`chasqui_card::MonadManifest`].
///
/// Heurística: tiene `members` (BTreeSet<FileId>) Y `cardinality`
/// (u32). La combinación es exclusiva del MonadManifest.
pub struct MonadJsonReader;
impl CardReader for MonadJsonReader {
fn name(&self) -> &'static str {
"monad-json"
}
fn can_read(&self, input: &Value) -> bool {
let obj = match input.as_object() {
Some(o) => o,
None => return false,
};
obj.contains_key("members") && obj.contains_key("cardinality")
}
fn read(&self, input: Value) -> Result<Card, CardLoadError> {
let id = pull_string(&input, "id").unwrap_or_default();
let label = pull_string(&input, "label").unwrap_or_default();
let lineage = pull_string(&input, "lineage");
let monad: MonadManifest =
serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
reader: "monad-json",
message: e.to_string(),
})?;
Ok(Card {
id,
schema_version: CARD_SCHEMA_VERSION,
lineage,
label,
extensions: Default::default(),
body: CardBody::Monad(monad),
})
}
}
// ============================================================================
// UiModule (nahual-meta-schema)
// ============================================================================
/// Reader para el shape JSON de los `module.json` de la metainterfaz
/// ([`nahual_meta_schema::Module`]).
///
/// Heurística: tiene `entities` Y `views` Y `menu`. Es el shape más
/// específico del repo, así que va primero en el orden default — si
/// matchea, ningún otro reader debería intentar.
pub struct UiModuleJsonReader;
impl CardReader for UiModuleJsonReader {
fn name(&self) -> &'static str {
"ui-module-json"
}
fn can_read(&self, input: &Value) -> bool {
let obj = match input.as_object() {
Some(o) => o,
None => return false,
};
obj.contains_key("entities") && obj.contains_key("views") && obj.contains_key("menu")
}
fn read(&self, input: Value) -> Result<Card, CardLoadError> {
let id = pull_string(&input, "id").unwrap_or_default();
let label = pull_string(&input, "label").unwrap_or_default();
// UiModule no tiene lineage en su schema, queda None.
let module: UiModuleSpec =
serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
reader: "ui-module-json",
message: e.to_string(),
})?;
Ok(Card {
id,
schema_version: CARD_SCHEMA_VERSION,
lineage: None,
label,
extensions: Default::default(),
body: CardBody::UiModule(module),
})
}
}
// ============================================================================
// Helpers
// ============================================================================
fn pull_string(v: &Value, key: &str) -> Option<String> {
v.get(key)?.as_str().map(|s| s.to_string())
}