use std::time::Duration;
use camel_api::CamelError;
use camel_core::{LanguageRegistryError, RouteDefinition};
use camel_dsl::{parse_json, parse_yaml};
use camel_language_jsonpath::JsonPathLanguage;
use camel_language_rhai::RhaiLanguage;
use camel_test::CamelTestContext;
async fn register_languages(h: &CamelTestContext) {
let mut guard = tokio::time::timeout(Duration::from_secs(10), h.ctx().lock())
.await
.expect("language registry lock timed out");
match guard.register_language("jsonpath", Box::new(JsonPathLanguage::new())) {
Ok(()) | Err(LanguageRegistryError::AlreadyRegistered { .. }) => {}
}
match guard.register_language("rhai", Box::new(RhaiLanguage::new())) {
Ok(()) | Err(LanguageRegistryError::AlreadyRegistered { .. }) => {}
}
drop(guard);
}
fn capability_message(language: &str) -> String {
format!(
"script: language '{language}' does not support mutating expressions; a `script` step requires a language that implements MutatingExpression. To replace the body with a read-only expression, use a `set_body`/`transform` step with this language instead"
)
}
async fn assert_rejected_without_state(
h: &CamelTestContext,
mut routes: Vec<RouteDefinition>,
language: &str,
route_id: &str,
) {
assert_eq!(routes.len(), 1, "fixture must contain exactly one route");
let err = h
.add_route(routes.remove(0))
.await
.expect_err("a read-only script language must be rejected at route add");
match &err {
CamelError::RouteError(_) => {}
other => panic!("expected CamelError::RouteError, got: {other:?}"),
}
let text = err.to_string();
let expected = capability_message(language);
assert!(
text.contains(&expected),
"error must contain the exact shared capability message\nexpected: {expected}\nactual: {text}"
);
let valid = format!(
"routes:\n - id: {route_id}\n from: \"direct:start\"\n steps:\n - to: \"mock:result\"\n"
);
let mut valid_routes = parse_yaml(&valid).expect("valid route must parse");
assert_eq!(
valid_routes.len(),
1,
"valid fixture must contain one route"
);
let result = h.add_route(valid_routes.remove(0)).await;
assert!(
result.is_ok(),
"rejected route must leave no registration state; re-adding id {route_id:?} failed: {result:?}"
);
}
const YAML_SIMPLE: &str = r#"routes:
- id: script-simple
from: "direct:start"
steps:
- script:
language: simple
source: "${body}"
- to: "mock:result"
"#;
const YAML_JSONPATH: &str = r#"routes:
- id: script-jsonpath
from: "direct:start"
steps:
- script:
language: jsonpath
source: "$.a"
- to: "mock:result"
"#;
const JSON_SIMPLE: &str = r#"{
"routes": [
{
"id": "script-simple",
"from": "direct:start",
"steps": [
{ "script": { "language": "simple", "source": "${body}" } },
{ "to": "mock:result" }
]
}
]
}"#;
const JSON_JSONPATH: &str = r#"{
"routes": [
{
"id": "script-jsonpath",
"from": "direct:start",
"steps": [
{ "script": { "language": "jsonpath", "source": "$.a" } },
{ "to": "mock:result" }
]
}
]
}"#;
const YAML_RHAI: &str = r#"routes:
- id: script-rhai
from: "direct:start"
steps:
- script:
language: rhai
source: 'body = "ok";'
- to: "mock:result"
"#;
#[tokio::test(flavor = "multi_thread")]
async fn yaml_script_simple_rejected_at_route_add() {
let h = CamelTestContext::builder()
.with_direct()
.with_mock()
.build()
.await;
register_languages(&h).await;
let routes = parse_yaml(YAML_SIMPLE).expect("YAML simple fixture must parse");
assert_rejected_without_state(&h, routes, "simple", "script-simple").await;
}
#[tokio::test(flavor = "multi_thread")]
async fn yaml_script_jsonpath_rejected_at_route_add() {
let h = CamelTestContext::builder()
.with_direct()
.with_mock()
.build()
.await;
register_languages(&h).await;
let routes = parse_yaml(YAML_JSONPATH).expect("YAML jsonpath fixture must parse");
assert_rejected_without_state(&h, routes, "jsonpath", "script-jsonpath").await;
}
#[tokio::test(flavor = "multi_thread")]
async fn json_script_simple_rejected_at_route_add() {
let h = CamelTestContext::builder()
.with_direct()
.with_mock()
.build()
.await;
register_languages(&h).await;
let routes = parse_json(JSON_SIMPLE).expect("JSON simple fixture must parse");
assert_rejected_without_state(&h, routes, "simple", "script-simple").await;
}
#[tokio::test(flavor = "multi_thread")]
async fn json_script_jsonpath_rejected_at_route_add() {
let h = CamelTestContext::builder()
.with_direct()
.with_mock()
.build()
.await;
register_languages(&h).await;
let routes = parse_json(JSON_JSONPATH).expect("JSON jsonpath fixture must parse");
assert_rejected_without_state(&h, routes, "jsonpath", "script-jsonpath").await;
}
#[tokio::test(flavor = "multi_thread")]
async fn rhai_script_route_add_still_succeeds() {
let h = CamelTestContext::builder()
.with_direct()
.with_mock()
.build()
.await;
register_languages(&h).await;
let mut routes = parse_yaml(YAML_RHAI).expect("YAML rhai fixture must parse");
assert_eq!(routes.len(), 1, "fixture must contain exactly one route");
let result = h.add_route(routes.remove(0)).await;
assert!(
result.is_ok(),
"rhai implements MutatingExpression, its script route must add: {result:?}"
);
}