camel_language_api/
lib.rs1pub mod error;
10pub mod eval;
11pub mod language_limits;
12
13pub use async_trait::async_trait;
14pub use camel_api::Value;
15pub use camel_api::body::Body;
16pub use camel_api::exchange::Exchange;
17pub use camel_api::message::Message;
18pub use camel_api::{ErrorPosition, ExpressionErrorClass};
19pub use error::LanguageError;
20pub use eval::{
21 EvalMeta, LanguageExpressionEval, LanguagePredicateEval, is_expression_detail_cause,
22 to_expression_failed,
23};
24pub use language_limits::{
25 JsEngineConfig, JsLimitsConfig, LanguagesConfig, MinijinjaEngineConfig, MinijinjaLimitsConfig,
26 RhaiEngineConfig, RhaiLimitsConfig,
27};
28
29pub trait Language: Send + Sync {
31 fn name(&self) -> &'static str;
32 fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError>;
33 fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError>;
34
35 fn create_predicate_group(
41 &self,
42 scripts: &[&str],
43 ) -> Result<Option<Box<dyn PredicateGroup>>, LanguageError> {
44 let _ = scripts;
45 Ok(None)
46 }
47
48 fn create_mutating_expression(
50 &self,
51 _script: &str,
52 ) -> Result<Box<dyn MutatingExpression>, LanguageError> {
53 Err(LanguageError::NotSupported {
54 feature: "mutating expressions".into(),
55 language: self.name().into(),
56 })
57 }
58
59 fn create_mutating_predicate(
61 &self,
62 _script: &str,
63 ) -> Result<Box<dyn MutatingPredicate>, LanguageError> {
64 Err(LanguageError::NotSupported {
65 feature: "mutating predicates".into(),
66 language: self.name().into(),
67 })
68 }
69}
70
71#[async_trait]
73pub trait Expression: Send + Sync {
74 async fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError>;
75}
76
77#[async_trait]
80pub trait MutatingExpression: Send + Sync {
81 async fn evaluate(&self, exchange: &mut Exchange) -> Result<Value, LanguageError>;
82}
83
84#[async_trait]
86pub trait Predicate: Send + Sync {
87 async fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError>;
88}
89
90#[async_trait]
95pub trait MutatingPredicate: Send + Sync {
96 async fn matches(&self, exchange: &mut Exchange) -> Result<bool, LanguageError>;
97}
98
99#[derive(Debug)]
106pub struct IndexedPredicateError {
107 pub index: usize,
109 pub error: LanguageError,
111}
112
113impl std::fmt::Display for IndexedPredicateError {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 write!(f, "predicate group clause {} failed", self.index)?;
116 if let Some(class) = self.error.class() {
117 write!(f, " ({class})")?;
118 }
119 if let Some(detail) = self.error.redacted_detail() {
120 write!(f, ": {detail}")?;
121 }
122 Ok(())
123 }
124}
125
126#[async_trait]
128pub trait PredicateGroup: Send + Sync {
129 async fn first_match(
136 &self,
137 exchange: &Exchange,
138 ) -> Result<Option<usize>, IndexedPredicateError>;
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 struct MockLanguage;
147
148 impl Language for MockLanguage {
149 fn name(&self) -> &'static str {
150 "mock"
151 }
152
153 fn create_expression(&self, _script: &str) -> Result<Box<dyn Expression>, LanguageError> {
154 Ok(Box::new(MockExpression))
155 }
156
157 fn create_predicate(&self, _script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
158 Ok(Box::new(MockPredicate))
159 }
160 }
161
162 struct MockExpression;
163
164 #[async_trait]
165 impl Expression for MockExpression {
166 async fn evaluate(&self, _exchange: &Exchange) -> Result<Value, LanguageError> {
167 Ok(Value::String("mock".into()))
168 }
169 }
170
171 struct MockPredicate;
172
173 #[async_trait]
174 impl Predicate for MockPredicate {
175 async fn matches(&self, _exchange: &Exchange) -> Result<bool, LanguageError> {
176 Ok(true)
177 }
178 }
179
180 #[test]
181 fn mock_language_returns_name() {
182 let lang = MockLanguage;
183 assert_eq!(lang.name(), "mock");
184 }
185
186 #[tokio::test]
187 async fn mock_language_creates_expression_and_evaluates() {
188 let lang = MockLanguage;
189 let expr = lang.create_expression("any").unwrap();
190 let ex = Exchange::new(Message::default());
191 let result = expr.evaluate(&ex).await.unwrap();
192 assert_eq!(result.as_str().unwrap(), "mock");
193 }
194
195 #[tokio::test]
196 async fn mock_language_creates_predicate_and_matches() {
197 let lang = MockLanguage;
198 let pred = lang.create_predicate("any").unwrap();
199 let ex = Exchange::new(Message::default());
200 assert!(pred.matches(&ex).await.unwrap());
201 }
202
203 #[test]
204 fn default_mutating_expression_returns_not_supported() {
205 let lang = MockLanguage;
206 let result = lang.create_mutating_expression("any");
207 assert!(matches!(result, Err(LanguageError::NotSupported { .. })));
208 if let Err(LanguageError::NotSupported { feature, language }) = result {
209 assert_eq!(feature, "mutating expressions");
210 assert_eq!(language, "mock");
211 }
212 }
213
214 #[test]
215 fn default_mutating_predicate_returns_not_supported() {
216 let lang = MockLanguage;
217 let result = lang.create_mutating_predicate("any");
218 assert!(matches!(result, Err(LanguageError::NotSupported { .. })));
219 }
220
221 #[test]
222 fn language_trait_is_send_sync() {
223 fn assert_send_sync<T: Send + Sync>() {}
224 assert_send_sync::<MockLanguage>();
225 }
226
227 #[test]
228 fn default_create_predicate_group_returns_none() {
229 let lang = MockLanguage;
230 let result = lang.create_predicate_group(&["a", "b"]);
231 assert!(matches!(result, Ok(None)));
232 }
233
234 #[test]
235 fn language_trait_group_hook_is_object_safe() {
236 let lang: &dyn Language = &MockLanguage;
237 let result: Result<Option<Box<dyn PredicateGroup>>, LanguageError> =
238 lang.create_predicate_group(&["a", "b"]);
239 assert!(matches!(result, Ok(None)));
240 }
241
242 #[test]
243 fn indexed_predicate_error_display_names_index() {
244 let err = IndexedPredicateError {
245 index: 2,
246 error: LanguageError::EvalError("SECRET".into()),
247 };
248 let rendered = format!("{err}");
249 assert!(rendered.contains('2'), "rendered: {rendered}");
250 assert!(!rendered.contains("SECRET"), "rendered: {rendered}");
251 }
252}