Skip to main content

camel_core/
language_registry.rs

1//! Helper for constructing a `SharedLanguageRegistry` from `LanguagesConfig`.
2//!
3//! This is the single source of truth for building language registries — called
4//! by `CamelContextBuilder::built_in_languages` (which passes
5//! `LanguagesConfig::default()`) and by `camel-cli` / programmatic embedders
6//! that want to apply `Camel.toml` limits at registration time.
7//!
8//! Engines are only included when their cargo feature is enabled
9//! (`lang-js`, `lang-rhai`, `lang-minijinja`). The `simple` language is always
10//! registered. Languages without configurable limits (jsonpath, xpath) are
11//! registered with their default constructor.
12
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15
16use camel_language_api::Language;
17use camel_language_api::LanguagesConfig;
18use thiserror::Error;
19
20use crate::lifecycle::adapters::route_controller::SharedLanguageRegistry;
21
22/// Error type for language registration operations on [`CamelContext`](crate::CamelContext).
23///
24/// This is distinct from [`camel_language_api::error::LanguageError`], which
25/// covers language-evaluation concerns (parse, eval, unknown variable, etc.).
26/// Registration is a context-configuration invariant, not a language concern.
27#[derive(Debug, Clone, PartialEq, Eq, Error)]
28pub enum LanguageRegistryError {
29    #[error("language '{name}' is already registered")]
30    AlreadyRegistered { name: String },
31}
32
33/// Build a language registry from `[languages.*]` config blocks.
34///
35/// Engines are only included when their cargo feature is enabled
36/// (`lang-rhai`, `lang-js`, `lang-minijinja`). The `simple` language is
37/// always registered (no limits apply). Languages without configurable limits
38/// (jsonpath, xpath) are registered with their default constructor so this
39/// helper is the single source of truth consumed by `built_in_languages()`.
40pub fn from_config(_config: &LanguagesConfig) -> SharedLanguageRegistry {
41    let mut languages: HashMap<String, Arc<dyn Language>> = HashMap::new();
42
43    languages.insert(
44        "simple".to_string(),
45        Arc::new(camel_language_simple::SimpleLanguage::new()),
46    );
47
48    #[cfg(feature = "lang-js")]
49    {
50        let js_lang = camel_language_js::JsLanguage::with_limits(_config.js.limits.clone());
51        languages.insert("js".to_string(), Arc::new(js_lang.clone()));
52        languages.insert("javascript".to_string(), Arc::new(js_lang));
53    }
54
55    #[cfg(feature = "lang-rhai")]
56    {
57        let rhai_lang = camel_language_rhai::RhaiLanguage::with_limits(_config.rhai.limits.clone());
58        languages.insert("rhai".to_string(), Arc::new(rhai_lang));
59    }
60
61    #[cfg(feature = "lang-minijinja")]
62    {
63        let minijinja_lang = camel_language_minijinja::MinijinjaLanguage::with_limits(
64            _config.minijinja.limits.clone(),
65        );
66        languages.insert("minijinja".to_string(), Arc::new(minijinja_lang));
67    }
68
69    // Languages without configurable limits — registered with ::new().
70    // Included here so built_in_languages() can delegate to from_config()
71    // as a single source of truth.
72    #[cfg(feature = "lang-jsonpath")]
73    {
74        languages.insert(
75            "jsonpath".to_string(),
76            Arc::new(camel_language_jsonpath::JsonPathLanguage::new()),
77        );
78    }
79
80    #[cfg(feature = "lang-xpath")]
81    {
82        languages.insert(
83            "xpath".to_string(),
84            Arc::new(camel_language_xpath::XPathLanguage::new()),
85        );
86    }
87
88    Arc::new(Mutex::new(languages))
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use camel_language_api::{JsLimitsConfig, LanguagesConfig, RhaiLimitsConfig};
95
96    #[test]
97    fn from_config_includes_simple_always() {
98        let registry = from_config(&LanguagesConfig::default());
99        let registry = registry.lock().expect("lock registry");
100        assert!(registry.contains_key("simple"));
101    }
102
103    #[test]
104    fn from_config_succeeds_with_rhai_limits_when_feature_enabled() {
105        let cfg = LanguagesConfig {
106            rhai: camel_language_api::RhaiEngineConfig {
107                limits: RhaiLimitsConfig {
108                    max_operations: Some(10),
109                    ..Default::default()
110                },
111            },
112            ..Default::default()
113        };
114        let registry = from_config(&cfg);
115        let registry = registry.lock().expect("lock registry");
116        #[cfg(feature = "lang-rhai")]
117        {
118            let lang = registry.get("rhai").expect("rhai present");
119            // We can't easily introspect limits on the trait object; this test
120            // just confirms the construction succeeds with custom limits.
121            assert_eq!(lang.name(), "rhai");
122        }
123        #[cfg(not(feature = "lang-rhai"))]
124        {
125            assert!(!registry.contains_key("rhai"));
126        }
127    }
128
129    #[test]
130    fn from_config_succeeds_with_js_limits_when_feature_enabled() {
131        let cfg = LanguagesConfig {
132            js: camel_language_api::JsEngineConfig {
133                limits: JsLimitsConfig {
134                    max_loop_iterations: Some(1_000),
135                    ..Default::default()
136                },
137            },
138            ..Default::default()
139        };
140        let registry = from_config(&cfg);
141        let registry = registry.lock().expect("lock registry");
142        #[cfg(feature = "lang-js")]
143        {
144            let lang = registry.get("js").expect("js present");
145            assert_eq!(lang.name(), "js");
146        }
147        #[cfg(not(feature = "lang-js"))]
148        {
149            assert!(!registry.contains_key("js"));
150        }
151    }
152
153    #[test]
154    fn from_config_succeeds_with_minijinja_limits_when_feature_enabled() {
155        let cfg = LanguagesConfig {
156            minijinja: camel_language_api::MinijinjaEngineConfig {
157                limits: camel_language_api::MinijinjaLimitsConfig {
158                    fuel: Some(10_000),
159                    ..Default::default()
160                },
161            },
162            ..Default::default()
163        };
164        let registry = from_config(&cfg);
165        let registry = registry.lock().expect("lock registry");
166        #[cfg(feature = "lang-minijinja")]
167        {
168            let lang = registry.get("minijinja").expect("minijinja present");
169            assert_eq!(lang.name(), "minijinja");
170        }
171        #[cfg(not(feature = "lang-minijinja"))]
172        {
173            assert!(!registry.contains_key("minijinja"));
174        }
175    }
176}