This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch pr/CAMEL-24698-source-checks
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 54d8c93b1641d8627a4e673c70cc0e5620d66bf4
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 13 15:29:30 2026 +0200

    CAMEL-24692: camel-jbang - the Simple check skips only a placeholder used 
as an operand of a logical operator
    
    Every placeholder-only expression was skipped as a workaround for the 
catalog rejecting placeholders as
    operands (fixed upstream in CAMEL-24692). A placeholder used as a value is 
validated now; one used as an
    operand of && or || is not, since it can expand to a whole predicate the 
catalog cannot know. Moved here
    from PR 26350, whose file no longer holds the validation since CAMEL-24695.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../dsl/jbang/core/commands/ai/SimpleChecks.java   | 53 ++++++++++++++-
 .../ai/SourceValidatorPlaceholderGuardTest.java    | 78 ++++++++++++++++++++++
 2 files changed, 129 insertions(+), 2 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java
index 2ef42d5654c9..51832938609b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java
@@ -96,8 +96,8 @@ final class SimpleChecks {
             if (simpleText == null || simpleText.isEmpty()) {
                 continue;
             }
-            // Skip placeholder-only expressions
-            if (simpleText.startsWith("{{") && simpleText.endsWith("}}")) {
+            // Skip what the catalog cannot validate because a placeholder is 
unresolved
+            if (hasPlaceholderAsLogicalOperand(simpleText)) {
                 continue;
             }
 
@@ -161,4 +161,53 @@ final class SimpleChecks {
         return false;
     }
 
+    /**
+     * Whether the text uses a property placeholder as an operand of a logical 
operator, such as
+     * <tt>{{enabled}} && ${body} > 10</tt>.
+     *
+     * A placeholder can expand to an entire predicate, which the catalog 
cannot know as it validates without a running
+     * Camel application. The catalog substitutes a placeholder with a dummy 
value, which is what an operand of a binary
+     * operator needs, but a logical operator needs a predicate on either 
side. Validating those would report an error
+     * for a route that is perfectly valid at runtime, so they are skipped.
+     */
+    public static boolean hasPlaceholderAsLogicalOperand(String text) {
+        if (text == null || !text.contains("{{")) {
+            return false;
+        }
+
+        // split into the operands of the logical operators, ignoring any 
quoted literal
+        List<String> operands = new ArrayList<>();
+        char quote = 0;
+        int start = 0;
+        for (int i = 0; i < text.length(); i++) {
+            char ch = text.charAt(i);
+            if (quote == 0 && (ch == '\'' || ch == '"')) {
+                quote = ch;
+            } else if (quote == ch) {
+                quote = 0;
+            } else if (quote == 0 && i < text.length() - 1) {
+                char next = text.charAt(i + 1);
+                if (ch == '&' && next == '&' || ch == '|' && next == '|') {
+                    operands.add(text.substring(start, i));
+                    i++;
+                    start = i + 1;
+                }
+            }
+        }
+        if (operands.isEmpty()) {
+            // no logical operator so the placeholders are all used as a value 
which the catalog can validate
+            return false;
+        }
+        operands.add(text.substring(start));
+
+        for (String operand : operands) {
+            String s = operand.trim();
+            // is the operand nothing but a single placeholder
+            if (s.startsWith("{{") && s.endsWith("}}") && s.indexOf("}}") == 
s.length() - 2) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java
new file mode 100644
index 000000000000..8a4a854dc10d
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.jbang.core.commands.ai;
+
+import java.util.List;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-24692: a placeholder used as a value is validated (the catalog 
substitutes it), a placeholder used as an
+ * operand of a logical operator is not (it can expand to a whole predicate 
the catalog cannot know).
+ */
+class SourceValidatorPlaceholderGuardTest {
+
+    private static final CamelCatalog CATALOG = new DefaultCamelCatalog();
+
+    @Test
+    void placeholderAsValueIsValidated() {
+        
assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("{{hot.threshold}}"));
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} >= 
{{hot.threshold}}"));
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("{{a}} == 
{{b}}"));
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} >= 
{{t}} && ${body} < {{u}}"));
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} == 
'abc'"));
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand(null));
+    }
+
+    @Test
+    void placeholderAsLogicalOperandIsSkipped() {
+        assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("{{a}} && 
{{b}}"));
+        assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} > 1 && 
{{flag}}"));
+        assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("{{flag}} || 
${body} > 1"));
+        // inside a quoted literal it is text
+        assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} == 
'{{a}} && {{b}}'"));
+    }
+
+    @Test
+    void placeholderPredicatesAreNotReportedAsErrors() {
+        String yaml = """
+                - from:
+                    uri: timer:tick
+                    steps:
+                      - filter:
+                          simple: "${body} >= {{hot.threshold}}"
+                          steps:
+                            - log: hot
+                      - filter:
+                          simple: "{{flag}} && ${body} > 1"
+                          steps:
+                            - log: flagged
+                      - filter:
+                          simple: "${body"
+                          steps:
+                            - log: broken
+                """;
+        List<String> errors = SourceValidator.validateYamlSimple(yaml, 
CATALOG);
+        assertTrue(errors.stream().noneMatch(e -> e.contains("hot.threshold") 
|| e.contains("flag")), String.valueOf(errors));
+        assertTrue(errors.stream().anyMatch(e -> e.contains("Line 13")), 
String.valueOf(errors));
+    }
+}

Reply via email to