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