⎇ 58c483eb2fefeb68d02cc572e9c29698277a5f68
shared/card/card-wit/src/lib.rs
History
Who touched what
raw
feat: card standalone — primitiva de identidad soberana (hoja, autocontenida)
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}