Skip to main content

camel_wit/
lib.rs

1//! Canonical WIT contracts for rust-camel WASM guests.
2//!
3//! This crate publishes the `camel:plugin` WIT package as embedded strings
4//! ([`PLUGIN_WIT`], [`BEAN_WIT`], [`SOURCE_WIT`], [`FULL_WIT`]) and includes
5//! the raw `.wit` files in the published crate package, so non-Rust guest
6//! authors can consume the contract with their own WIT toolchain. See the crate
7//! README for the multi-language guest quickstart. Package versioning follows
8//! ADR-0053 (`camel:plugin@1.0.0`).
9
10/// WIT source for the `plugin` world (standalone file).
11pub const PLUGIN_WIT: &str = include_str!("../wit/camel-plugin.wit");
12
13/// WIT source for the `bean` world (standalone file, same package as PLUGIN_WIT).
14pub const BEAN_WIT: &str = include_str!("../wit/camel-bean.wit");
15
16/// WIT source for the `source` world (standalone file, same package as PLUGIN_WIT).
17pub const SOURCE_WIT: &str = include_str!("../wit/camel-source.wit");
18
19/// Combined WIT package with both `plugin` and `bean` worlds in a single document.
20pub const FULL_WIT: &str = include_str!("../wit/camel-all.wit");
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25
26    // ── WIT-002: Tests for WIT definitions ──────────────────────────────────
27
28    #[test]
29    fn test_plugin_wit_is_non_empty() {
30        assert!(!PLUGIN_WIT.is_empty(), "PLUGIN_WIT should not be empty");
31    }
32
33    #[test]
34    fn test_bean_wit_is_non_empty() {
35        assert!(!BEAN_WIT.is_empty(), "BEAN_WIT should not be empty");
36    }
37
38    #[test]
39    fn test_full_wit_is_non_empty() {
40        assert!(!FULL_WIT.is_empty(), "FULL_WIT should not be empty");
41    }
42
43    #[test]
44    fn test_wit_constants_contain_package_declaration() {
45        assert!(PLUGIN_WIT.contains("package camel:plugin@1.0.0"));
46        assert!(BEAN_WIT.contains("package camel:plugin@1.0.0"));
47        assert!(FULL_WIT.contains("package camel:plugin@1.0.0"));
48        assert!(SOURCE_WIT.contains("package camel:plugin@1.0.0"));
49    }
50
51    #[test]
52    fn test_wit_exchange_has_route_and_message_id_fields() {
53        // WIT-005: verify route-id and message-id fields are present
54        assert!(
55            FULL_WIT.contains("route-id"),
56            "wasm-exchange should contain route-id field"
57        );
58        assert!(
59            FULL_WIT.contains("message-id"),
60            "wasm-exchange should contain message-id field"
61        );
62        assert!(
63            PLUGIN_WIT.contains("route-id"),
64            "plugin WIT should contain route-id field"
65        );
66        assert!(
67            PLUGIN_WIT.contains("message-id"),
68            "plugin WIT should contain message-id field"
69        );
70    }
71
72    #[test]
73    fn test_plugin_wit_contains_authorization_policy_world() {
74        assert!(
75            PLUGIN_WIT.contains("world authorization-policy"),
76            "PLUGIN_WIT should contain 'world authorization-policy'"
77        );
78    }
79
80    #[test]
81    fn test_plugin_wit_authorization_policy_has_evaluate() {
82        assert!(
83            PLUGIN_WIT.contains("export evaluate: func(exchange: wasm-exchange) -> result<option<string>, wasm-error>"),
84            "PLUGIN_WIT should contain evaluate export"
85        );
86    }
87
88    #[test]
89    fn test_plugin_wit_authorization_policy_has_init_with_config() {
90        assert!(
91            PLUGIN_WIT.contains(
92                "export init: func(config: list<tuple<string, string>>) -> result<_, string>"
93            ),
94            "PLUGIN_WIT should contain init with config parameter"
95        );
96    }
97
98    #[test]
99    fn test_full_wit_contains_authorization_policy_world() {
100        assert!(
101            FULL_WIT.contains("world authorization-policy"),
102            "FULL_WIT should contain 'world authorization-policy'"
103        );
104    }
105
106    fn strip_comments(wit: &str) -> String {
107        wit.lines()
108            .filter(|l| !l.trim_start().starts_with("//"))
109            .collect::<Vec<_>>()
110            .join("\n")
111            .trim()
112            .to_string()
113    }
114
115    #[test]
116    fn test_example_bean_wit_matches_canonical() {
117        let example_dir = std::path::Path::new(concat!(
118            env!("CARGO_MANIFEST_DIR"),
119            "/../../examples/wasm-bean-example/wit"
120        ));
121        if !example_dir.exists() {
122            return;
123        }
124        let example_bean = std::fs::read_to_string(example_dir.join("camel-bean.wit"))
125            .expect("read example bean wit");
126        let canonical_stripped = strip_comments(BEAN_WIT);
127        let example_stripped = strip_comments(&example_bean);
128        assert_eq!(
129            canonical_stripped, example_stripped,
130            "examples/wasm-bean-example/wit/camel-bean.wit must match canonical without comments"
131        );
132    }
133
134    #[test]
135    fn test_example_plugin_wit_has_route_id_and_message_id() {
136        let example_dir = std::path::Path::new(concat!(
137            env!("CARGO_MANIFEST_DIR"),
138            "/../../examples/wasm-bean-example/wit"
139        ));
140        if !example_dir.exists() {
141            return;
142        }
143        let example_plugin = std::fs::read_to_string(example_dir.join("camel-plugin.wit"))
144            .expect("read example plugin wit");
145        assert!(
146            example_plugin.contains("route-id"),
147            "example camel-plugin.wit must contain route-id"
148        );
149        assert!(
150            example_plugin.contains("message-id"),
151            "example camel-plugin.wit must contain message-id"
152        );
153        assert!(
154            example_plugin.contains("world authorization-policy"),
155            "example camel-plugin.wit must contain authorization-policy world"
156        );
157        assert!(
158            example_plugin.contains(
159                "export init: func(config: list<tuple<string, string>>) -> result<_, string>"
160            ),
161            "example camel-plugin.wit must contain init(config) in bean world"
162        );
163    }
164
165    #[test]
166    fn test_full_wit_has_all_worlds() {
167        // FULL_WIT (canonical camel-all.wit) is the merged reference document
168        // containing every world. The example wit dir intentionally ships only
169        // camel-plugin.wit + camel-bean.wit (compile-ready subset, no world
170        // overlap) because wit-bindgen 0.58 rejects duplicate world
171        // declarations across files in the same package.
172        assert!(
173            FULL_WIT.contains("world plugin"),
174            "FULL_WIT must contain plugin world"
175        );
176        assert!(
177            FULL_WIT.contains("world bean"),
178            "FULL_WIT must contain bean world"
179        );
180        assert!(
181            FULL_WIT.contains("world authorization-policy"),
182            "FULL_WIT must contain authorization-policy world"
183        );
184    }
185
186    #[test]
187    fn test_host_wit_matches_canonical() {
188        let host_wit_dir = std::path::Path::new(concat!(
189            env!("CARGO_MANIFEST_DIR"),
190            "/../components/camel-component-wasm/wit"
191        ));
192        assert!(
193            host_wit_dir.exists(),
194            "camel-component-wasm/wit/ must exist — drift cannot be detected if the directory is missing"
195        );
196
197        for (name, canonical) in [
198            ("camel-plugin.wit", PLUGIN_WIT),
199            ("camel-bean.wit", BEAN_WIT),
200            ("camel-source.wit", SOURCE_WIT),
201        ] {
202            let host = std::fs::read_to_string(host_wit_dir.join(name))
203                .unwrap_or_else(|_| panic!("read host {}", name));
204            let canonical_stripped = strip_comments(canonical);
205            let host_stripped = strip_comments(&host);
206            assert_eq!(
207                canonical_stripped, host_stripped,
208                "camel-component-wasm/wit/{} must match canonical without comments",
209                name
210            );
211        }
212    }
213}