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 3edf55b5d4a6 CAMEL-24850: the validator says what to write for
pollEnrich/enrich with a uri, a steps item inside steps, and EIPs directly
under step
3edf55b5d4a6 is described below
commit 3edf55b5d4a60dbe60ea1d9ba5c924354e50bd3d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 20 20:17:42 2026 +0200
CAMEL-24850: the validator says what to write for pollEnrich/enrich with a
uri, a steps item inside steps, and EIPs directly under step
Three shapes from the round-2 benchmark that the schema validator rejected
with a message that did not say what to write, so a model or a person needed
another round or two to get past them. Three rows in SchemaHints.PROPERTY,
before the generic did-you-mean row:
- pollEnrich: {uri: ...} / enrich: {resourceUri: ...}: the endpoint is an
expression, and the message says so with the value echoed.
- - steps: as a grouping item inside a steps list, or steps: on an EIP
without a pipeline: there is no group inside a list, move the items up or
use step: {id: ..., steps: [...]}; and no "did you mean 'step'?", which
led
the model to the next shape.
- EIP names directly under step: step is the Step EIP, its EIPs go in its
steps: list; one line for the whole item instead of one per EIP.
Closes #26633
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
.../camel/dsl/yaml/validator/SchemaHints.java | 22 ++++++
.../validator/YamlValidatorPropertyHintTest.java | 89 ++++++++++++++++++++++
2 files changed, 111 insertions(+)
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 c5db70953cd3..1576498172db 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
@@ -344,6 +344,28 @@ final class SchemaHints {
unknownProperty(".*/bean", m -> Set.of("parameters", "args",
"arguments").contains(m.unknown()),
m -> "arguments are written in the method call: bean:
{ref: myBean, method: \"process(${body},"
+ " 'x')\"}"),
+ // pollEnrich: {uri: ...}: the endpoint of enrich and pollEnrich
is an expression (CAMEL-24850)
+ unknownProperty(".*/(enrich|pollEnrich)",
+ m -> m.unknown().equals("uri") ||
m.unknown().equals("resourceUri"),
+ m -> {
+ JsonNode instance = m.error().getInstanceNode();
+ JsonNode value = instance != null ?
instance.get(m.unknown()) : null;
+ String uri = value != null && value.isValueNode() ?
value.asText() : "file:...";
+ return "the endpoint of " + m.name() + " is an
expression: write " + m.name()
+ + ": {expression: {constant: {expression: \"" +
uri + "\"}}}";
+ }),
+ // - steps: [...] as an item of a steps list, or steps: on an EIP
without a pipeline: there is no group item
+ // (and no "did you mean 'step'?", which leads to the Step EIP
with the EIPs as its keys)
+ unknownProperty(null, m -> m.unknown().equals("steps"),
+ m -> "steps: is the list of a route or of an EIP that owns
a pipeline (filter, split, choice, step);"
+ + " an EIP is an item of that list, not a group
inside it: move the items up one level, or use"
+ + " step: {id: ..., steps: [...]} for a named group"),
+ // step: {setHeader: ..., split: ...}: step is the Step EIP; one
message for the whole item, not one per EIP
+ replace("additionalProperties", ".*/step",
+ m -> m.unknown() != null &&
m.validator().stepNames().contains(m.unknown()),
+ m -> "step is the Step EIP, a named group: its EIPs go in
its steps: list (step: {id: ..., steps: [-"
+ + " setHeader: ...]})",
+ "additionalProperties", "additionalProperties"),
unknownProperty(null,
m -> YamlValidator.closest(m.unknown(),
m.validator().knownProperties(m.schemaLocation())) != null,
m -> "did you mean '"
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 e2cf1c3e461f..e62750b894db 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
@@ -38,6 +38,95 @@ public class YamlValidatorPropertyHintTest {
validator.init();
}
+ /** CAMEL-24850: the three shapes get the form to write, in both schema
modes. */
+ private static List<YamlValidator> bothModes() throws Exception {
+ YamlValidator canonical = new YamlValidator(true);
+ canonical.init();
+ return List.of(validator, canonical);
+ }
+
+ @Test
+ public void testPollEnrichWithAUriSaysItIsAnExpression() throws Exception {
+ for (YamlValidator v : bothModes()) {
+ List<Error> errors = v.validate("""
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - pollEnrich:
+ uri: file:./order.json
+ - enrich:
+ resourceUri: direct:prices
+ """);
+ assertThat(errors).hasSize(2);
+ assertThat(errors.get(0).getMessage())
+ .contains("the endpoint of pollEnrich is an expression:
write pollEnrich: {expression: {constant:"
+ + " {expression: \"file:./order.json\"}}}");
+ assertThat(errors.get(1).getMessage())
+ .contains("the endpoint of enrich is an expression: write
enrich: {expression: {constant:"
+ + " {expression: \"direct:prices\"}}}");
+ }
+ }
+
+ @Test
+ public void testStepsAsAGroupItemSaysThereIsNoGroup() throws Exception {
+ for (YamlValidator v : bothModes()) {
+ List<Error> errors = v.validate("""
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - steps:
+ - setHeader:
+ name: a
+ expression:
+ constant:
+ expression: "1"
+ - to:
+ uri: mock:a
+ steps:
+ - log:
+ message: hi
+ """);
+ assertThat(errors).hasSize(2);
+ for (Error e : errors) {
+ assertThat(e.getMessage())
+ .contains("steps: is the list of a route or of an EIP
that owns a pipeline")
+ .contains("step: {id: ..., steps: [...]} for a named
group")
+ .doesNotContain("did you mean");
+ }
+ }
+ }
+
+ @Test
+ public void testEipsDirectlyUnderStepGetOneMessageWithTheShape() throws
Exception {
+ for (YamlValidator v : bothModes()) {
+ List<Error> errors = v.validate("""
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - step:
+ setHeader:
+ name: a
+ expression:
+ constant:
+ expression: "1"
+ split:
+ expression:
+ simple:
+ expression: "${body}"
+ steps:
+ - log:
+ message: hi
+ """);
+ assertThat(errors).hasSize(1);
+ assertThat(errors.get(0).getMessage())
+ .isEqualTo("step is the Step EIP, a named group: its EIPs
go in its steps: list"
+ + " (step: {id: ..., steps: [- setHeader:
...]})");
+ }
+ }
+
@Test
public void testMisspelledOptionGetsTheClosestName() throws Exception {
List<Error> errors = validator.validate("""