Skip to main content

camel_language_api/
lib.rs

1//! camel-language-api — traits and types for building Camel expression languages.
2//!
3//! Language SPI for rust-camel — defines the core traits all expression/predicate languages implement.
4//!
5//! Main traits: `Language`, `Expression`, `Predicate`, `MutatingExpression`, `MutatingPredicate`.
6//! Main modules: `error`.
7
8pub mod error;
9
10pub use async_trait::async_trait;
11pub use camel_api::Value;
12pub use camel_api::body::Body;
13pub use camel_api::exchange::Exchange;
14pub use camel_api::message::Message;
15pub use error::LanguageError;
16
17/// A Language factory: produces Expression and Predicate objects.
18pub trait Language: Send + Sync {
19    fn name(&self) -> &'static str;
20    fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError>;
21    fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError>;
22
23    /// Create a mutating expression. Default returns NotSupported.
24    fn create_mutating_expression(
25        &self,
26        _script: &str,
27    ) -> Result<Box<dyn MutatingExpression>, LanguageError> {
28        Err(LanguageError::NotSupported {
29            feature: "mutating expressions".into(),
30            language: self.name().into(),
31        })
32    }
33
34    /// Create a mutating predicate. Default returns NotSupported.
35    fn create_mutating_predicate(
36        &self,
37        _script: &str,
38    ) -> Result<Box<dyn MutatingPredicate>, LanguageError> {
39        Err(LanguageError::NotSupported {
40            feature: "mutating predicates".into(),
41            language: self.name().into(),
42        })
43    }
44}
45
46/// Evaluates to a Value against an Exchange.
47#[async_trait]
48pub trait Expression: Send + Sync {
49    async fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError>;
50}
51
52/// Expression that may modify the Exchange during evaluation.
53/// Changes to headers, properties, or body are propagated back.
54#[async_trait]
55pub trait MutatingExpression: Send + Sync {
56    async fn evaluate(&self, exchange: &mut Exchange) -> Result<Value, LanguageError>;
57}
58
59/// Evaluates to bool against an Exchange.
60#[async_trait]
61pub trait Predicate: Send + Sync {
62    async fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError>;
63}
64
65/// Predicate that may modify the Exchange during evaluation.
66/// Changes to headers, properties, or body are propagated back.
67///
68/// Reserved for future use. Not yet implemented by any language.
69#[async_trait]
70pub trait MutatingPredicate: Send + Sync {
71    async fn matches(&self, exchange: &mut Exchange) -> Result<bool, LanguageError>;
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    /// A minimal `Language` impl used to verify the trait compiles and methods work.
79    struct MockLanguage;
80
81    impl Language for MockLanguage {
82        fn name(&self) -> &'static str {
83            "mock"
84        }
85
86        fn create_expression(&self, _script: &str) -> Result<Box<dyn Expression>, LanguageError> {
87            Ok(Box::new(MockExpression))
88        }
89
90        fn create_predicate(&self, _script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
91            Ok(Box::new(MockPredicate))
92        }
93    }
94
95    struct MockExpression;
96
97    #[async_trait]
98    impl Expression for MockExpression {
99        async fn evaluate(&self, _exchange: &Exchange) -> Result<Value, LanguageError> {
100            Ok(Value::String("mock".into()))
101        }
102    }
103
104    struct MockPredicate;
105
106    #[async_trait]
107    impl Predicate for MockPredicate {
108        async fn matches(&self, _exchange: &Exchange) -> Result<bool, LanguageError> {
109            Ok(true)
110        }
111    }
112
113    #[test]
114    fn mock_language_returns_name() {
115        let lang = MockLanguage;
116        assert_eq!(lang.name(), "mock");
117    }
118
119    #[tokio::test]
120    async fn mock_language_creates_expression_and_evaluates() {
121        let lang = MockLanguage;
122        let expr = lang.create_expression("any").unwrap();
123        let ex = Exchange::new(Message::default());
124        let result = expr.evaluate(&ex).await.unwrap();
125        assert_eq!(result.as_str().unwrap(), "mock");
126    }
127
128    #[tokio::test]
129    async fn mock_language_creates_predicate_and_matches() {
130        let lang = MockLanguage;
131        let pred = lang.create_predicate("any").unwrap();
132        let ex = Exchange::new(Message::default());
133        assert!(pred.matches(&ex).await.unwrap());
134    }
135
136    #[test]
137    fn default_mutating_expression_returns_not_supported() {
138        let lang = MockLanguage;
139        let result = lang.create_mutating_expression("any");
140        assert!(matches!(result, Err(LanguageError::NotSupported { .. })));
141        if let Err(LanguageError::NotSupported { feature, language }) = result {
142            assert_eq!(feature, "mutating expressions");
143            assert_eq!(language, "mock");
144        }
145    }
146
147    #[test]
148    fn default_mutating_predicate_returns_not_supported() {
149        let lang = MockLanguage;
150        let result = lang.create_mutating_predicate("any");
151        assert!(matches!(result, Err(LanguageError::NotSupported { .. })));
152    }
153
154    #[test]
155    fn language_trait_is_send_sync() {
156        fn assert_send_sync<T: Send + Sync>() {}
157        assert_send_sync::<MockLanguage>();
158    }
159}