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