tawasuyu

tawasuyu/card

Sovereign content-addressed identity primitive (card-core + WIT-typed flows) in Rust.

Markdown 9Rust 7TOML 4HTML 1165 Definitions

⎇ 58c483eb2fefeb68d02cc572e9c29698277a5f68 shared/card/card-wit/src/lib.rs Rust · 178 lines · 5.6 KB History Who touched what raw

feat: card standalone — primitiva de identidad soberana (hoja, autocontenida) e6d21c602 · Sergio ·

1//! `brahman-card-wit` — extractor de contratos WIT.
2//!
3//! **DORMIDO (2026-05-30).** No existe ningún archivo `.wit` en el workspace
4//! y ningún crate de producción depende de éste (sólo `examples/` y la
5//! dev-dependency de `card-sidecar`). Es la "Capa 3" de Brahman (ver `/BRAHMAN.md`):
6//! la idea de módulos agnósticos descritos por interfaz WIT nunca se ejecutó.
7//! El **contrato agnóstico real y vigente** es `shared/card` (formato `Card`) +
8//! el handshake nativo Rust de `card-handshake` + el namespacing de `DhtKey`.
9//! Se conserva como herramienta opcional por si algún día aparecen `.wit`
10//! reales; no asumir que está en ninguna ruta de build.
11//!
12//! Nota: el tipo de metadata [`WitInterface`] vive en `card-core` y SÍ lo usa
13//! el broker para matching estructural — eso es metadata opcional viva. Lo
14//! dormido es este *parser* de archivos `.wit` inexistentes.
15//!
16//! Crate **opcional** (no es dep de `brahman-card`). Parsea texto WIT
17//! mediante [`wit-parser`] y devuelve una lista de [`WitInterface`]
18//! (uno por `world`) lista para acoplarse a una [`card_core::Card`]
19//! cuando se construye una [`card_core::ResolvedCard`].
20//!
21//! Casos de uso (hipotéticos hasta que existan `.wit`):
22//!
23//! - El Init lee `<modulo>/wit/protocol.wit` durante el descubrimiento
24//!   y lo combina con la Card del módulo para obtener una
25//!   `ResolvedCard::from_conscious(card, wit)`.
26//! - Tooling (`brahman-wit-info`) inspecciona un `.wit` y muestra
27//!   sus mundos, exports e imports.
28//!
29//! No depende de `wasm-tools`/`wit-component` — sólo del parser texto.
30
31#![forbid(unsafe_code)]
32#![warn(rust_2018_idioms)]
33
34use std::path::{Path, PathBuf};
35
36use card_core::WitInterface;
37use thiserror::Error;
38use wit_parser::{Resolve, WorldKey};
39
40#[derive(Debug, Error)]
41pub enum WitError {
42    #[error("parse: {0}")]
43    Parse(String),
44    #[error("E/S: {0}")]
45    Io(#[from] std::io::Error),
46}
47
48/// Parsea WIT desde una string. Devuelve un `WitInterface` por cada
49/// `world` declarado.
50pub fn parse_wit(source: &str) -> Result<Vec<WitInterface>, WitError> {
51    parse_with_path(source, Path::new("inline.wit"))
52}
53
54/// Parsea WIT desde un archivo. Útil para `module/wit/protocol.wit`.
55pub fn parse_wit_file(path: impl AsRef<Path>) -> Result<Vec<WitInterface>, WitError> {
56    let p = path.as_ref();
57    let source = std::fs::read_to_string(p)?;
58    parse_with_path(&source, p)
59}
60
61fn parse_with_path(source: &str, path: &Path) -> Result<Vec<WitInterface>, WitError> {
62    let mut resolve = Resolve::new();
63    let path_buf: PathBuf = path.to_path_buf();
64    resolve
65        .push_str(&path_buf, source)
66        .map_err(|e| WitError::Parse(e.to_string()))?;
67
68    let mut out = Vec::new();
69    for (_pkg_id, pkg) in resolve.packages.iter() {
70        let pkg_name = pkg.name.to_string();
71        for (_name, &world_id) in &pkg.worlds {
72            let world = &resolve.worlds[world_id];
73            let exports = collect_keys(world.exports.iter().map(|(k, _)| k), &resolve);
74            let imports = collect_keys(world.imports.iter().map(|(k, _)| k), &resolve);
75            out.push(WitInterface {
76                package: pkg_name.clone(),
77                world: world.name.clone(),
78                exports,
79                imports,
80            });
81        }
82    }
83    Ok(out)
84}
85
86fn collect_keys<'a, I>(keys: I, resolve: &Resolve) -> Vec<String>
87where
88    I: Iterator<Item = &'a WorldKey>,
89{
90    keys.map(|k| match k {
91        WorldKey::Name(n) => n.clone(),
92        WorldKey::Interface(id) => resolve.interfaces[*id]
93            .name
94            .clone()
95            .unwrap_or_else(|| format!("<interface#{}>", id.index())),
96    })
97    .collect()
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    const SAMPLE: &str = r#"
105package brahman:test@0.1.0;
106
107interface handshake {
108    hello: func() -> result<_, string>;
109}
110
111interface lifecycle {
112    report: func();
113}
114
115world module {
116    import handshake;
117    import lifecycle;
118    export run: func() -> result<_, string>;
119}
120"#;
121
122    #[test]
123    fn parses_inline_wit() {
124        let worlds = parse_wit(SAMPLE).unwrap();
125        assert_eq!(worlds.len(), 1, "esperaba un único world");
126        let w = &worlds[0];
127        assert!(w.package.starts_with("brahman:test"));
128        assert_eq!(w.world, "module");
129        assert!(
130            w.imports.iter().any(|i| i == "handshake"),
131            "imports={:?}",
132            w.imports
133        );
134        assert!(
135            w.imports.iter().any(|i| i == "lifecycle"),
136            "imports={:?}",
137            w.imports
138        );
139        assert!(
140            w.exports.iter().any(|e| e == "run"),
141            "exports={:?}",
142            w.exports
143        );
144    }
145
146    #[test]
147    fn parses_shared_protocol() {
148        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../shared_wit/protocol.wit");
149        let worlds = parse_wit_file(path).unwrap();
150        assert!(
151            worlds.iter().any(|w| w.world == "module"),
152            "no encontró world 'module' en {:?}",
153            worlds.iter().map(|w| &w.world).collect::<Vec<_>>()
154        );
155        assert!(
156            worlds.iter().any(|w| w.world == "admin-host"),
157            "no encontró world 'admin-host'"
158        );
159    }
160
161    #[test]
162    fn parse_error_on_garbage() {
163        let bad = "this is not wit at all { } } ;;;;";
164        assert!(matches!(parse_wit(bad), Err(WitError::Parse(_))));
165    }
166
167    #[test]
168    fn empty_world_handled() {
169        let src = r#"
170package brahman:empty@0.1.0;
171world hollow {}
172"#;
173        let worlds = parse_wit(src).unwrap();
174        assert_eq!(worlds.len(), 1);
175        assert!(worlds[0].exports.is_empty());
176        assert!(worlds[0].imports.is_empty());
177    }
178}