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 100afa698552 CAMEL-24856: the properties validator reports a nested 
segment under an option group, which fails at startup
100afa698552 is described below

commit 100afa698552572d6a36a1e86999d54a8765a345
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 09:44:02 2026 +0200

    CAMEL-24856: the properties validator reports a nested segment under an 
option group, which fails at startup
    
    
camel.resilience4j.circuitbreaker.supplierCircuitBreaker.slidingWindowSize=4,
    an invented per-id form, passed the properties validation and the run died 
at
    startup with "Cannot find getter method: supplierCircuitBreaker on bean:
    class java.lang.String". The properties checks report a key with a nested
    segment under one of the main model's option groups (resilience4j,
    faulttolerance, threadpool, health...) with the global form and, for
    resilience4j, the per circuit breaker form in the route. The prefixes whose
    keys nest by design (camel.component, camel.beans, camel.variable,
    camel.kamelet, camel.main, bracketed keys) are left to the checks that own
    them.
    
    Closes #26639
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../jbang/core/commands/ai/PropertiesChecks.java   | 57 ++++++++++++++++++++++
 .../commands/ai/SourceValidatorPropertiesTest.java | 19 ++++++++
 2 files changed, 76 insertions(+)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/PropertiesChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/PropertiesChecks.java
index 4ddc2265e610..52bf04e7f05f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/PropertiesChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/PropertiesChecks.java
@@ -21,12 +21,14 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.ConfigurationPropertiesValidationResult;
+import org.apache.camel.tooling.model.MainModel;
 
 /**
  * The application.properties checks of {@link SourceValidator}: unknown 
camel.* options with the option meant, an
@@ -46,6 +48,56 @@ final class PropertiesChecks {
         return validatePropertiesLines(content, line -> 
validatePropertyLine(line, catalog, extraPropertyLine));
     }
 
+    /** camel.<group>.<rest>=: the option groups of the main model 
(resilience4j, faulttolerance, threadpool...). */
+    static final Pattern GROUP_KEY_PATTERN = 
Pattern.compile("^\\s*camel\\.([a-zA-Z0-9-]+)\\.([^=\\s]+)\\s*=");
+
+    /** The camel.* prefixes whose keys legitimately nest, or that other 
checks own. */
+    private static final Set<String> NESTING_GROUPS = Set.of("component", 
"dataformat", "language", "beans", "variable",
+            "kamelet", "jbang", "route-template", "routeTemplate", "main", 
"rest", "server", "management");
+
+    /**
+     * 
camel.resilience4j.circuitbreaker.supplierCircuitBreaker.slidingWindowSize=4, 
an invented per-id form: the
+     * catalog accepts it and the run dies at startup ("Cannot find getter 
method: supplierCircuitBreaker on bean: class
+     * java.lang.String"). The options of a group are global, one segment 
after the group; resilience4j is also set per
+     * circuit breaker in the route (CAMEL-24856).
+     */
+    static String nestedGroupKeyHint(String line, CamelCatalog catalog) {
+        Matcher m = GROUP_KEY_PATTERN.matcher(line);
+        if (!m.find()) {
+            return null;
+        }
+        String group = m.group(1);
+        String rest = m.group(2);
+        if (!rest.contains(".") || rest.contains("[") || 
NESTING_GROUPS.contains(group)) {
+            return null;
+        }
+        MainModel mm = catalog.mainModel();
+        if (mm == null) {
+            return null; // a catalog without the main model: the key goes to 
the catalog as is
+        }
+        String prefix = "camel." + group + ".";
+        List<String> options = new ArrayList<>();
+        for (var o : mm.getOptions()) {
+            if (o.getName().startsWith(prefix) && 
!o.getName().substring(prefix.length()).contains(".")) {
+                options.add(o.getName().substring(prefix.length()));
+            }
+        }
+        if (options.isEmpty()) {
+            return null; // not a known group: the catalog reports the key
+        }
+        String first = rest.substring(0, rest.indexOf('.'));
+        String last = rest.substring(rest.lastIndexOf('.') + 1);
+        String option = options.contains(last) ? last : closestName(last, 
options);
+        String example = "camel." + group + "." + (option != null ? option : 
options.get(0)) + "=...";
+        String more = "resilience4j".equals(group)
+                ? ", or per circuit breaker in the route: circuitBreaker: 
{resilience4jConfiguration: {"
+                  + (option != null ? option : "...") + ": ...}}"
+                : "; the options are " + String.join(", ", options.size() > 8 
? options.subList(0, 8) : options)
+                  + (options.size() > 8 ? ", ..." : "");
+        return first + "    Unknown option (camel." + group + " has no nested 
settings such as " + first
+               + ": its options are global, " + example + more + ")";
+    }
+
     /** Validates one properties line: a {@code camel.*} key against the 
catalog, any other with the extra check. */
     static final Pattern COMPONENT_KEY_PATTERN
             = 
Pattern.compile("^\\s*camel\\.(component|dataformat|language)\\.([A-Za-z0-9-]+)\\.");
@@ -76,6 +128,11 @@ final class PropertiesChecks {
                 return name + "    Unknown " + kind + (closest != null ? " 
(did you mean " + closest + "?)" : "");
             }
         }
+        // camel.resilience4j.circuitbreaker.<id>.<option>: a nested segment 
under a known option group (CAMEL-24856)
+        String nested = nestedGroupKeyHint(line, catalog);
+        if (nested != null) {
+            return nested;
+        }
         try {
             ConfigurationPropertiesValidationResult result = 
catalog.validateConfigurationProperty(line);
             if (result.isAccepted()) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPropertiesTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPropertiesTest.java
index a9bff80561c0..b894b3ce1eb9 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPropertiesTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPropertiesTest.java
@@ -44,6 +44,25 @@ public class SourceValidatorPropertiesTest {
         assertThat(msgs.get(1)).startsWith("Line 3: jacksn    Unknown 
dataformat").contains("did you mean jackson");
     }
 
+    /**
+     * CAMEL-24856: a nested segment under an option group is not a property; 
the options are global or in the route.
+     */
+    @Test
+    void aNestedKeyUnderAnOptionGroupIsReported() {
+        List<String> msgs = SourceValidator.validateProperties("""
+                
camel.resilience4j.circuitbreaker.supplierCircuitBreaker.slidingWindowSize=4
+                camel.resilience4j.slidingWindowSize=4
+                camel.faulttolerance.bulkhead.myPool.enabled=true
+                """, catalog, null);
+        assertThat(msgs).hasSize(2);
+        assertThat(msgs.get(0))
+                .startsWith("Line 1: circuitbreaker    Unknown option 
(camel.resilience4j has no nested settings")
+                .contains("camel.resilience4j.slidingWindowSize=...")
+                .contains("circuitBreaker: {resilience4jConfiguration: 
{slidingWindowSize: ...}}");
+        assertThat(msgs.get(1)).startsWith("Line 3: bulkhead    Unknown option 
(camel.faulttolerance has no nested settings")
+                .contains("the options are");
+    }
+
     @Test
     void wrongMainKeyGetsTheClosestOption() {
         List<String> msgs = SourceValidator.validateProperties("""

Reply via email to