This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25529 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 39cecb04201d8bb1a7cd04f3a21726774f5fdbae Author: Claus Ibsen <[email protected]> AuthorDate: Sat Oct 10 14:05:27 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 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. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../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
