gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definiciones

⎇ b08d5cbe0a6222f5b1479f40e68346b5d38246a8 crates/protocol/brahman-card-wit/src/lib.rs Rust · 165 líneas · 4.7 KB Historia Quién tocó qué crudo

refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG 550c98f27 · sergio ·

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