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