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