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 38fac5360201 CAMEL-24745: the canonical YAML DSL schema requires
route: a top-level from: is the compact notation, reported with the route form
38fac5360201 is described below
commit 38fac5360201051acb916b1fd8ad692ad8a69c8b
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 20 20:17:31 2026 +0200
CAMEL-24745: the canonical YAML DSL schema requires route: a top-level
from: is the compact notation, reported with the route form
camel run warned on a top-level - from: without - route: as the compact
notation, while camel validate yaml --canonical accepted it because the
canonical schema listed from among its top-level entries. The two now agree,
on the side of the runtime: the canonical form requires route:. XML routes
have always been <route> with <from> inside, the YAML DSL follows the XML
shape, and Kaoto and Karavan write the route form; the Java DSL's bare
from()
is a convenience of the RouteBuilder, not a document shape.
The schema generator leaves the compact @YamlIn type out of the canonical
schema, so camelYamlDsl-canonical.json has no top-level from and no orphan
definition for it. The validator reports a top-level from: with the form to
write, the way it reports the other compact shapes. The YAML DSL doc's
examples, the Integration template of camel init and the affected tests are
written in the route form; a Kamelet's template: keeps its from:, which is
the Kamelet spec's shape. Upgrade guide note added.
Closes #26632
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
.../org/apache/camel/catalog/docs/yaml-dsl.adoc | 141 +++++++++++----------
.../camel/catalog/schemas/camelYamlDsl-model.json | 110 ++++++++--------
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 10 ++
.../main/resources/templates/integration.yaml.ftl | 23 ++--
.../dsl/jbang/core/common/TemplateHelperTest.java | 5 +-
.../maven/dsl/yaml/GenerateYamlSchemaMojo.java | 8 ++
.../camel/dsl/yaml/validator/SchemaHints.java | 6 +
.../yaml/validator/YamlCanonicalValidatorTest.java | 18 +++
.../validator/YamlValidatorExpressionHintTest.java | 25 ++--
.../validator/YamlValidatorScalarLeniencyTest.java | 42 +++---
.../resources/schema/camelYamlDsl-canonical.json | 34 -----
.../resources/schema/camelYamlDsl-model.json | 110 ++++++++--------
.../camel-yaml-dsl/src/main/docs/yaml-dsl.adoc | 141 +++++++++++----------
.../camel/dsl/yaml/YamlCompletionTreeTest.java | 4 +-
14 files changed, 348 insertions(+), 329 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/yaml-dsl.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/yaml-dsl.adoc
index c1ad1cd0126c..0f9c094dcc45 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/yaml-dsl.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/yaml-dsl.adoc
@@ -19,20 +19,21 @@ A route is collection of elements defined as follows:
[source,yaml]
----
-- from: #<1>
- uri: "direct:start"
- steps: #<2>
- - filter:
- expression:
- simple: "${in.header.continue} == true"
- steps: #<2>
- - to:
- uri: "log:filtered"
- - to:
- uri: "log:original"
+- route: #<1>
+ from:
+ uri: "direct:start"
+ steps: #<2>
+ - filter:
+ expression:
+ simple: "${in.header.continue} == true"
+ steps: #<2>
+ - to:
+ uri: "log:filtered"
+ - to:
+ uri: "log:original"
----
-<1> route entry point, by default `from` and `rest` are supported
+<1> a route, with the `from` endpoint it consumes from (`rest`, `beans`,
`onException` and more are the other top-level entries)
<2> processing steps
[NOTE]
@@ -190,14 +191,15 @@ This allows YAML such as:
[source,yaml]
----
-- from:
- uri: "direct:start"
- steps:
- - myStep:
- id: "custom-step"
- steps:
- - to:
- uri: "mock:result"
+- route:
+ from:
+ uri: "direct:start"
+ steps:
+ - myStep:
+ id: "custom-step"
+ steps:
+ - to:
+ uri: "mock:result"
----
Resolvers are discovered when YAML routes are parsed. Camel starts with the
built-in YAML DSL resolvers, then adds the
@@ -295,25 +297,27 @@ To define an endpoint with the YAML dsl you have two
options:
+
[source,yaml]
----
-- from:
- uri: "timer:tick?period=1s"
- steps:
- - to:
- uri: "telegram:bots?authorizationToken=XXX"
+- route:
+ from:
+ uri: "timer:tick?period=1s"
+ steps:
+ - to:
+ uri: "telegram:bots?authorizationToken=XXX"
----
. Using URI and parameters:
+
[source,yaml]
----
-- from:
- uri: "timer://tick"
- parameters:
- period: "1s"
- steps:
- - to:
- uri: "telegram:bots"
- parameters:
- authorizationToken: "XXX"
+- route:
+ from:
+ uri: "timer://tick"
+ parameters:
+ period: "1s"
+ steps:
+ - to:
+ uri: "telegram:bots"
+ parameters:
+ authorizationToken: "XXX"
----
=== Map values for parameters
@@ -326,16 +330,17 @@ configure _tags_ as a Map:
[source,yaml]
----
-- from:
- uri: "plc4x"
- parameters:
- driver: "some driver url here"
- tags:
- "tags_2": "XXX"
- "tags_6": "YYY"
- steps:
- - to:
- uri: "log:plc"
+- route:
+ from:
+ uri: "plc4x"
+ parameters:
+ driver: "some driver url here"
+ tags:
+ "tags_2": "XXX"
+ "tags_6": "YYY"
+ steps:
+ - to:
+ uri: "log:plc"
----
In this example the _tags_ options is of Map type and can be configured using
YAML map syntax.
@@ -671,27 +676,28 @@ as shown below:
[source,yaml]
----
-- from:
- uri: "direct:start"
- steps:
- - choice:
- when:
- - jsonpath:
- expression: "person.middlename"
- suppressExceptions: true
- steps:
- - to:
- uri: mock:middle
- - jsonpath:
- expression: "person.lastname"
- suppressExceptions: true
- steps:
- - to:
- uri: mock:last
- otherwise:
- steps:
+- route:
+ from:
+ uri: "direct:start"
+ steps:
+ - choice:
+ when:
+ - jsonpath:
+ expression: "person.middlename"
+ suppressExceptions: true
+ steps:
+ - to:
+ uri: mock:middle
+ - jsonpath:
+ expression: "person.lastname"
+ suppressExceptions: true
+ steps:
- to:
- uri: mock:other
+ uri: mock:last
+ otherwise:
+ steps:
+ - to:
+ uri: mock:other
----
In the route above, the following message
@@ -735,6 +741,7 @@ The canonical schema removes all implicit patterns:
* No string shorthands: EIPs like `log` must use the object form with explicit
properties.
* No implicit expressions: Expression-aware EIPs require the `expression`
wrapper.
* No `oneOf`/`anyOf`/`not` constructs: The schema uses only simple `type:
object` with `properties`.
+* A route is written under `route:`: a top-level `from:` is the compact
notation of a route.
This results in a schema that is approximately 25% smaller and significantly
easier for tooling to process.
@@ -932,8 +939,9 @@ camel validate normalize --output normalized/ myroute.yaml
Camel logs a WARN message when YAML routes use compact (shorthand) notation
instead of
the canonical (explicit) form: a step written as a string (`log: "..."`), a
language written as a
string (`simple: "..."`), or a language key directly on the EIP (`setBody:
{simple: ...}`) instead of
-under `expression:`. This is to encourage adopting the canonical style which is
-more friendly for tooling and AI assistants.
+under `expression:`. A top-level `- from:` without `- route:` is compact
notation as well: a route is
+written under `route:`, as an XML route is always a `<route>`. This is to
encourage adopting the canonical
+style which is more friendly for tooling and AI assistants.
The warning is logged once per resource file and looks like:
@@ -964,6 +972,7 @@ This reports each use of the compact notation with the
canonical form to write,
----
/0/route/from/steps/0/setBody: setBody: {simple: ...} is the deprecated
compact notation: an expression is written under expression: (setBody:
{expression: {simple: {expression: "..."}}}); camel validate normalize rewrites
a file in the canonical format
/0/route/from/steps/1/log: log: "..." is the deprecated compact notation:
write log: {message: "..."}; camel validate normalize rewrites a file in the
canonical format
+/1: a top-level from: is the deprecated compact notation: a route is written
under route: (- route: {from: {uri: "...", steps: [...]}}); camel validate
normalize rewrites a file in the canonical format
----
The `YamlValidator` class supports both schemas programmatically:
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
index baae198c55d3..984dfb7c82ee 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
@@ -22,13 +22,6 @@
"title" : "Error Handler",
"description" : "Configures error handling strategy for routes,
controlling how failed exchanges are handled and redelivered",
"label" : "configuration,error,errorhandling"
- }, {
- "name" : "from",
- "type" : "object",
- "ref" : "from",
- "title" : "From",
- "description" : "Defines the consumer endpoint that acts as the input
source for a route",
- "label" : "eip,endpoint,routing"
}, {
"name" : "intercept",
"type" : "object",
@@ -865,58 +858,6 @@
"type" : "string"
} ]
},
- "from" : {
- "title" : "From",
- "description" : "Defines the consumer endpoint that acts as the input
source for a route",
- "label" : "eip,endpoint,routing",
- "aliases" : [ "source", "ingress" ],
- "isListItem" : true,
- "children" : [ {
- "name" : "id",
- "type" : "string",
- "displayName" : "Id",
- "kind" : "attribute",
- "index" : 0,
- "group" : "common"
- }, {
- "name" : "note",
- "type" : "string",
- "displayName" : "Note",
- "kind" : "attribute",
- "index" : 1,
- "group" : "common"
- }, {
- "name" : "description",
- "type" : "string",
- "displayName" : "Description",
- "kind" : "attribute",
- "index" : 2,
- "group" : "common"
- }, {
- "name" : "uri",
- "type" : "string",
- "required" : true,
- "displayName" : "Uri",
- "kind" : "attribute",
- "index" : 3,
- "group" : "common"
- }, {
- "name" : "variableReceive",
- "type" : "string",
- "displayName" : "Variable Receive",
- "kind" : "attribute",
- "index" : 4,
- "group" : "common"
- }, {
- "name" : "parameters",
- "type" : "object"
- }, {
- "name" : "steps",
- "type" : "array",
- "ref" : "steps",
- "required" : true
- } ]
- },
"a2aSubTask" : {
"title" : "A2A Sub Task",
"description" : "Groups route steps and emits A2A (Agent-to-Agent)
protocol progress events before, after, or when the grouped work fails",
@@ -3047,6 +2988,57 @@
"ref" : "steps"
} ]
},
+ "from" : {
+ "title" : "From",
+ "description" : "Defines the consumer endpoint that acts as the input
source for a route",
+ "label" : "eip,endpoint,routing",
+ "aliases" : [ "source", "ingress" ],
+ "children" : [ {
+ "name" : "id",
+ "type" : "string",
+ "displayName" : "Id",
+ "kind" : "attribute",
+ "index" : 0,
+ "group" : "common"
+ }, {
+ "name" : "note",
+ "type" : "string",
+ "displayName" : "Note",
+ "kind" : "attribute",
+ "index" : 1,
+ "group" : "common"
+ }, {
+ "name" : "description",
+ "type" : "string",
+ "displayName" : "Description",
+ "kind" : "attribute",
+ "index" : 2,
+ "group" : "common"
+ }, {
+ "name" : "uri",
+ "type" : "string",
+ "required" : true,
+ "displayName" : "Uri",
+ "kind" : "attribute",
+ "index" : 3,
+ "group" : "common"
+ }, {
+ "name" : "variableReceive",
+ "type" : "string",
+ "displayName" : "Variable Receive",
+ "kind" : "attribute",
+ "index" : 4,
+ "group" : "common"
+ }, {
+ "name" : "parameters",
+ "type" : "object"
+ }, {
+ "name" : "steps",
+ "type" : "array",
+ "ref" : "steps",
+ "required" : true
+ } ]
+ },
"globalOption" : {
"title" : "Global Option",
"description" : "Defines a single global configuration option as a
key/value pair for the CamelContext",
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 5938aeccfa92..a5b9bd21be8f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -870,6 +870,16 @@ to the whole process, so a browser session served by
`--web` would be recorded i
file as the local session. Previously the combination was accepted, but
recording never produced any
output, so run the two modes in separate processes instead.
+=== camel-yaml-dsl - the canonical schema requires route:
+
+A top-level `- from:` without `- route:` is the compact notation of a route,
and the canonical schema
+(`camelYamlDsl-canonical.json`) no longer lists `from` among its top-level
entries: a route is written under
+`route:`, as an XML route is always a `<route>`, and as Kaoto and Karavan
write it. `camel validate yaml --canonical`
+now reports a top-level `from:` with the form to write, the way it reports the
other compact shapes, and
+`camel validate normalize` rewrites it. The Integration template of `camel
init` writes its flows in the `- route:`
+form as well; a Kamelet's `template:` keeps its `from:`, which is the Kamelet
spec's shape. The classic schema and `camel run` are unchanged: the file keeps
working,
+with the compact notation warning `camel run` already logged for it.
+
=== camel-mail
`MimeMultipartDataFormat` now uses `MailHeaderFilterStrategy` instead of a
plain
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/integration.yaml.ftl
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/integration.yaml.ftl
index 3c200771ad7e..a043286bd553 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/integration.yaml.ftl
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/integration.yaml.ftl
@@ -22,14 +22,15 @@ metadata:
name: [=Name]
spec:
flows:
- - from:
- uri: "timer:yaml"
- parameters:
- period: "1000"
- steps:
- - setBody:
- expression:
- constant:
- expression: "Hello Camel from yaml"
- - log:
- message: "${body}"
+ - route:
+ from:
+ uri: "timer:yaml"
+ parameters:
+ period: "1000"
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: "Hello Camel from yaml"
+ - log:
+ message: "${body}"
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/TemplateHelperTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/TemplateHelperTest.java
index a7f91b4ef0d9..f583461a359c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/TemplateHelperTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/TemplateHelperTest.java
@@ -524,10 +524,11 @@ class TemplateHelperTest {
void testInitKameletTemplatesAreCanonical(String name) throws Exception {
String content = loadInitTemplate(name).replace("[=Name]",
"my-kamelet");
- // the Kamelet CR wraps the route in spec.template as its from
+ // the Kamelet CR wraps the route in spec.template as its from (the
Kamelet spec's shape); validated as the
+ // route it is, since a top-level from: is the compact notation of a
route file (CAMEL-24745)
Map<String, Object> cr = new Yaml().load(content);
Map<String, Object> spec = (Map<String, Object>) cr.get("spec");
- assertCanonical(name, new Yaml().dump(List.of(spec.get("template"))));
+ assertCanonical(name, new Yaml().dump(List.of(Map.of("route",
spec.get("template")))));
}
private static void assertCanonical(String name, String yaml) throws
Exception {
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java
index 792d1b506f62..117822f3c546 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java
@@ -76,6 +76,9 @@ public class GenerateYamlSchemaMojo extends
GenerateYamlSupportMojo {
@Parameter(defaultValue = "false")
private boolean canonical;
+ /** The top-level entries the canonical schema leaves out: the compact
notation of a route. */
+ private static final Set<String> COMPACT_TOP_LEVEL_NODES = Set.of("from");
+
private ObjectNode items;
private ObjectNode definitions;
private ObjectNode step;
@@ -135,6 +138,11 @@ public class GenerateYamlSchemaMojo extends
GenerateYamlSupportMojo {
continue;
}
if (hasAnnotation(entry.getValue(), YAML_IN_ANNOTATION)) {
+ if (canonical && !nodes.isEmpty() &&
COMPACT_TOP_LEVEL_NODES.containsAll(nodes)) {
+ // a top-level from: without route: is the compact
notation of a route (the runtime warns on it,
+ // CAMEL-24745): the canonical schema requires - route:
{from: ...}, as XML requires <route>
+ continue;
+ }
nodes.forEach(node -> {
items.withObject("/properties")
.putObject(node)
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
index e3f6102c0171..c5db70953cd3 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
@@ -372,6 +372,12 @@ final class SchemaHints {
* line, and the normalize command.
*/
static final List<Hint> COMPACT = List.of(
+ // - from: at the top level: the route is written under route:, as
XML writes <route> (CAMEL-24745)
+ replace("additionalProperties", null,
+ m -> "from".equals(m.unknown()) && m.nameIsIndex() &&
m.parentName().isEmpty(),
+ m -> "a top-level from: is the deprecated compact
notation: a route is written under route:"
+ + " (- route: {from: {uri: \"...\", steps: [...]}})"
+ NORMALIZE_HINT,
+ COMPACT_NOTATION, COMPACT_NOTATION),
// setBody: {simple: ...} or when: [- simple: ...]: the language
key sits on the EIP, not under expression:
replace("additionalProperties", null,
m -> m.unknown() != null &&
m.validator().languageKeys().contains(m.unknown()),
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlCanonicalValidatorTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlCanonicalValidatorTest.java
index 73dd235aea44..43cc7bbdb376 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlCanonicalValidatorTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlCanonicalValidatorTest.java
@@ -164,6 +164,24 @@ public class YamlCanonicalValidatorTest {
}
// CAMEL-24723: the compact notation is reported as such, one message per
occurrence, with the canonical form
+ @Test
+ public void testTopLevelFromIsReportedWithTheRouteForm() throws Exception {
+ // CAMEL-24745: a top-level from: is the compact notation of a route;
the classic schema accepts it, the
+ // canonical schema requires route:, as XML requires <route>
+ var route = """
+ - from:
+ uri: "direct:start"
+ steps:
+ - to:
+ uri: "mock:a"
+ """;
+ assertThat(classicValidator.validate(route)).isEmpty();
+ assertThat(canonicalValidator.validate(route)).extracting(e ->
e.getInstanceLocation() + ": " + e.getMessage())
+ .containsExactly("/0: a top-level from: is the deprecated
compact notation: a route is written under"
+ + " route: (- route: {from: {uri: \"...\",
steps: [...]}});"
+ + " camel validate normalize rewrites a file
in the canonical format");
+ }
+
@Test
public void testCompactNotationIsReportedWithTheCanonicalForm() throws
Exception {
var route = """
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 f6668272a421..0f04e2ba86cb 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
@@ -73,18 +73,19 @@ public class YamlValidatorExpressionHintTest {
@Test
public void testAggregateCompletionSizeAsPlainValue() {
String yaml = """
- - from:
- uri: timer:tick
- steps:
- - aggregate:
- correlationExpression:
- constant:
- expression: "a"
- completionSizeExpression: 10
- aggregationStrategy: myStrategy
- steps:
- - log:
- message: "${body}"
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - aggregate:
+ correlationExpression:
+ constant:
+ expression: "a"
+ completionSizeExpression: 10
+ aggregationStrategy: myStrategy
+ steps:
+ - log:
+ message: "${body}"
""";
assertHint(yaml, "completionSizeExpression: {constant: {expression:
\"10\"}}");
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorScalarLeniencyTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorScalarLeniencyTest.java
index 961f31a64cb9..29e41035a10d 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorScalarLeniencyTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorScalarLeniencyTest.java
@@ -108,24 +108,26 @@ public class YamlValidatorScalarLeniencyTest {
public void testStepsAsMapStillRejected() {
// a map where the schema expects a list is what the runtime rejects
with "Node type map is invalid, expected array"
assertRejectedYaml("""
- - from:
- uri: timer:tick
- steps:
- log: "hi"
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ log: "hi"
""", "array expected");
}
@Test
public void testWhenAsMapStillRejected() {
assertRejectedYaml("""
- - from:
- uri: timer:tick
- steps:
- - choice:
- when:
- simple: "${body} == 1"
- steps:
- - log: "one"
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - choice:
+ when:
+ simple: "${body} == 1"
+ steps:
+ - log: "one"
""", "array expected");
}
@@ -136,19 +138,21 @@ public class YamlValidatorScalarLeniencyTest {
get:
path: /hello
to: direct:hello
- - from:
- uri: direct:hello
- steps:
- - log: "hi"
+ - route:
+ from:
+ uri: direct:hello
+ steps:
+ - log: "hi"
""", "array expected");
}
@Test
public void testScalarWhereStepsExpectedStillRejected() {
assertRejectedYaml("""
- - from:
- uri: timer:tick
- steps: "log:hi"
+ - route:
+ from:
+ uri: timer:tick
+ steps: "log:hi"
""", "array expected");
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
index b8a2fb4bb707..9d8fc986b152 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
@@ -284,37 +284,6 @@
},
"required" : [ "steps", "uri" ]
},
-
"org.apache.camel.dsl.yaml.deserializers.RouteFromDefinitionDeserializer" : {
- "type" : "object",
- "additionalProperties" : false,
- "properties" : {
- "description" : {
- "type" : "string"
- },
- "id" : {
- "type" : "string"
- },
- "note" : {
- "type" : "string"
- },
- "parameters" : {
- "type" : "object"
- },
- "steps" : {
- "type" : "array",
- "items" : {
- "$ref" :
"#/items/definitions/org.apache.camel.model.ProcessorDefinition"
- }
- },
- "uri" : {
- "type" : "string"
- },
- "variableReceive" : {
- "type" : "string"
- }
- },
- "required" : [ "steps", "uri" ]
- },
"org.apache.camel.model.A2ASubTaskDefinition" : {
"title" : "A2A Sub Task",
"description" : "Groups route steps and emits A2A (Agent-to-Agent)
protocol progress events before, after, or when the grouped work fails",
@@ -13338,9 +13307,6 @@
"errorHandler" : {
"$ref" :
"#/items/definitions/org.apache.camel.dsl.yaml.deserializers.ErrorHandlerDeserializer"
},
- "from" : {
- "$ref" :
"#/items/definitions/org.apache.camel.dsl.yaml.deserializers.RouteFromDefinitionDeserializer"
- },
"intercept" : {
"$ref" :
"#/items/definitions/org.apache.camel.model.InterceptDefinition"
},
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
index baae198c55d3..984dfb7c82ee 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
@@ -22,13 +22,6 @@
"title" : "Error Handler",
"description" : "Configures error handling strategy for routes,
controlling how failed exchanges are handled and redelivered",
"label" : "configuration,error,errorhandling"
- }, {
- "name" : "from",
- "type" : "object",
- "ref" : "from",
- "title" : "From",
- "description" : "Defines the consumer endpoint that acts as the input
source for a route",
- "label" : "eip,endpoint,routing"
}, {
"name" : "intercept",
"type" : "object",
@@ -865,58 +858,6 @@
"type" : "string"
} ]
},
- "from" : {
- "title" : "From",
- "description" : "Defines the consumer endpoint that acts as the input
source for a route",
- "label" : "eip,endpoint,routing",
- "aliases" : [ "source", "ingress" ],
- "isListItem" : true,
- "children" : [ {
- "name" : "id",
- "type" : "string",
- "displayName" : "Id",
- "kind" : "attribute",
- "index" : 0,
- "group" : "common"
- }, {
- "name" : "note",
- "type" : "string",
- "displayName" : "Note",
- "kind" : "attribute",
- "index" : 1,
- "group" : "common"
- }, {
- "name" : "description",
- "type" : "string",
- "displayName" : "Description",
- "kind" : "attribute",
- "index" : 2,
- "group" : "common"
- }, {
- "name" : "uri",
- "type" : "string",
- "required" : true,
- "displayName" : "Uri",
- "kind" : "attribute",
- "index" : 3,
- "group" : "common"
- }, {
- "name" : "variableReceive",
- "type" : "string",
- "displayName" : "Variable Receive",
- "kind" : "attribute",
- "index" : 4,
- "group" : "common"
- }, {
- "name" : "parameters",
- "type" : "object"
- }, {
- "name" : "steps",
- "type" : "array",
- "ref" : "steps",
- "required" : true
- } ]
- },
"a2aSubTask" : {
"title" : "A2A Sub Task",
"description" : "Groups route steps and emits A2A (Agent-to-Agent)
protocol progress events before, after, or when the grouped work fails",
@@ -3047,6 +2988,57 @@
"ref" : "steps"
} ]
},
+ "from" : {
+ "title" : "From",
+ "description" : "Defines the consumer endpoint that acts as the input
source for a route",
+ "label" : "eip,endpoint,routing",
+ "aliases" : [ "source", "ingress" ],
+ "children" : [ {
+ "name" : "id",
+ "type" : "string",
+ "displayName" : "Id",
+ "kind" : "attribute",
+ "index" : 0,
+ "group" : "common"
+ }, {
+ "name" : "note",
+ "type" : "string",
+ "displayName" : "Note",
+ "kind" : "attribute",
+ "index" : 1,
+ "group" : "common"
+ }, {
+ "name" : "description",
+ "type" : "string",
+ "displayName" : "Description",
+ "kind" : "attribute",
+ "index" : 2,
+ "group" : "common"
+ }, {
+ "name" : "uri",
+ "type" : "string",
+ "required" : true,
+ "displayName" : "Uri",
+ "kind" : "attribute",
+ "index" : 3,
+ "group" : "common"
+ }, {
+ "name" : "variableReceive",
+ "type" : "string",
+ "displayName" : "Variable Receive",
+ "kind" : "attribute",
+ "index" : 4,
+ "group" : "common"
+ }, {
+ "name" : "parameters",
+ "type" : "object"
+ }, {
+ "name" : "steps",
+ "type" : "array",
+ "ref" : "steps",
+ "required" : true
+ } ]
+ },
"globalOption" : {
"title" : "Global Option",
"description" : "Defines a single global configuration option as a
key/value pair for the CamelContext",
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
index c1ad1cd0126c..0f9c094dcc45 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
@@ -19,20 +19,21 @@ A route is collection of elements defined as follows:
[source,yaml]
----
-- from: #<1>
- uri: "direct:start"
- steps: #<2>
- - filter:
- expression:
- simple: "${in.header.continue} == true"
- steps: #<2>
- - to:
- uri: "log:filtered"
- - to:
- uri: "log:original"
+- route: #<1>
+ from:
+ uri: "direct:start"
+ steps: #<2>
+ - filter:
+ expression:
+ simple: "${in.header.continue} == true"
+ steps: #<2>
+ - to:
+ uri: "log:filtered"
+ - to:
+ uri: "log:original"
----
-<1> route entry point, by default `from` and `rest` are supported
+<1> a route, with the `from` endpoint it consumes from (`rest`, `beans`,
`onException` and more are the other top-level entries)
<2> processing steps
[NOTE]
@@ -190,14 +191,15 @@ This allows YAML such as:
[source,yaml]
----
-- from:
- uri: "direct:start"
- steps:
- - myStep:
- id: "custom-step"
- steps:
- - to:
- uri: "mock:result"
+- route:
+ from:
+ uri: "direct:start"
+ steps:
+ - myStep:
+ id: "custom-step"
+ steps:
+ - to:
+ uri: "mock:result"
----
Resolvers are discovered when YAML routes are parsed. Camel starts with the
built-in YAML DSL resolvers, then adds the
@@ -295,25 +297,27 @@ To define an endpoint with the YAML dsl you have two
options:
+
[source,yaml]
----
-- from:
- uri: "timer:tick?period=1s"
- steps:
- - to:
- uri: "telegram:bots?authorizationToken=XXX"
+- route:
+ from:
+ uri: "timer:tick?period=1s"
+ steps:
+ - to:
+ uri: "telegram:bots?authorizationToken=XXX"
----
. Using URI and parameters:
+
[source,yaml]
----
-- from:
- uri: "timer://tick"
- parameters:
- period: "1s"
- steps:
- - to:
- uri: "telegram:bots"
- parameters:
- authorizationToken: "XXX"
+- route:
+ from:
+ uri: "timer://tick"
+ parameters:
+ period: "1s"
+ steps:
+ - to:
+ uri: "telegram:bots"
+ parameters:
+ authorizationToken: "XXX"
----
=== Map values for parameters
@@ -326,16 +330,17 @@ configure _tags_ as a Map:
[source,yaml]
----
-- from:
- uri: "plc4x"
- parameters:
- driver: "some driver url here"
- tags:
- "tags_2": "XXX"
- "tags_6": "YYY"
- steps:
- - to:
- uri: "log:plc"
+- route:
+ from:
+ uri: "plc4x"
+ parameters:
+ driver: "some driver url here"
+ tags:
+ "tags_2": "XXX"
+ "tags_6": "YYY"
+ steps:
+ - to:
+ uri: "log:plc"
----
In this example the _tags_ options is of Map type and can be configured using
YAML map syntax.
@@ -671,27 +676,28 @@ as shown below:
[source,yaml]
----
-- from:
- uri: "direct:start"
- steps:
- - choice:
- when:
- - jsonpath:
- expression: "person.middlename"
- suppressExceptions: true
- steps:
- - to:
- uri: mock:middle
- - jsonpath:
- expression: "person.lastname"
- suppressExceptions: true
- steps:
- - to:
- uri: mock:last
- otherwise:
- steps:
+- route:
+ from:
+ uri: "direct:start"
+ steps:
+ - choice:
+ when:
+ - jsonpath:
+ expression: "person.middlename"
+ suppressExceptions: true
+ steps:
+ - to:
+ uri: mock:middle
+ - jsonpath:
+ expression: "person.lastname"
+ suppressExceptions: true
+ steps:
- to:
- uri: mock:other
+ uri: mock:last
+ otherwise:
+ steps:
+ - to:
+ uri: mock:other
----
In the route above, the following message
@@ -735,6 +741,7 @@ The canonical schema removes all implicit patterns:
* No string shorthands: EIPs like `log` must use the object form with explicit
properties.
* No implicit expressions: Expression-aware EIPs require the `expression`
wrapper.
* No `oneOf`/`anyOf`/`not` constructs: The schema uses only simple `type:
object` with `properties`.
+* A route is written under `route:`: a top-level `from:` is the compact
notation of a route.
This results in a schema that is approximately 25% smaller and significantly
easier for tooling to process.
@@ -932,8 +939,9 @@ camel validate normalize --output normalized/ myroute.yaml
Camel logs a WARN message when YAML routes use compact (shorthand) notation
instead of
the canonical (explicit) form: a step written as a string (`log: "..."`), a
language written as a
string (`simple: "..."`), or a language key directly on the EIP (`setBody:
{simple: ...}`) instead of
-under `expression:`. This is to encourage adopting the canonical style which is
-more friendly for tooling and AI assistants.
+under `expression:`. A top-level `- from:` without `- route:` is compact
notation as well: a route is
+written under `route:`, as an XML route is always a `<route>`. This is to
encourage adopting the canonical
+style which is more friendly for tooling and AI assistants.
The warning is logged once per resource file and looks like:
@@ -964,6 +972,7 @@ This reports each use of the compact notation with the
canonical form to write,
----
/0/route/from/steps/0/setBody: setBody: {simple: ...} is the deprecated
compact notation: an expression is written under expression: (setBody:
{expression: {simple: {expression: "..."}}}); camel validate normalize rewrites
a file in the canonical format
/0/route/from/steps/1/log: log: "..." is the deprecated compact notation:
write log: {message: "..."}; camel validate normalize rewrites a file in the
canonical format
+/1: a top-level from: is the deprecated compact notation: a route is written
under route: (- route: {from: {uri: "...", steps: [...]}}); camel validate
normalize rewrites a file in the canonical format
----
The `YamlValidator` class supports both schemas programmatically:
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlCompletionTreeTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlCompletionTreeTest.java
index b025c9efc12a..73872c14a910 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlCompletionTreeTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlCompletionTreeTest.java
@@ -55,9 +55,11 @@ class YamlCompletionTreeTest {
assertThat(root).as("root node must exist").isNotNull();
Set<String> names = childNames(root);
- assertThat(names).contains("route", "from", "rest", "beans",
+ assertThat(names).contains("route", "rest", "beans",
"errorHandler", "onException", "routeConfiguration",
"intercept", "interceptFrom", "interceptSendToEndpoint");
+ // a top-level from: is the compact notation of a route: the canonical
tree has it under route only (CAMEL-24745)
+ assertThat(names).doesNotContain("from");
}
@Test