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 e008baa436e5 CAMEL-25336: the YAML validator reports ${...} in a 
constant, which is used as written (#27369)
e008baa436e5 is described below

commit e008baa436e58b26fe4d780b3c20bf0bf134bf98
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 5 11:34:22 2026 +0200

    CAMEL-25336: the YAML validator reports ${...} in a constant, which is used 
as written (#27369)
    
    * CAMEL-25336: the YAML validator reports ${...} in a constant, which is 
used as written
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25336: the message starts with the expression, as the location 
already names the constant
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/dsl/yaml/validator/YamlValidator.java    |  49 ++++++++-
 .../dsl/yaml/validator/ConstantWithSimpleTest.java | 113 +++++++++++++++++++++
 2 files changed, 160 insertions(+), 2 deletions(-)

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 e42290470f92..f63f7e5b78cf 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
@@ -785,18 +785,30 @@ public class YamlValidator {
      * call to a method named $ and jsonpath as an invalid path. Says which 
language it is and how to write it there.
      */
     void checkSimpleSyntaxInScripts(JsonNode node, NodePath path, List<Error> 
errors) {
+        checkSimpleSyntaxInScripts(node, path, errors, false);
+    }
+
+    /**
+     * @param template inside a step that sets a template, such as setHeader 
CamelVelocityTemplate: its constant is
+     *                 template text with the template engine's own ${...}, 
not simple
+     */
+    private void checkSimpleSyntaxInScripts(JsonNode node, NodePath path, 
List<Error> errors, boolean template) {
         if (node == null) {
             return;
         }
         if (node.isArray()) {
             for (int i = 0; i < node.size(); i++) {
-                checkSimpleSyntaxInScripts(node.get(i), path.append(i), 
errors);
+                checkSimpleSyntaxInScripts(node.get(i), path.append(i), 
errors, template);
             }
             return;
         }
         if (!node.isObject()) {
             return;
         }
+        if (node.has("name") && node.get("name").isTextual()
+                && 
node.get("name").asText().toLowerCase(Locale.ROOT).contains("template")) {
+            template = true;
+        }
         var fields = node.fieldNames();
         while (fields.hasNext()) {
             String name = fields.next();
@@ -834,7 +846,40 @@ public class YamlValidator {
                                    + "\"}")
                         .build());
             }
-            checkSimpleSyntaxInScripts(value, path.append(name), errors);
+            if ("constant".equals(name) && !template) {
+                checkConstantWithSimple(value, path.append(name), errors);
+            }
+            checkSimpleSyntaxInScripts(value, path.append(name), errors, 
template);
+        }
+    }
+
+    /** A simple function in a constant: ${header.sku}, ${body}, 
${exchangeProperty.x}, ${date:now:...}. */
+    private static final Pattern SIMPLE_FUNCTION = 
Pattern.compile("\\$\\{[a-z][^}]*}");
+
+    /**
+     * A constant is used as written, so ${header.sku} in it stays the literal 
text ${header.sku}. Only simple evaluates
+     * it (the error message in an otherwise right route reads "unknown sku 
${header.sku}").
+     */
+    private static void checkConstantWithSimple(JsonNode value, NodePath path, 
List<Error> errors) {
+        String text = null;
+        if (value.isTextual()) {
+            text = value.asText();
+        } else if (value.isObject() && value.has("expression") && 
value.get("expression").isTextual()) {
+            text = value.get("expression").asText();
+        }
+        if (text == null || text.startsWith("resource:")) {
+            return;
+        }
+        Matcher m = SIMPLE_FUNCTION.matcher(text);
+        if (m.find()) {
+            errors.add(Error.builder()
+                    .keyword("type")
+                    .instanceLocation(path)
+                    .messageKey("type")
+                    .format(new MessageFormat("{0}"))
+                    .arguments(m.group() + " is not evaluated, a constant is 
used as written: use simple:"
+                               + " {expression: \"" + text.replace("\"", "'") 
+ "\"} for a value with expressions")
+                    .build());
         }
     }
 
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/ConstantWithSimpleTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/ConstantWithSimpleTest.java
new file mode 100644
index 000000000000..bec32eec7133
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/ConstantWithSimpleTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.yaml.validator;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A constant is used as written: ${header.sku} in it is not evaluated, and 
the route answers the literal text.
+ */
+public class ConstantWithSimpleTest {
+
+    private static YamlValidator validator;
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        validator = new YamlValidator();
+        validator.init();
+    }
+
+    @Test
+    public void testSimpleInAConstantIsReported() {
+        // what the benchmark wrote for the 404 answer of an unknown sku
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:getStock
+                      steps:
+                        - setHeader:
+                            name: CamelHttpResponseCode
+                            constant: 404
+                        - setBody:
+                            expression:
+                              constant: '{"error": "unknown sku 
${header.sku}"}'
+                """;
+        assertThat(lines(yaml)).singleElement().satisfies(m -> {
+            assertThat(m).startsWith("Line 10: ");
+            assertThat(m).contains("/constant: ${header.sku} is not evaluated, 
a constant is used as written");
+            assertThat(m).contains("use simple: {expression: \"{'error': 
'unknown sku ${header.sku}'}\"}");
+        });
+    }
+
+    @Test
+    public void testTheShortFormIsReported() {
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - setHeader:
+                            name: greeting
+                            constant: "Hello ${body}"
+                """;
+        assertThat(lines(yaml)).singleElement()
+                .satisfies(m -> assertThat(m).contains("/constant: ${body} is 
not evaluated"));
+    }
+
+    @Test
+    public void testPlainConstantsAreFine() {
+        // no simple function: text, a property placeholder, an environment 
style ${HOME}, a resource; and a template
+        // header, whose ${...} the template engine evaluates
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - setHeader:
+                            name: a
+                            constant: "unknown sku"
+                        - setHeader:
+                            name: b
+                            constant: "{{app.name}}"
+                        - setHeader:
+                            name: c
+                            constant: "${HOME}/data"
+                        - setBody:
+                            constant: resource:file:stock.json
+                        - setBody:
+                            simple: "unknown sku ${header.sku}"
+                        - setHeader:
+                            name: CamelVelocityTemplate
+                            expression:
+                              constant: "Hi this is a velocity template that 
can do templating ${body}"
+                """;
+        assertThat(lines(yaml)).isEmpty();
+    }
+
+    private static List<String> lines(String yaml) {
+        try {
+            return YamlValidator.describeAll(yaml, validator.validate(yaml));
+        } catch (Exception e) {
+            throw new AssertionError("Failed to validate:\n" + yaml, e);
+        }
+    }
+}

Reply via email to