tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

⎇ 09501911b7ec497215773d2cb7192a2e6ce17e02 crates/core/brahman-cards/src/readers.rs Rust · 168 líneas · 5.8 KB Historia Quién tocó qué crudo

feat(brahman-cards): brazo unificado V1 — readers JSON + estructura canónica 09501911b · Sergio ·

1//! Readers V1: tres formatos JSON ya existentes en el monorepo.
2//!
3//! Cada reader implementa:
4//! - `can_read`: heurística estructural para decidir si el JSON es
5//!   suyo. No requiere flag explícito en el input — los inputs legacy
6//!   no los tienen.
7//! - `read`: deserializa el JSON al tipo del crate origen (sin tocarlo)
8//!   y lo envuelve en [`Card`] derivando los campos del wrapper.
9//!
10//! Convenciones para derivar el wrapper:
11//! - `id`: del campo `id` del input (cada formato lo expone). Si es
12//!   ULID se serializa a string canónico.
13//! - `label`: del campo `label`.
14//! - `lineage`: del campo `lineage` cuando existe (Ente/Monad).
15//! - `extensions`: campos JSON desconocidos respecto a la struct del
16//!   crate origen. Hoy lo mantenemos vacío (los crates origen ya
17//!   tienen sus propios `extensions` internos via `#[serde(flatten)]`)
18//!   — no duplicamos. Si en el futuro queremos mover el "extras" del
19//!   crate origen al wrapper, esta es la palanca.
20
21use serde_json::Value;
22
23use crate::{Card, CardBody, CardLoadError, CardReader, EnteCard, MonadManifest, UiModuleSpec, CARD_SCHEMA_VERSION};
24
25// ============================================================================
26// Ente (brahman-card)
27// ============================================================================
28
29/// Reader para el shape JSON de [`brahman_card::Card`].
30///
31/// Heurística de detección: el input tiene `payload` Y `supervision`
32/// — son los campos requeridos del schema Ente que ningún otro
33/// formato del monorepo tiene.
34pub struct EnteJsonReader;
35
36impl CardReader for EnteJsonReader {
37    fn name(&self) -> &'static str {
38        "ente-json"
39    }
40
41    fn can_read(&self, input: &Value) -> bool {
42        let obj = match input.as_object() {
43            Some(o) => o,
44            None => return false,
45        };
46        obj.contains_key("payload") && obj.contains_key("supervision")
47    }
48
49    fn read(&self, input: Value) -> Result<Card, CardLoadError> {
50        let id = pull_string(&input, "id").unwrap_or_default();
51        let label = pull_string(&input, "label").unwrap_or_default();
52        let lineage = pull_string(&input, "lineage");
53
54        let ente: EnteCard =
55            serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
56                reader: "ente-json",
57                message: e.to_string(),
58            })?;
59
60        Ok(Card {
61            id,
62            schema_version: CARD_SCHEMA_VERSION,
63            lineage,
64            label,
65            extensions: Default::default(),
66            body: CardBody::Ente(ente),
67        })
68    }
69}
70
71// ============================================================================
72// Monad (nouser-card)
73// ============================================================================
74
75/// Reader para el shape JSON de [`nouser_card::MonadManifest`].
76///
77/// Heurística: tiene `members` (BTreeSet<FileId>) Y `cardinality`
78/// (u32). La combinación es exclusiva del MonadManifest.
79pub struct MonadJsonReader;
80
81impl CardReader for MonadJsonReader {
82    fn name(&self) -> &'static str {
83        "monad-json"
84    }
85
86    fn can_read(&self, input: &Value) -> bool {
87        let obj = match input.as_object() {
88            Some(o) => o,
89            None => return false,
90        };
91        obj.contains_key("members") && obj.contains_key("cardinality")
92    }
93
94    fn read(&self, input: Value) -> Result<Card, CardLoadError> {
95        let id = pull_string(&input, "id").unwrap_or_default();
96        let label = pull_string(&input, "label").unwrap_or_default();
97        let lineage = pull_string(&input, "lineage");
98
99        let monad: MonadManifest =
100            serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
101                reader: "monad-json",
102                message: e.to_string(),
103            })?;
104
105        Ok(Card {
106            id,
107            schema_version: CARD_SCHEMA_VERSION,
108            lineage,
109            label,
110            extensions: Default::default(),
111            body: CardBody::Monad(monad),
112        })
113    }
114}
115
116// ============================================================================
117// UiModule (nakui-ui-schema)
118// ============================================================================
119
120/// Reader para el shape JSON de los `module.json` de la UI Nakui
121/// ([`nakui_ui_schema::Module`]).
122///
123/// Heurística: tiene `entities` Y `views` Y `menu`. Es el shape más
124/// específico del repo, así que va primero en el orden default — si
125/// matchea, ningún otro reader debería intentar.
126pub struct UiModuleJsonReader;
127
128impl CardReader for UiModuleJsonReader {
129    fn name(&self) -> &'static str {
130        "ui-module-json"
131    }
132
133    fn can_read(&self, input: &Value) -> bool {
134        let obj = match input.as_object() {
135            Some(o) => o,
136            None => return false,
137        };
138        obj.contains_key("entities") && obj.contains_key("views") && obj.contains_key("menu")
139    }
140
141    fn read(&self, input: Value) -> Result<Card, CardLoadError> {
142        let id = pull_string(&input, "id").unwrap_or_default();
143        let label = pull_string(&input, "label").unwrap_or_default();
144        // UiModule no tiene lineage en su schema, queda None.
145        let module: UiModuleSpec =
146            serde_json::from_value(input).map_err(|e| CardLoadError::ReaderFailed {
147                reader: "ui-module-json",
148                message: e.to_string(),
149            })?;
150
151        Ok(Card {
152            id,
153            schema_version: CARD_SCHEMA_VERSION,
154            lineage: None,
155            label,
156            extensions: Default::default(),
157            body: CardBody::UiModule(module),
158        })
159    }
160}
161
162// ============================================================================
163// Helpers
164// ============================================================================
165
166fn pull_string(v: &Value, key: &str) -> Option<String> {
167    v.get(key)?.as_str().map(|s| s.to_string())
168}