1pub const PLUGIN_WIT: &str = include_str!("../wit/camel-plugin.wit");
3
4pub const BEAN_WIT: &str = include_str!("../wit/camel-bean.wit");
6
7pub const FULL_WIT: &str = include_str!("../wit/camel-all.wit");
9
10pub const APPLICATION_JSON: &str = "application/json";
18
19pub const TEXT_PLAIN: &str = "text/plain";
21
22pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
24
25pub const TEXT_HTML: &str = "text/html";
27
28pub const APPLICATION_XML: &str = "application/xml";
30
31pub const APPLICATION_FORM_URLENCODED: &str = "application/x-www-form-urlencoded";
33
34pub fn wit_dir() -> &'static std::path::Path {
54 std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/wit"))
55}
56
57use std::sync::atomic::{AtomicUsize, Ordering};
58
59use camel_api::CamelError;
60
61const DEFAULT_MAX_RESOURCES: usize = 1000;
63
64#[derive(Debug)]
72pub struct WitHost {
73 max_resources: usize,
74 allocation_count: AtomicUsize,
75}
76
77impl WitHost {
78 pub fn new() -> Self {
80 Self::with_max_resources(DEFAULT_MAX_RESOURCES)
81 }
82
83 pub fn with_max_resources(max: usize) -> Self {
85 Self {
86 max_resources: max,
87 allocation_count: AtomicUsize::new(0),
88 }
89 }
90
91 pub fn allocate(&self, _name: &str) -> Result<(), CamelError> {
97 let mut current = self.allocation_count.load(Ordering::Relaxed);
98 loop {
99 if current >= self.max_resources {
100 return Err(CamelError::ProcessorError("resource limit exceeded".into()));
101 }
102 match self.allocation_count.compare_exchange_weak(
103 current,
104 current + 1,
105 Ordering::AcqRel,
106 Ordering::Relaxed,
107 ) {
108 Ok(_) => return Ok(()),
109 Err(actual) => current = actual,
110 }
111 }
112 }
113
114 pub fn deallocate(&self, _name: &str) {
116 self.allocation_count.fetch_sub(1, Ordering::AcqRel);
117 }
118
119 pub fn resource_count(&self) -> usize {
121 self.allocation_count.load(Ordering::Acquire)
122 }
123
124 pub fn max_resources(&self) -> usize {
126 self.max_resources
127 }
128}
129
130impl Default for WitHost {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_wit_host_rejects_beyond_max_resources() {
142 let host = WitHost::with_max_resources(3);
143 host.allocate("a").unwrap();
144 host.allocate("b").unwrap();
145 host.allocate("c").unwrap();
146 let result = host.allocate("d"); assert!(result.is_err());
148 }
149
150 #[test]
151 fn test_wit_host_default_limit_is_1000() {
152 let host = WitHost::new();
153 assert_eq!(host.max_resources(), 1000);
154 }
155
156 #[test]
157 fn test_wit_host_allows_up_to_limit() {
158 let host = WitHost::with_max_resources(2);
159 assert!(host.allocate("x").is_ok());
160 assert!(host.allocate("y").is_ok());
161 assert!(host.allocate("z").is_err());
162 }
163
164 #[test]
165 fn test_wit_host_deallocate_frees_slot() {
166 let host = WitHost::with_max_resources(1);
167 host.allocate("a").unwrap();
168 assert!(host.allocate("b").is_err());
169 host.deallocate("a");
170 assert!(host.allocate("b").is_ok());
171 }
172
173 #[test]
174 fn test_wit_host_resource_count_tracks_allocations() {
175 let host = WitHost::new();
176 assert_eq!(host.resource_count(), 0);
177 host.allocate("a").unwrap();
178 host.allocate("b").unwrap();
179 assert_eq!(host.resource_count(), 2);
180 host.deallocate("a");
181 assert_eq!(host.resource_count(), 1);
182 }
183
184 #[test]
185 fn test_wit_host_error_is_processor_error() {
186 let host = WitHost::with_max_resources(1);
187 host.allocate("a").unwrap();
188 let err = host.allocate("b").unwrap_err();
189 assert!(matches!(err, CamelError::ProcessorError(_)));
190 assert!(err.to_string().contains("resource limit exceeded"));
191 }
192
193 #[test]
196 fn test_wit_dir_exists() {
197 let dir = wit_dir();
198 assert!(
199 dir.exists(),
200 "wit_dir() should point to an existing directory"
201 );
202 assert!(dir.is_dir(), "wit_dir() should be a directory");
203 }
204
205 #[test]
206 fn test_wit_dir_contains_expected_files() {
207 let dir = wit_dir();
208 assert!(
209 dir.join("camel-plugin.wit").exists(),
210 "camel-plugin.wit missing"
211 );
212 assert!(
213 dir.join("camel-bean.wit").exists(),
214 "camel-bean.wit missing"
215 );
216 assert!(dir.join("camel-all.wit").exists(), "camel-all.wit missing");
217 }
218
219 #[test]
220 fn test_plugin_wit_is_non_empty() {
221 assert!(!PLUGIN_WIT.is_empty(), "PLUGIN_WIT should not be empty");
222 }
223
224 #[test]
225 fn test_bean_wit_is_non_empty() {
226 assert!(!BEAN_WIT.is_empty(), "BEAN_WIT should not be empty");
227 }
228
229 #[test]
230 fn test_full_wit_is_non_empty() {
231 assert!(!FULL_WIT.is_empty(), "FULL_WIT should not be empty");
232 }
233
234 #[test]
235 fn test_wit_constants_contain_package_declaration() {
236 assert!(PLUGIN_WIT.contains("package camel:plugin"));
237 assert!(BEAN_WIT.contains("package camel:plugin"));
238 assert!(FULL_WIT.contains("package camel:plugin"));
239 }
240
241 #[test]
242 fn test_wit_exchange_has_route_and_message_id_fields() {
243 assert!(
245 FULL_WIT.contains("route-id"),
246 "wasm-exchange should contain route-id field"
247 );
248 assert!(
249 FULL_WIT.contains("message-id"),
250 "wasm-exchange should contain message-id field"
251 );
252 assert!(
253 PLUGIN_WIT.contains("route-id"),
254 "plugin WIT should contain route-id field"
255 );
256 assert!(
257 PLUGIN_WIT.contains("message-id"),
258 "plugin WIT should contain message-id field"
259 );
260 }
261
262 #[test]
263 fn test_plugin_wit_contains_authorization_policy_world() {
264 assert!(
265 PLUGIN_WIT.contains("world authorization-policy"),
266 "PLUGIN_WIT should contain 'world authorization-policy'"
267 );
268 }
269
270 #[test]
271 fn test_plugin_wit_authorization_policy_has_evaluate() {
272 assert!(
273 PLUGIN_WIT.contains("export evaluate: func(exchange: wasm-exchange) -> result<option<string>, wasm-error>"),
274 "PLUGIN_WIT should contain evaluate export"
275 );
276 }
277
278 #[test]
279 fn test_plugin_wit_authorization_policy_has_init_with_config() {
280 assert!(
281 PLUGIN_WIT.contains(
282 "export init: func(config: list<tuple<string, string>>) -> result<_, string>"
283 ),
284 "PLUGIN_WIT should contain init with config parameter"
285 );
286 }
287
288 #[test]
289 fn test_full_wit_contains_authorization_policy_world() {
290 assert!(
291 FULL_WIT.contains("world authorization-policy"),
292 "FULL_WIT should contain 'world authorization-policy'"
293 );
294 }
295
296 fn strip_comments(wit: &str) -> String {
297 wit.lines()
298 .filter(|l| !l.trim_start().starts_with("//"))
299 .collect::<Vec<_>>()
300 .join("\n")
301 .trim()
302 .to_string()
303 }
304
305 #[test]
306 fn test_example_bean_wit_matches_canonical() {
307 let example_dir = std::path::Path::new(concat!(
308 env!("CARGO_MANIFEST_DIR"),
309 "/../../examples/wasm-bean-example/wit"
310 ));
311 if !example_dir.exists() {
312 return;
313 }
314 let example_bean = std::fs::read_to_string(example_dir.join("camel-bean.wit"))
315 .expect("read example bean wit");
316 let canonical_stripped = strip_comments(BEAN_WIT);
317 let example_stripped = strip_comments(&example_bean);
318 assert_eq!(
319 canonical_stripped, example_stripped,
320 "examples/wasm-bean-example/wit/camel-bean.wit must match canonical without comments"
321 );
322 }
323
324 #[test]
325 fn test_example_plugin_wit_has_route_id_and_message_id() {
326 let example_dir = std::path::Path::new(concat!(
327 env!("CARGO_MANIFEST_DIR"),
328 "/../../examples/wasm-bean-example/wit"
329 ));
330 if !example_dir.exists() {
331 return;
332 }
333 let example_plugin = std::fs::read_to_string(example_dir.join("camel-plugin.wit"))
334 .expect("read example plugin wit");
335 assert!(
336 example_plugin.contains("route-id"),
337 "example camel-plugin.wit must contain route-id"
338 );
339 assert!(
340 example_plugin.contains("message-id"),
341 "example camel-plugin.wit must contain message-id"
342 );
343 assert!(
344 example_plugin.contains("world authorization-policy"),
345 "example camel-plugin.wit must contain authorization-policy world"
346 );
347 assert!(
348 example_plugin.contains(
349 "export init: func(config: list<tuple<string, string>>) -> result<_, string>"
350 ),
351 "example camel-plugin.wit must contain init(config) in bean world"
352 );
353 }
354
355 #[test]
356 fn test_example_all_wit_has_all_worlds() {
357 let example_dir = std::path::Path::new(concat!(
358 env!("CARGO_MANIFEST_DIR"),
359 "/../../examples/wasm-bean-example/wit"
360 ));
361 if !example_dir.exists() {
362 return;
363 }
364 let example_all = std::fs::read_to_string(example_dir.join("camel-all.wit"))
365 .expect("read example all wit");
366 assert!(
367 example_all.contains("world plugin"),
368 "camel-all.wit must contain plugin world"
369 );
370 assert!(
371 example_all.contains("world bean"),
372 "camel-all.wit must contain bean world"
373 );
374 assert!(
375 example_all.contains("world authorization-policy"),
376 "camel-all.wit must contain authorization-policy world"
377 );
378 }
379
380 #[test]
381 fn test_host_wit_matches_canonical() {
382 let host_wit_dir = std::path::Path::new(concat!(
383 env!("CARGO_MANIFEST_DIR"),
384 "/../components/camel-component-wasm/wit"
385 ));
386 if !host_wit_dir.exists() {
387 return;
388 }
389 let host_plugin = std::fs::read_to_string(host_wit_dir.join("camel-plugin.wit"))
390 .expect("read host plugin wit");
391 let canonical_stripped = strip_comments(PLUGIN_WIT);
392 let host_stripped = strip_comments(&host_plugin);
393 assert_eq!(
394 canonical_stripped, host_stripped,
395 "camel-component-wasm/wit/camel-plugin.wit must match canonical without comments"
396 );
397 }
398
399 #[test]
400 fn test_content_type_constants_compile() {
401 assert_eq!(APPLICATION_JSON, "application/json");
403 assert_eq!(TEXT_PLAIN, "text/plain");
404 assert_eq!(APPLICATION_OCTET_STREAM, "application/octet-stream");
405 assert_eq!(TEXT_HTML, "text/html");
406 assert_eq!(APPLICATION_XML, "application/xml");
407 assert_eq!(
408 APPLICATION_FORM_URLENCODED,
409 "application/x-www-form-urlencoded"
410 );
411 }
412}