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 7d93a74f9203 CAMEL-25529: camel-yaml-dsl-validator - an option of a
step's EIP at the column of the EIP: say which key it lines up with (#27660)
7d93a74f9203 is described below
commit 7d93a74f9203af657c36eda840d078e243e9bd4d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 10 15:17:23 2026 +0200
CAMEL-25529: camel-yaml-dsl-validator - an option of a step's EIP at the
column of the EIP: say which key it lines up with (#27660)
The hint said "steps: is at the column of - split: ... indented under
split:, two spaces more"; read from the dash, that is where steps: already was,
and a second error said to move the items up a level. The hint now names the
key the option lines up with and the one it belongs next to, and the repeated
error is dropped.
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../camel/dsl/yaml/validator/SchemaHints.java | 65 +++++++++++++++++++++-
.../camel/dsl/yaml/validator/YamlValidator.java | 2 +
.../dsl/yaml/validator/StructureHintTest.java | 27 ++++++++-
3 files changed, 91 insertions(+), 3 deletions(-)
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 576430ae89d5..7f4c8edec0c8 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
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml.validator;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -191,6 +192,10 @@ final class SchemaHints {
return generic;
}
Set<String> options = m.validator().optionsOf(eip);
+ // the key the option lines up with once it is under the EIP: "the
column of - split:" read as the column of
+ // the dash, and two spaces more than that is where the model had put
it already (it wrote the same five times)
+ JsonNode value = node.get(eip);
+ String sibling = value != null && value.isObject() && value.size() > 0
? value.fieldNames().next() : null;
List<String> parts = new ArrayList<>();
for (String k : keys) {
if (k.equals(eip)) {
@@ -198,9 +203,14 @@ final class SchemaHints {
}
if (steps.contains(k)) {
parts.add(k + ": is another EIP: start it as its own item, - "
+ k + ":");
+ } else if (options.contains(k) && sibling != null) {
+ parts.add(k + ": lines up with " + eip + ":, so it is read as
a second key of the - item; it is an"
+ + " option of " + eip + ": indent " + k + ": and the
lines under it two spaces more, so " + k
+ + ": lines up with " + sibling + ":");
} else if (options.contains(k)) {
- parts.add(k + ": is at the column of - " + eip + ": as an
option of " + eip + " it is indented under "
- + eip + ":, two spaces more, next to its other
options");
+ parts.add(k + ": lines up with " + eip + ":, so it is read as
a second key of the - item; it is an"
+ + " option of " + eip + ": write the options of " +
eip + ": on the lines under it, two"
+ + " spaces further right than " + eip + ":, with " +
k + ": among them");
} else {
// neither: say what a step is, without guessing where the key
belongs
return generic;
@@ -209,6 +219,57 @@ final class SchemaHints {
return parts.isEmpty() ? generic : String.join("; ", parts);
}
+ /**
+ * The options of the step's EIP that are written as second keys of the
step (at the column of the EIP), when the
+ * step has an EIP; empty otherwise.
+ */
+ static Set<String> misplacedOptions(JsonNode node, YamlValidator
validator) {
+ if (node == null || !node.isObject() || node.size() < 2) {
+ return Set.of();
+ }
+ Set<String> steps = validator.stepNames();
+ List<String> keys = new ArrayList<>();
+ node.fieldNames().forEachRemaining(keys::add);
+ String eip =
keys.stream().filter(steps::contains).findFirst().orElse(null);
+ if (eip == null) {
+ return Set.of();
+ }
+ Set<String> options = validator.optionsOf(eip);
+ Set<String> answer = new LinkedHashSet<>();
+ for (String k : keys) {
+ if (!k.equals(eip) && !steps.contains(k) && options.contains(k)) {
+ answer.add(k);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * An option of a step's EIP at the column of the EIP fails twice: "at
most 1 properties" (the hint above says where
+ * it goes) and "property 'steps' is not defined" in the oneOf branch of
the step, whose hint says to move the items
+ * up a level, the opposite. The second one is dropped.
+ */
+ static List<Error> dropMisplacedOptionRepeats(List<Error> errors,
YamlValidator validator) {
+ Set<String> repeats = new HashSet<>();
+ for (Error e : errors) {
+ if ("maxProperties".equals(e.getKeyword())) {
+ String at = String.valueOf(e.getInstanceLocation());
+ if (at.matches(".*/steps/\\d+")) {
+ for (String k : misplacedOptions(e.getInstanceNode(),
validator)) {
+ repeats.add(at + " " + k);
+ }
+ }
+ }
+ }
+ if (repeats.isEmpty()) {
+ return errors;
+ }
+ List<Error> answer = new ArrayList<>(errors);
+ answer.removeIf(e -> "additionalProperties".equals(e.getKeyword()) &&
e.getMessage() != null
+ && repeats.contains(e.getInstanceLocation() + " " +
between(e.getMessage(), "property '", "'")));
+ return answer;
+ }
+
//
-------------------------------------------------------------------------------------------------------------
// expression hints
//
-------------------------------------------------------------------------------------------------------------
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 c1f60be2d4ec..5db8ff7df451 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
@@ -724,6 +724,8 @@ public class YamlValidator {
errors = SchemaHints.apply(SchemaHints.EXPRESSION, errors, this);
errors = SchemaHints.apply(SchemaHints.PROPERTY, errors, this);
errors = SchemaHints.apply(SchemaHints.LIST, errors, this);
+ // before the step hints, which rewrite the error without its instance
node
+ errors = SchemaHints.dropMisplacedOptionRepeats(errors, this);
errors = SchemaHints.apply(SchemaHints.STEP, errors, this);
// CAMEL-24707: the schema requires the expression, so a node without
one fails its oneOf with "0 are valid"
// plus one "required property <language> not found" per language;
replace that with one line that says
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
index c58171d0700a..bc44197dcede 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
@@ -72,7 +72,32 @@ public class StructureHintTest {
allowNamedParameters: true
""";
assertThat(lines(yaml)).anySatisfy(m -> assertThat(m)
- .contains("parameters: is at the column of - to: as an option
of to it is indented under to:"));
+ .contains("parameters: lines up with to:, so it is read as a
second key of the - item")
+ .contains("indent parameters: and the lines under it two
spaces more, so parameters: lines up with"
+ + " uri:"));
+ }
+
+ @Test
+ public void stepsAtTheColumnOfSplit() {
+ // as qwen3.6 wrote it ten times in a row: the hint named "the column
of - split:", and two spaces more than
+ // the dash is where steps: already was; the second error said to move
the items up a level
+ String yaml = """
+ - route:
+ from:
+ uri: timer:orders
+ steps:
+ - split:
+ expression:
+ simple: "${body[lines]}"
+ steps:
+ - log:
+ message: "pick ${body[qty]} x ${body[sku]}"
+ """;
+ assertThat(lines(yaml)).singleElement().satisfies(m -> assertThat(m)
+ .startsWith("Line 5: ")
+ .contains("steps: lines up with split:")
+ .contains("so steps: lines up with expression:")
+ .doesNotContain("move the items up"));
}
@Test