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 861026f8faa2 CAMEL-24702: camel-yaml-dsl - YAML load errors say what 
to write: unknown expression or field with the nearest name, beans class not 
found (#26361)
861026f8faa2 is described below

commit 861026f8faa21946933aca88b3f5033d3e97e424
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 13 15:49:46 2026 +0200

    CAMEL-24702: camel-yaml-dsl - YAML load errors say what to write: unknown 
expression or field with the nearest name, beans class not found (#26361)
    
    * CAMEL-24702: camel-yaml-dsl - YAML load errors say what to write: unknown 
expression or field with the nearest name, beans class not found, the file's 
shape
    
    Squash of the 5 commits on fix/CAMEL-24702 (the run-by-run history is on 
bench/after-run).
    
    camel-yaml-dsl - say that an expression is expected when a plain value is 
used for handled, continued and the other expression options
    camel-yaml-dsl - do not wrap the expression message in Error constructing 
YAML node id
    camel-yaml-dsl - 'Unknown expression with id: bean' says the bean language 
is written as method:
    camel-yaml-dsl - an unsupported field names the node and the field, and 
bean as a language says method:
    camel-yaml-dsl - 'Error creating bean' names the class that was not found 
and the built-in strategy package
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * CAMEL-24702: camel-yaml-dsl - the class-not-found hint names the class in 
dotted form also for NoClassDefFoundError
    
    Review note on PR 26361: NoClassDefFoundError.getMessage() uses the 
internal java/lang/Foo form.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    ---------
    
    Co-authored-by: Claude Fable 5.1 <[email protected]>
---
 .../yaml/common/YamlDeserializationContext.java    |  17 ++-
 .../deserializers/ExpressionDeserializers.java     |   8 +-
 .../dsl/yaml/deserializers/BeansDeserializer.java  |  28 ++++-
 .../dsl/yaml/GenerateYamlDeserializersMojo.java    |  15 ++-
 .../camel/dsl/yaml/validator/YamlValidator.java    |  53 +++++++-
 .../validator/YamlValidatorExpressionHintTest.java | 139 +++++++++++++++++++++
 .../apache/camel/dsl/yaml/OnExceptionTest.groovy   |  40 ++++++
 7 files changed, 294 insertions(+), 6 deletions(-)

diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
index cb95ebf8e3b7..5cbabd0a741b 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
@@ -28,7 +28,9 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
 import org.apache.camel.Service;
 import org.apache.camel.dsl.yaml.common.exception.DuplicateKeyException;
+import org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException;
 import org.apache.camel.dsl.yaml.common.exception.UnknownNodeIdException;
+import org.apache.camel.dsl.yaml.common.exception.UnsupportedFieldException;
 import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.support.OrderedComparator;
@@ -404,8 +406,21 @@ public class YamlDeserializationContext extends 
StandardConstructor implements C
             return constructor.construct(node);
         } catch (DuplicateKeyException | UnknownNodeIdException e) {
             throw e;
-        } catch (YamlDeserializationException e) {
+        } catch (YamlDeserializationException | InvalidExpressionException e) {
+            // already says what is wrong and where
             throw e;
+        } catch (UnsupportedFieldException e) {
+            // name the node and the field, and for a field that is a mistaken 
language name say what to write
+            String field = e.getProblem() != null && 
e.getProblem().startsWith("Unsupported field: ")
+                    ? e.getProblem().substring("Unsupported field: ".length()) 
: null;
+            String hint = "";
+            if ("bean".equals(field)) {
+                hint = " (the bean language is written as method: {ref: 
myBean, method: process})";
+            } else if ("expression".equals(field) || "language".equals(field)) 
{
+                hint = " (an expression is written with the language as the 
key: simple: \"...\", constant: \"...\")";
+            }
+            throw new YamlDeserializationException(
+                    node, "Error constructing YAML node id: " + id + ": 
unsupported field: " + field + hint, e);
         } catch (RuntimeException e) {
             throw new YamlDeserializationException(node, "Error constructing 
YAML node id: " + id, e);
         }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
index 394bc6839bbb..9933c583d163 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
@@ -8,6 +8,7 @@ import java.lang.SuppressWarnings;
 import javax.annotation.processing.Generated;
 import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
 import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException;
 import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.spi.annotations.YamlProperty;
 import org.apache.camel.spi.annotations.YamlType;
@@ -15,6 +16,7 @@ import org.snakeyaml.engine.v2.api.ConstructNode;
 import org.snakeyaml.engine.v2.nodes.MappingNode;
 import org.snakeyaml.engine.v2.nodes.Node;
 import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.ScalarNode;
 
 /**
  * The model automatically scan all classes, also those one deprecated. They 
will be dropped when removed from core model.
@@ -37,7 +39,7 @@ public final class ExpressionDeserializers extends 
YamlDeserializerSupport {
         Node val = setDeserializationContext(nt.getValueNode(), dc);
         ExpressionDefinition answer = constructExpressionType(key, val);
         if (answer == null) {
-            throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
"Unknown expression with id: " + key);
+            throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
"Unknown expression with id: " + key + ("bean".equals(key) ? " (the bean 
language is written as method: {ref: myBean, method: process})" : ""));
         }
         return answer;
     }
@@ -219,6 +221,10 @@ public final class ExpressionDeserializers extends 
YamlDeserializerSupport {
     public static class ExpressionSubElementDefinitionDeserializers implements 
ConstructNode {
         @Override
         public Object construct(Node node) {
+            if (!(node instanceof MappingNode)) {
+                String text = node instanceof ScalarNode ? asText(node) : 
node.getNodeType().name().toLowerCase();
+                throw new InvalidExpressionException(node, "an expression is 
expected here, not a plain value (" + text + "): write constant: \"" + text + 
"\" for a fixed value, or simple: \"...\" for a dynamic one");
+            }
             ExpressionDefinition val = constructExpressionType(node);
             return new 
org.apache.camel.model.ExpressionSubElementDefinition(val);
         }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
index a5a53eded9df..cc616d59e384 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
@@ -125,7 +125,7 @@ public class BeansDeserializer extends 
YamlDeserializerSupport implements Constr
             } else {
                 String msg
                         = name != null ? "Error creating bean: " + name + " of 
type: " + type : "Error creating bean: " + type;
-                throw new RuntimeException(msg, e);
+                throw new RuntimeException(msg + classNotFoundHint(e), e);
             }
         }
     }
@@ -190,4 +190,30 @@ public class BeansDeserializer extends 
YamlDeserializerSupport implements Constr
         model.addCustomBean(def);
     }
 
+    /**
+     * The cause of a bean that could not be created is a 
ClassNotFoundException more often than not (a wrong package, a
+     * missing dependency); say so, and for a Camel aggregation strategy 
written in the wrong package name the right
+     * one.
+     */
+    static String classNotFoundHint(Throwable e) {
+        for (Throwable t = e; t != null; t = t.getCause()) {
+            if (t instanceof ClassNotFoundException || t instanceof 
NoClassDefFoundError) {
+                // NoClassDefFoundError names the class in internal form 
(java/lang/Foo)
+                String cls = t.getMessage() != null ? 
t.getMessage().trim().replace('/', '.') : "";
+                String simple = cls.substring(cls.lastIndexOf('.') + 1);
+                String hint = ": class " + cls + " was not found";
+                if (simple.endsWith("AggregationStrategy") && 
!cls.startsWith("org.apache.camel.processor.aggregate.")) {
+                    String candidate = "org.apache.camel.processor.aggregate." 
+ simple;
+                    try {
+                        Class.forName(candidate, false, 
BeansDeserializer.class.getClassLoader());
+                        return hint + " (did you mean " + candidate + "?)";
+                    } catch (Throwable ignore) {
+                        // not one of the built-in strategies
+                    }
+                }
+                return hint + " (check the package name; a class from another 
library needs its dependency added)";
+            }
+        }
+        return "";
+    }
 }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
index d0d60ae00fa9..632d5f8565f8 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
@@ -184,7 +184,7 @@ public class GenerateYamlDeserializersMojo extends 
GenerateYamlSupportMojo {
                                 .addStatement("ExpressionDefinition answer = 
constructExpressionType(key, val)")
                                 .beginControlFlow("if (answer == null)")
                                 .addStatement(
-                                        "throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
\"Unknown expression with id: \" + key)")
+                                        "throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
\"Unknown expression with id: \" + key + (\"bean\".equals(key) ? \" (the bean 
language is written as method: {ref: myBean, method: process})\" : \"\"))")
                                 .endControlFlow()
                                 .addStatement("return answer")
                                 .build())
@@ -295,6 +295,19 @@ public class GenerateYamlDeserializersMojo extends 
GenerateYamlSupportMojo {
                                         .addAnnotation(Override.class)
                                         .addParameter(Node.class, "node")
                                         .returns(Object.class)
+                                        // CAMEL-24702: a plain value 
(handled: true) must say what is expected instead
+                                        .beginControlFlow("if (!(node 
instanceof $T))",
+                                                
ClassName.get("org.snakeyaml.engine.v2.nodes", "MappingNode"))
+                                        .addStatement(
+                                                "String text = node instanceof 
$T ? asText(node) : node.getNodeType().name().toLowerCase()",
+                                                
ClassName.get("org.snakeyaml.engine.v2.nodes", "ScalarNode"))
+                                        .addStatement("throw new $T(node, $S + 
text + $S + text + $S)",
+                                                
ClassName.get("org.apache.camel.dsl.yaml.common.exception",
+                                                        
"InvalidExpressionException"),
+                                                "an expression is expected 
here, not a plain value (",
+                                                "): write constant: \"",
+                                                "\" for a fixed value, or 
simple: \"...\" for a dynamic one")
+                                        .endControlFlow()
                                         .addStatement("$T val = 
constructExpressionType(node)", CN_EXPRESSION_DEFINITION)
                                         .addStatement("return new 
org.apache.camel.model.ExpressionSubElementDefinition(val)")
                                         .build())
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 882b2b9b022c..d5be9f63144d 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
@@ -118,6 +118,7 @@ public class YamlValidator {
     private List<Error> validate(JsonNode target) {
         var errors = filterOneOfNoise(new 
ArrayList<>(schema.validate(target)));
         errors.removeIf(YamlValidator::isRuntimeAcceptedScalar);
+        errors = withExpressionHints(errors);
         if (canonical) {
             checkOneOfCardinality(target, new NodePath(PathType.JSON_POINTER), 
errors);
         }
@@ -351,8 +352,8 @@ public class YamlValidator {
      * <li>a quoted scalar that parses as the expected type (e.g. {@code 
parallelProcessing: "true"}) - the runtime
      * converts the text.</li>
      * </ul>
-     * Everything else stays strict: unknown properties, structure (a map 
where a list is expected), enums, and
-     * strings that do not parse as the expected type.
+     * Everything else stays strict: unknown properties, structure (a map 
where a list is expected), enums, and strings
+     * that do not parse as the expected type.
      * <p>
      * This assumes the runtime defers the conversion for every scalar 
attribute the schema exposes. The few model
      * attributes that are still converted while deserializing (so a 
placeholder is never resolved for them) are not
@@ -383,6 +384,54 @@ public class YamlValidator {
         return (instance.isNumber() || instance.isBoolean()) && 
isExpectedType(error, "string");
     }
 
+    /**
+     * Replaces the schema's "boolean found, object expected" for a plain 
value at an option that takes an expression
+     * (such as {@code handled: true} on onException, or {@code 
completionSizeExpression: 10} on aggregate) with a
+     * message that shows the expression form, and drops the duplicates the 
schema composition produces for it.
+     */
+    static List<Error> withExpressionHints(List<Error> errors) {
+        List<Error> answer = new ArrayList<>(errors.size());
+        Set<String> seen = new LinkedHashSet<>();
+        for (Error error : errors) {
+            Error hinted = withExpressionHint(error);
+            if (hinted == error || seen.add(hinted.getInstanceLocation() + " " 
+ hinted.getMessage())) {
+                answer.add(hinted);
+            }
+        }
+        return answer;
+    }
+
+    static Error withExpressionHint(Error error) {
+        if (!"type".equals(error.getKeyword())) {
+            return error;
+        }
+        JsonNode instance = error.getInstanceNode();
+        if (instance == null || !instance.isValueNode()) {
+            return error;
+        }
+        String schemaLocation = String.valueOf(error.getSchemaLocation());
+        String evaluationPath = String.valueOf(error.getEvaluationPath());
+        if (!schemaLocation.contains(EXPRESSION_SUB_ELEMENT) && 
!evaluationPath.contains(EXPRESSION_SUB_ELEMENT)) {
+            return error;
+        }
+        String location = String.valueOf(error.getInstanceLocation());
+        String name = location.substring(location.lastIndexOf('/') + 1);
+        String value = instance.asText();
+        String message = String.format(
+                "a plain value (%s) found, an expression expected: write %s: 
{constant: \"%s\"} for a fixed value, or %s: {simple: \"...\"} for a dynamic 
one",
+                value, name, value, name);
+        // the message is not a MessageFormat pattern (it contains braces), so 
pass it as the single argument
+        return Error.builder()
+                .keyword("type")
+                .instanceLocation(error.getInstanceLocation())
+                .messageKey("expression")
+                .format(new MessageFormat("{0}"))
+                .arguments(message)
+                .build();
+    }
+
+    private static final String EXPRESSION_SUB_ELEMENT = 
"ExpressionSubElementDefinition";
+
     private static boolean isBooleanText(String text) {
         String s = text.trim();
         return "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s);
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
new file mode 100644
index 000000000000..1fe58e691fd5
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorExpressionHintTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.networknt.schema.Error;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-24702: options that take an expression (handled, continued, 
retryWhile, the aggregate and throttle correlation
+ * expressions) are commonly written as a plain value. The DSL does not accept 
that, so the validator must say what to
+ * write instead of "boolean found, object expected".
+ */
+public class YamlValidatorExpressionHintTest {
+
+    private static YamlValidator classic;
+    private static YamlValidator canonical;
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        classic = new YamlValidator();
+        classic.init();
+        canonical = new YamlValidator(true);
+        canonical.init();
+    }
+
+    @Test
+    public void testHandledAsPlainValue() {
+        String yaml = """
+                - onException:
+                    exception:
+                      - java.lang.Exception
+                    handled: true
+                    steps:
+                      - log:
+                          message: "Error: ${exception.message}"
+                """;
+        assertHint(yaml, "handled: {constant: \"true\"}", "handled: {simple: 
\"...\"}");
+    }
+
+    @Test
+    public void testContinuedAsPlainValue() {
+        String yaml = """
+                - onException:
+                    exception:
+                      - java.lang.Exception
+                    continued: "true"
+                    steps:
+                      - log:
+                          message: "Error: ${exception.message}"
+                """;
+        assertHint(yaml, "continued: {constant: \"true\"}");
+    }
+
+    @Test
+    public void testAggregateCompletionSizeAsPlainValue() {
+        String yaml = """
+                - from:
+                    uri: timer:tick
+                    steps:
+                      - aggregate:
+                          correlationExpression:
+                            constant:
+                              expression: "a"
+                          completionSizeExpression: 10
+                          aggregationStrategy: myStrategy
+                          steps:
+                            - log:
+                                message: "${body}"
+                """;
+        assertHint(yaml, "completionSizeExpression: {constant: \"10\"}");
+    }
+
+    @Test
+    public void testExpressionFormIsAccepted() {
+        // explicit form, so that it is valid in canonical mode too
+        String yaml = """
+                - onException:
+                    exception:
+                      - java.lang.Exception
+                    handled:
+                      constant:
+                        expression: "true"
+                    continued:
+                      simple:
+                        expression: "${header.retry} == null"
+                    steps:
+                      - log:
+                          message: "Error: ${exception.message}"
+                """;
+        for (YamlValidator validator : List.of(classic, canonical)) {
+            assertThat(validate(validator, yaml)).isEmpty();
+        }
+    }
+
+    private void assertHint(String yaml, String... expectedInMessage) {
+        for (YamlValidator validator : List.of(classic, canonical)) {
+            String mode = validator.isCanonical() ? "canonical" : "classic";
+            List<Error> errors = validate(validator, yaml);
+            assertThat(errors).as("%s mode must report the plain value:\n%s", 
mode, yaml).isNotEmpty();
+            for (String expected : expectedInMessage) {
+                assertThat(errors)
+                        .as("%s mode must show the expression form '%s'", 
mode, expected)
+                        .anyMatch(e -> e.getMessage().contains(expected));
+            }
+            // one hint, not the two or three type errors the schema 
composition produces
+            assertThat(errors.stream().filter(e -> e.getMessage().contains("an 
expression expected")).count())
+                    .as("%s mode must report the hint once", mode)
+                    .isEqualTo(1);
+            assertThat(errors).noneMatch(e -> e.getMessage().contains("object 
expected"));
+        }
+    }
+
+    private List<Error> validate(YamlValidator validator, String yaml) {
+        try {
+            return validator.validate(yaml);
+        } catch (Exception e) {
+            throw new AssertionError("Failed to validate:\n" + yaml, e);
+        }
+    }
+}
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
index d37d2b9e1749..f880731fc838 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/OnExceptionTest.groovy
@@ -20,6 +20,7 @@ import org.apache.camel.dsl.yaml.support.YamlTestSupport
 import org.apache.camel.component.mock.MockEndpoint
 import org.apache.camel.dsl.yaml.support.model.MyException
 import org.apache.camel.dsl.yaml.support.model.MyFailingProcessor
+import org.apache.camel.support.ResourceHelper
 import org.junit.jupiter.api.Assertions
 
 class OnExceptionTest extends YamlTestSupport {
@@ -59,4 +60,43 @@ class OnExceptionTest extends YamlTestSupport {
             MockEndpoint.assertIsSatisfied(context)
     }
 
+    // CAMEL-24702: handled is an expression; a plain value must fail with a 
message that says what to write
+    def "onException handled as plain value fails with a helpful message"() {
+        when:
+            loadRoutes([ResourceHelper.fromString("route-0.yaml", '''
+                - onException:
+                    handled: true
+                    exception:
+                      - java.lang.Exception
+                    steps:
+                      - to: "mock:on-exception"
+            '''.stripIndent())], false)
+        then:
+            def e = thrown(Exception)
+            def messages = []
+            for (Throwable t = e; t != null; t = t.cause) {
+                messages << t.message
+            }
+            messages.any { it != null && it.contains('an expression is 
expected here, not a plain value (true)') && it.contains('constant: "true"') }
+    }
+
+
+    // CAMEL-24702: an unsupported field names the node and the field; bean as 
a language says method:
+    def "setBody with bean as a field fails with a message naming the field"() 
{
+        when:
+            loadRoutes([ResourceHelper.fromString("route-1.yaml", '''
+                - from:
+                    uri: timer:tick
+                    steps:
+                      - setBody:
+                          bean: myBean
+            '''.stripIndent())], false)
+        then:
+            def e = thrown(Exception)
+            def messages = []
+            for (Throwable t = e; t != null; t = t.cause) {
+                messages << t.message
+            }
+            messages.any { it != null && it.contains('Error constructing YAML 
node id: setBody: unsupported field: bean') && it.contains('method: {ref: 
myBean, method: process}') }
+    }
 }

Reply via email to