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