This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 52e115605675 CAMEL-24722: camel-yaml-dsl - the validator and
deserializer hints recommend the canonical YAML format, not the deprecated
shorthand (#26401)
52e115605675 is described below
commit 52e115605675c33420cb6d11f3cc11370bbd9ba8
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 14 21:16:36 2026 +0200
CAMEL-24722: camel-yaml-dsl - the validator and deserializer hints
recommend the canonical YAML format, not the deprecated shorthand (#26401)
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../dsl/jbang/core/commands/ai/EndpointChecks.java | 10 +++--
.../yaml/common/YamlDeserializationContext.java | 5 ++-
.../deserializers/ExpressionDeserializers.java | 2 +-
.../dsl/yaml/GenerateYamlDeserializersMojo.java | 4 +-
.../camel/dsl/yaml/validator/YamlValidator.java | 49 ++++++++++++----------
.../validator/YamlValidatorExpressionHintTest.java | 6 +--
.../validator/YamlValidatorPropertyHintTest.java | 8 ++--
.../apache/camel/dsl/yaml/OnExceptionTest.groovy | 4 +-
8 files changed, 50 insertions(+), 38 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
index fd3e1178683c..f992dd13de86 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
@@ -81,7 +81,8 @@ final class EndpointChecks {
if (several.find()) {
// to: direct:a,direct:b : one endpoint per to:, several go
through multicast or recipientList
errors.add(linePrefix(i) + "a to: takes one endpoint; \"" +
uri + "\" names several: send to each with"
- + " multicast: {to: [...]} (all of them) or
recipientList: {constant: \"" + uri + "\"}"
+ + " multicast: {to: [...]} (all of them) or
recipientList: {expression: {constant: {expression: \""
+ + uri + "\"}}}"
+ " (a list evaluated at runtime), or write one -
to: step per endpoint");
continue;
}
@@ -205,9 +206,10 @@ final class EndpointChecks {
Map.entry("timer:interval", "write period=<millis>"),
Map.entry("timer:delayMs", "write delay=<millis>"),
Map.entry("timer:repeat", "write repeatCount=<n>"),
- Map.entry("timer:body", "a timer message has no body: set it with
a setBody step (setBody: {constant: \"...\"})"),
- Map.entry("timer:message",
- "a timer message has no body: set it with a setBody step
(setBody: {constant: \"...\"})"),
+ Map.entry("timer:body", "a timer message has no body: set it with
a setBody step"
+ + " (setBody: {expression: {constant:
{expression: \"...\"}}})"),
+ Map.entry("timer:message", "a timer message has no body: set it
with a setBody step"
+ + " (setBody: {expression: {constant:
{expression: \"...\"}}})"),
Map.entry("timer:cron", "a cron expression is the cron or quartz
component: cron:tick?schedule=0/5+*+*+*+*+?"),
Map.entry("timer:schedule", "a cron expression is the cron or
quartz component: cron:tick?schedule=0/5+*+*+*+*+?"),
Map.entry("log:message", "the message is the body; a text is set
with a setBody step or the log EIP"),
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
index 5cbabd0a741b..87bb44f521b5 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
@@ -415,9 +415,10 @@ public class YamlDeserializationContext extends
StandardConstructor implements C
? e.getProblem().substring("Unsupported field: ".length())
: null;
String hint = "";
if ("bean".equals(field)) {
- hint = " (the bean language is written as method: {ref:
myBean, method: process})";
+ hint = " (the bean language is written as expression: {method:
{ref: myBean, method: process}})";
} else if ("expression".equals(field) || "language".equals(field))
{
- hint = " (an expression is written with the language as the
key: simple: \"...\", constant: \"...\")";
+ hint = " (an expression is written with the expression:
wrapper and the language as the key: expression: {simple:"
+ + " {expression: \"...\"}}, expression: {constant:
{expression: \"...\"}})";
}
throw new YamlDeserializationException(
node, "Error constructing YAML node id: " + id + ":
unsupported field: " + field + hint, e);
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
index 9933c583d163..cffcaf708cfd 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
@@ -223,7 +223,7 @@ public final class ExpressionDeserializers extends
YamlDeserializerSupport {
public Object construct(Node node) {
if (!(node instanceof MappingNode)) {
String text = node instanceof ScalarNode ? asText(node) :
node.getNodeType().name().toLowerCase();
- throw new InvalidExpressionException(node, "an expression is
expected here, not a plain value (" + text + "): write constant: \"" + text +
"\" for a fixed value, or simple: \"...\" for a dynamic one");
+ throw new InvalidExpressionException(node, "an expression is
expected here, not a plain value (" + text + "): write constant: {expression:
\"" + text + "\"} for a fixed value, or simple: {expression: \"...\"} for a
dynamic one");
}
ExpressionDefinition val = constructExpressionType(node);
return new
org.apache.camel.model.ExpressionSubElementDefinition(val);
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
index 8b81504b1883..633e2f2e4ed3 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
@@ -306,8 +306,8 @@ public class GenerateYamlDeserializersMojo extends
GenerateYamlSupportMojo {
ClassName.get("org.apache.camel.dsl.yaml.common.exception",
"InvalidExpressionException"),
"an expression is expected
here, not a plain value (",
- "): write constant: \"",
- "\" for a fixed value, or
simple: \"...\" for a dynamic one")
+ "): write constant:
{expression: \"",
+ "\"} for a fixed value, or
simple: {expression: \"...\"} for a dynamic one")
.endControlFlow()
.addStatement("$T val =
constructExpressionType(node)", CN_EXPRESSION_DEFINITION)
.addStatement("return new
org.apache.camel.model.ExpressionSubElementDefinition(val)")
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
index 884b57cf4c8d..25351b09f954 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
@@ -445,16 +445,17 @@ public class YamlValidator {
"idempotentConsumer");
private static final Map<String, String> EXPRESSION_EXAMPLES = Map.of(
- "split", "split: {tokenize: \",\"} or split: {simple: \"${body}\"}
(delimiter only applies to the result of"
- + " the expression)",
- "filter", "filter: {simple: \"${header.type} == 'urgent'\"}",
- "when", "when: {simple: \"${body} contains 'x'\"}",
- "setBody", "setBody: {simple: \"Hello ${body}\"} or setBody:
{constant: \"Hello\"}",
- "setHeader", "setHeader: {name: id, simple: \"${exchangeId}\"}",
- "loop", "loop: {constant: \"3\"}",
- "recipientList", "recipientList: {simple: \"${header.to}\"}",
- "script", "script: {groovy: \"...\"}",
- "delay", "delay: {constant: \"1000\"}");
+ "split", "split: {expression: {tokenize: {token: \",\"}}} or
split: {expression: {simple: {expression:"
+ + " \"${body}\"}}} (delimiter only applies to the result
of the expression)",
+ "filter", "filter: {expression: {simple: {expression:
\"${header.type} == 'urgent'\"}}}",
+ "when", "when: {expression: {simple: {expression: \"${body}
contains 'x'\"}}}",
+ "setBody", "setBody: {expression: {simple: {expression: \"Hello
${body}\"}}} or setBody: {expression:"
+ + " {constant: {expression: \"Hello\"}}}",
+ "setHeader", "setHeader: {name: id, expression: {simple:
{expression: \"${exchangeId}\"}}}",
+ "loop", "loop: {expression: {constant: {expression: \"3\"}}}",
+ "recipientList", "recipientList: {expression: {simple:
{expression: \"${header.to}\"}}}",
+ "script", "script: {expression: {groovy: {expression: \"...\"}}}",
+ "delay", "delay: {expression: {constant: {expression:
\"1000\"}}}");
private static final Set<String> SCRIPT_LANGUAGES = Set.of("groovy", "js",
"python", "python3", "mvel", "ognl",
"jq", "jsonpath", "xpath", "xquery", "spel", "jactl", "java",
"joor", "quickjs", "wasm", "datasonnet");
@@ -508,7 +509,8 @@ public class YamlValidator {
.messageKey("type")
.format(new MessageFormat("{0}"))
.arguments(name + ": ${...} is simple syntax, not " +
name + ": write the expression in " + name
- + " (" + example + "), or use simple: \"" +
text.replace("\"", "'") + "\"")
+ + " (" + example + "), or use simple:
{expression: \"" + text.replace("\"", "'")
+ + "\"}")
.build());
}
checkSimpleSyntaxInScripts(value, path.append(name), errors);
@@ -552,7 +554,8 @@ public class YamlValidator {
if (EXPRESSION_REQUIRED.contains(name) && (value == null ||
value.isNull() || value.isObject())
&& !hasExpression(value)) {
String example = EXPRESSION_EXAMPLES.getOrDefault(name,
- name + ": {simple: \"...\"} or " + name + ":
{constant: \"...\"}");
+ name + ": {expression: {simple: {expression:
\"...\"}}} or " + name
+ + ":
{expression: {constant: {expression: \"...\"}}}");
errors.add(Error.builder()
.keyword("required")
.instanceLocation(path.append(name))
@@ -873,7 +876,7 @@ public class YamlValidator {
String name = location.substring(location.lastIndexOf('/') + 1);
String value = instance.asText();
String message = String.format(
- "a plain value (%s) found, an expression expected: write %s:
{constant: \"%s\"} for a fixed value, or %s: {simple: \"...\"} for a dynamic
one",
+ "a plain value (%s) found, an expression expected: write %s:
{constant: {expression: \"%s\"}} for a fixed value, or %s: {simple:
{expression: \"...\"}} for a dynamic one",
value, name, value, name);
// the message is not a MessageFormat pattern (it contains braces), so
pass it as the single argument
return Error.builder()
@@ -917,8 +920,9 @@ public class YamlValidator {
.instanceLocation(error.getInstanceLocation())
.messageKey("type")
.format(new MessageFormat("{0}"))
- .arguments(error.getMessage() + " (an expression is
written with the language as the key, e.g."
- + " groovy: \"...\", simple: \"...\", constant:
\"...\"; the language: form is"
+ .arguments(error.getMessage() + " (an expression is
written with the language as the key and its"
+ + " expression: property, e.g. groovy:
{expression: \"...\"}, simple: {expression: \"...\"},"
+ + " constant: {expression: \"...\"}; the
language: form is"
+ " language: {language: groovy, expression:
\"...\"})")
.build();
}
@@ -949,7 +953,8 @@ public class YamlValidator {
.format(new MessageFormat("{0}"))
.arguments(error.getMessage() + " (" + entry + " is a map,
not a list: - " + entry + ": followed by its"
+ " properties indented" +
(entry.equals("onException")
- ? " (exception: [java.lang.Exception],
handled: {constant: \"true\"}, steps: [...])"
+ ? " (exception: [java.lang.Exception],
handled: {constant: {expression: \"true\"}},"
+ + " steps: [...])"
: "")
+ "; several of them are several - " + entry +
": items)")
.build();
@@ -1088,18 +1093,20 @@ public class YamlValidator {
&&
(EXPRESSION_REQUIRED.contains(location.substring(location.lastIndexOf('/') + 1))
|| location.endsWith("/expression"))) {
// setBody: {script: ...}: script is an EIP; the language is the
key of an expression
- hint = "script is an EIP step, not a language: write the language
as the key of the expression (groovy:"
- + " \"...\", simple: \"...\"), or run a script as its own
step with - script: {groovy: \"...\"}";
+ hint = "script is an EIP step, not a language: write the language
as the key of the expression (expression:"
+ + " {groovy: {expression: \"...\"}}, expression: {simple:
{expression: \"...\"}}), or run a script as"
+ + " its own step with - script: {expression: {groovy:
{expression: \"...\"}}}";
} else if (unknown.equals("bean") && !location.endsWith("/steps")) {
// setBody: {bean: myBean} : the bean language is method:
- hint = "the bean language is written as method: (method: {ref:
myBean, method: process}), or call the bean"
- + " as a step with - bean: {ref: myBean, method: process}";
+ hint = "the bean language is written as method: (expression:
{method: {ref: myBean, method: process}}), or"
+ + " call the bean as a step with - bean: {ref: myBean,
method: process}";
} else if
(location.matches(".*/(setHeader|setProperty|setVariable|removeHeader|removeProperty|removeVariable)")
&& closest(unknown,
knownProperties(String.valueOf(error.getSchemaLocation()))) == null) {
// setHeader: {CamelNumberA: {simple: ...}} : the name is a
property, not the key
String eip = location.substring(location.lastIndexOf('/') + 1);
hint = "the name is a property: " + eip + ": {name: " + unknown
- + (eip.startsWith("set") ? ", simple: \"...\"}" : "}") + "
(" + unknown + " is not the key)";
+ + (eip.startsWith("set") ? ", expression: {simple:
{expression: \"...\"}}}" : "}")
+ + " (" + unknown + " is not the key)";
} else if (location.endsWith("/bean")
&& (unknown.equals("parameters") || unknown.equals("args") ||
unknown.equals("arguments"))) {
hint = "arguments are written in the method call: bean: {ref:
myBean, method: \"process(${body}, 'x')\"}";
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
index 1fe58e691fd5..f6668272a421 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
@@ -53,7 +53,7 @@ public class YamlValidatorExpressionHintTest {
- log:
message: "Error: ${exception.message}"
""";
- assertHint(yaml, "handled: {constant: \"true\"}", "handled: {simple:
\"...\"}");
+ assertHint(yaml, "handled: {constant: {expression: \"true\"}}",
"handled: {simple: {expression: \"...\"}}");
}
@Test
@@ -67,7 +67,7 @@ public class YamlValidatorExpressionHintTest {
- log:
message: "Error: ${exception.message}"
""";
- assertHint(yaml, "continued: {constant: \"true\"}");
+ assertHint(yaml, "continued: {constant: {expression: \"true\"}}");
}
@Test
@@ -86,7 +86,7 @@ public class YamlValidatorExpressionHintTest {
- log:
message: "${body}"
""";
- assertHint(yaml, "completionSizeExpression: {constant: \"10\"}");
+ assertHint(yaml, "completionSizeExpression: {constant: {expression:
\"10\"}}");
}
@Test
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
index 6426251e3d45..0492b6a97f44 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
@@ -343,7 +343,8 @@ public class YamlValidatorPropertyHintTest {
parameters: [1, 2]
""");
assertThat(errors).anySatisfy(e ->
assertThat(e.getMessage()).contains("property 'CamelNumberA' is not defined")
- .contains("the name is a property: setHeader: {name:
CamelNumberA, simple: \"...\"}"));
+ .contains(
+ "the name is a property: setHeader: {name:
CamelNumberA, expression: {simple: {expression: \"...\"}}}"));
assertThat(errors).anySatisfy(e ->
assertThat(e.getMessage()).contains("property 'parameters' is not defined")
.contains("arguments are written in the method call"));
}
@@ -471,7 +472,7 @@ public class YamlValidatorPropertyHintTest {
assertThat(errors).hasSize(1);
assertThat(errors.get(0).getMessage())
.startsWith("groovy: ${...} is simple syntax, not groovy:
write the expression in groovy (body.value < 1")
- .contains("or use simple: \"${body.value} < 1\"");
+ .contains("or use simple: {expression: \"${body.value} <
1\"}");
}
@Test
@@ -604,7 +605,8 @@ public class YamlValidatorPropertyHintTest {
text: "println 'hi'"
""");
assertThat(errors).anyMatch(
- e -> e.getMessage().contains("the language as the key") &&
e.getMessage().contains("groovy: \"...\""));
+ e -> e.getMessage().contains("the language as the key")
+ && e.getMessage().contains("groovy: {expression:
\"...\"}"));
}
@Test
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
index f880731fc838..1b8140870efc 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
@@ -77,7 +77,7 @@ class OnExceptionTest extends YamlTestSupport {
for (Throwable t = e; t != null; t = t.cause) {
messages << t.message
}
- messages.any { it != null && it.contains('an expression is
expected here, not a plain value (true)') && it.contains('constant: "true"') }
+ messages.any { it != null && it.contains('an expression is
expected here, not a plain value (true)') && it.contains('constant:
{expression: "true"}') }
}
@@ -97,6 +97,6 @@ class OnExceptionTest extends YamlTestSupport {
for (Throwable t = e; t != null; t = t.cause) {
messages << t.message
}
- messages.any { it != null && it.contains('Error constructing YAML
node id: setBody: unsupported field: bean') && it.contains('method: {ref:
myBean, method: process}') }
+ messages.any { it != null && it.contains('Error constructing YAML
node id: setBody: unsupported field: bean') && it.contains('expression:
{method: {ref: myBean, method: process}}') }
}
}