jamesnetherton commented on code in PR #9336:
URL: https://github.com/apache/camel-quarkus/pull/9336#discussion_r4291697705


##########
extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/devui/CamelCoreDevUIService.java:
##########
@@ -141,6 +151,125 @@ public boolean updateConsoleOptions(String id, 
Map<String, Object> options) {
         }) != null;
     }
 
+    /**
+     * Gets the Camel catalog documentation of a component available in the 
application.
+     *
+     * @param  name          The component scheme
+     * @param  optionsFilter Optional keyword to keep only the matching options
+     * @return               JSON String representation of the component 
catalog documentation
+     */
+    @JsonRpcDescription("Camel catalog documentation of a component: endpoint 
URI syntax and all options"
+            + " (name, type, default value, description), from the exact Camel 
version of this application."
+            + " Use it before writing a route with an unfamiliar component 
instead of guessing URI options.")
+    @DevMCPEnableByDefault
+    public String camelCatalogDoc(
+            @JsonRpcDescription("Component scheme, e.g. timer, kafka, sql") 
String name,
+            @JsonRpcDescription("Optional keyword to keep only the options 
whose name or description matches") String optionsFilter) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        String json = catalog.componentJSonSchema(name);
+        if (json == null) {
+            JsonObject error = new JsonObject();
+            error.put("error", "Component '" + name + "' is not on the 
application classpath. Add the camel-quarkus-"

Review Comment:
   Building the hint as `"camel-quarkus-" + scheme` isn't always right (same 
for the message in `camelValidateEndpointUri`). `https` gives 
`camel-quarkus-https`, which doesn't exist (it ships in camel-http), and the 
same goes for other multi-scheme components and components registered under a 
custom bean name. Agents will add the suggested dependency and the build will 
fail. One option is to look up the artifactId from the catalog's component 
model where it's known, or fall back to wording like "the extension providing 
the `<scheme>` component".



##########
extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/devui/CamelCoreDevUIService.java:
##########
@@ -141,6 +151,125 @@ public boolean updateConsoleOptions(String id, 
Map<String, Object> options) {
         }) != null;
     }
 
+    /**
+     * Gets the Camel catalog documentation of a component available in the 
application.
+     *
+     * @param  name          The component scheme
+     * @param  optionsFilter Optional keyword to keep only the matching options
+     * @return               JSON String representation of the component 
catalog documentation
+     */
+    @JsonRpcDescription("Camel catalog documentation of a component: endpoint 
URI syntax and all options"
+            + " (name, type, default value, description), from the exact Camel 
version of this application."
+            + " Use it before writing a route with an unfamiliar component 
instead of guessing URI options.")
+    @DevMCPEnableByDefault
+    public String camelCatalogDoc(
+            @JsonRpcDescription("Component scheme, e.g. timer, kafka, sql") 
String name,
+            @JsonRpcDescription("Optional keyword to keep only the options 
whose name or description matches") String optionsFilter) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        String json = catalog.componentJSonSchema(name);
+        if (json == null) {
+            JsonObject error = new JsonObject();
+            error.put("error", "Component '" + name + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + name + " dependency to the project.");
+            return error.toJson();
+        }
+        if (optionsFilter == null || optionsFilter.isBlank()) {
+            return json;
+        }
+        return filterComponentOptions(json, optionsFilter);
+    }
+
+    /**
+     * Validates a Camel endpoint URI against the catalog of the application.
+     *
+     * @param  uri The full endpoint URI
+     * @return     JSON String representation of the validation result
+     */
+    @JsonRpcDescription("Validates a Camel endpoint URI against the catalog of 
this application:"
+            + " unknown options (with suggestions), invalid enum or boolean 
values, missing required options."
+            + " Call it after writing or changing any endpoint URI.")
+    @DevMCPEnableByDefault
+    public String camelValidateEndpointUri(
+            @JsonRpcDescription("Full endpoint URI, e.g. 
kafka:orders?brokers=localhost:9092") String uri) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        JsonObject result = new JsonObject();
+        String scheme = uri != null ? StringHelper.before(uri, ":") : null;
+        if (scheme == null || scheme.isBlank()) {
+            result.put("valid", false);
+            result.put("errors", "Malformed endpoint URI, expected 
scheme:path[?options]");
+            return result.toJson();
+        }
+        if (catalog.componentJSonSchema(scheme) == null) {
+            result.put("valid", false);
+            result.put("errors", "Component '" + scheme + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + scheme + " dependency to the project.");
+            return result.toJson();
+        }
+        try {
+            EndpointValidationResult validation = 
catalog.validateProperties(scheme, catalog.endpointProperties(uri));

Review Comment:
   This reimplements `catalog.validateEndpointProperties(uri)` and differs from 
it in a few ways:
   - `validateProperties(scheme, map)` doesn't honour the component's 
`lenientProperties` flag, because it reads `lenient` via `Boolean.getBoolean`, 
which looks up a system property. So `http://localhost:8080/api?foo=bar` is 
reported invalid with `Unknown option: foo`. The skill tells agents to validate 
after every URI change, so they'd strip valid parameters.
   - The URI isn't normalized and the scheme is extracted with 
`StringHelper.before(uri, ":")`. A placeholder like `{{myEndpoint}}` is 
reported as malformed, where Camel would report it as `incapable`, and ` 
timer:tick` (leading space) gives the scheme ` timer`.
   - The component JSON is loaded once just to check it exists, then again for 
validation.
   
   Could this delegate to `validateEndpointProperties(uri)` and map its 
`EndpointValidationResult` into the response instead?



##########
extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/devui/CamelCoreDevUIService.java:
##########
@@ -141,6 +151,125 @@ public boolean updateConsoleOptions(String id, 
Map<String, Object> options) {
         }) != null;
     }
 
+    /**
+     * Gets the Camel catalog documentation of a component available in the 
application.
+     *
+     * @param  name          The component scheme
+     * @param  optionsFilter Optional keyword to keep only the matching options
+     * @return               JSON String representation of the component 
catalog documentation
+     */
+    @JsonRpcDescription("Camel catalog documentation of a component: endpoint 
URI syntax and all options"
+            + " (name, type, default value, description), from the exact Camel 
version of this application."
+            + " Use it before writing a route with an unfamiliar component 
instead of guessing URI options.")
+    @DevMCPEnableByDefault
+    public String camelCatalogDoc(
+            @JsonRpcDescription("Component scheme, e.g. timer, kafka, sql") 
String name,
+            @JsonRpcDescription("Optional keyword to keep only the options 
whose name or description matches") String optionsFilter) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        String json = catalog.componentJSonSchema(name);
+        if (json == null) {
+            JsonObject error = new JsonObject();
+            error.put("error", "Component '" + name + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + name + " dependency to the project.");
+            return error.toJson();
+        }
+        if (optionsFilter == null || optionsFilter.isBlank()) {
+            return json;
+        }
+        return filterComponentOptions(json, optionsFilter);
+    }
+
+    /**
+     * Validates a Camel endpoint URI against the catalog of the application.
+     *
+     * @param  uri The full endpoint URI
+     * @return     JSON String representation of the validation result
+     */
+    @JsonRpcDescription("Validates a Camel endpoint URI against the catalog of 
this application:"
+            + " unknown options (with suggestions), invalid enum or boolean 
values, missing required options."
+            + " Call it after writing or changing any endpoint URI.")
+    @DevMCPEnableByDefault
+    public String camelValidateEndpointUri(
+            @JsonRpcDescription("Full endpoint URI, e.g. 
kafka:orders?brokers=localhost:9092") String uri) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        JsonObject result = new JsonObject();
+        String scheme = uri != null ? StringHelper.before(uri, ":") : null;
+        if (scheme == null || scheme.isBlank()) {
+            result.put("valid", false);
+            result.put("errors", "Malformed endpoint URI, expected 
scheme:path[?options]");
+            return result.toJson();
+        }
+        if (catalog.componentJSonSchema(scheme) == null) {
+            result.put("valid", false);
+            result.put("errors", "Component '" + scheme + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + scheme + " dependency to the project.");
+            return result.toJson();
+        }
+        try {
+            EndpointValidationResult validation = 
catalog.validateProperties(scheme, catalog.endpointProperties(uri));
+            result.put("valid", validation.isSuccess());
+            if (!validation.isSuccess()) {
+                result.put("errors", validation.summaryErrorMessage(false));
+            }
+        } catch (URISyntaxException e) {
+            result.put("valid", false);
+            result.put("errors", "Malformed endpoint URI: " + e.getMessage());
+        }
+        return result.toJson();
+    }
+
+    private static String filterComponentOptions(String json, String filter) {
+        try {
+            JsonObject schema = (JsonObject) Jsoner.deserialize(json);
+            String keyword = filter.toLowerCase(Locale.ROOT);
+            for (String section : List.of("componentProperties", "properties", 
"headers")) {
+                if (schema.get(section) instanceof JsonObject options) {
+                    JsonObject filtered = new JsonObject();
+                    for (Map.Entry<String, Object> option : 
options.entrySet()) {
+                        if (optionMatches(option, keyword)) {
+                            filtered.put(option.getKey(), option.getValue());
+                        }
+                    }
+                    schema.put(section, filtered);
+                }
+            }
+            return schema.toJson();
+        } catch (DeserializationException e) {
+            return json;
+        }
+    }
+
+    private static boolean optionMatches(Map.Entry<String, Object> option, 
String keyword) {
+        if (option.getKey().toLowerCase(Locale.ROOT).contains(keyword)) {
+            return true;
+        }
+        if (option.getValue() instanceof Map<?, ?> attributes) {
+            return attributes.get("description") instanceof String description
+                    && description.toLowerCase(Locale.ROOT).contains(keyword);
+        }
+        return false;
+    }
+
+    private static RuntimeCamelCatalog getRuntimeCamelCatalog() {

Review Comment:
   Minor: `getRuntimeCamelCatalog()` duplicates `getDevConsoleRegistry()` apart 
from the plugin class. A shared `private static <T> T getContextPlugin(Class<T> 
type)` would do for both.



##########
extensions/bean/deployment/src/main/resources/META-INF/quarkus-skill.md:
##########
@@ -0,0 +1,45 @@
+<!--
+
+    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.
+
+-->
+
+### Referring to beans by name in routes
+
+A bean called by name from a route needs three annotations: `@Named("myBean")` 
(or `io.smallrye.common.annotation.Identifier`), a CDI scope such as 
`@ApplicationScoped`, and `@RegisterForReflection` (**required for native 
mode**):
+
+```java
+@ApplicationScoped
+@Named("myBean")
+@RegisterForReflection
+public class MyBean {
+    public String hello(String name) {
+        return "Hello " + name;
+    }
+}
+```
+
+Use it in a route with the `.bean("myBean", "hello")` EIP (works in every 
DSL), or as an endpoint: `.to(bean("myBean").method("hello"))` with the 
Endpoint DSL, `.to("bean:myBean?method=hello")` with string URIs.
+
+### POJO consuming and producing
+
+- `@Consume("direct:in")` on a bean method auto-creates a route that delivers 
messages to that method.
+- `@Produce("direct:out")` on a `ProducerTemplate`, `FluentProducerTemplate`, 
or custom interface field sends messages to the endpoint.
+- Neither annotation works on setter methods: use fields.

Review Comment:
   `@Consume` is a method annotation, so "use fields" doesn't apply to it. The 
setter restriction in `cdi.adoc` is about `@EndpointInject` and `@Produce`. An 
agent following this might move `@Consume` onto a field, and the consumer would 
never be created.



##########
extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/devui/CamelCoreDevUIService.java:
##########
@@ -141,6 +151,125 @@ public boolean updateConsoleOptions(String id, 
Map<String, Object> options) {
         }) != null;
     }
 
+    /**
+     * Gets the Camel catalog documentation of a component available in the 
application.
+     *
+     * @param  name          The component scheme
+     * @param  optionsFilter Optional keyword to keep only the matching options
+     * @return               JSON String representation of the component 
catalog documentation
+     */
+    @JsonRpcDescription("Camel catalog documentation of a component: endpoint 
URI syntax and all options"
+            + " (name, type, default value, description), from the exact Camel 
version of this application."
+            + " Use it before writing a route with an unfamiliar component 
instead of guessing URI options.")
+    @DevMCPEnableByDefault
+    public String camelCatalogDoc(
+            @JsonRpcDescription("Component scheme, e.g. timer, kafka, sql") 
String name,
+            @JsonRpcDescription("Optional keyword to keep only the options 
whose name or description matches") String optionsFilter) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        String json = catalog.componentJSonSchema(name);
+        if (json == null) {
+            JsonObject error = new JsonObject();
+            error.put("error", "Component '" + name + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + name + " dependency to the project.");
+            return error.toJson();
+        }
+        if (optionsFilter == null || optionsFilter.isBlank()) {
+            return json;
+        }
+        return filterComponentOptions(json, optionsFilter);
+    }
+
+    /**
+     * Validates a Camel endpoint URI against the catalog of the application.
+     *
+     * @param  uri The full endpoint URI
+     * @return     JSON String representation of the validation result
+     */
+    @JsonRpcDescription("Validates a Camel endpoint URI against the catalog of 
this application:"
+            + " unknown options (with suggestions), invalid enum or boolean 
values, missing required options."
+            + " Call it after writing or changing any endpoint URI.")
+    @DevMCPEnableByDefault
+    public String camelValidateEndpointUri(
+            @JsonRpcDescription("Full endpoint URI, e.g. 
kafka:orders?brokers=localhost:9092") String uri) {
+        RuntimeCamelCatalog catalog = getRuntimeCamelCatalog();
+        if (catalog == null) {
+            return "{}";
+        }
+        JsonObject result = new JsonObject();
+        String scheme = uri != null ? StringHelper.before(uri, ":") : null;
+        if (scheme == null || scheme.isBlank()) {
+            result.put("valid", false);
+            result.put("errors", "Malformed endpoint URI, expected 
scheme:path[?options]");
+            return result.toJson();
+        }
+        if (catalog.componentJSonSchema(scheme) == null) {
+            result.put("valid", false);
+            result.put("errors", "Component '" + scheme + "' is not on the 
application classpath. Add the camel-quarkus-"
+                    + scheme + " dependency to the project.");
+            return result.toJson();
+        }
+        try {
+            EndpointValidationResult validation = 
catalog.validateProperties(scheme, catalog.endpointProperties(uri));
+            result.put("valid", validation.isSuccess());
+            if (!validation.isSuccess()) {
+                result.put("errors", validation.summaryErrorMessage(false));
+            }
+        } catch (URISyntaxException e) {
+            result.put("valid", false);
+            result.put("errors", "Malformed endpoint URI: " + e.getMessage());
+        }
+        return result.toJson();
+    }
+
+    private static String filterComponentOptions(String json, String filter) {
+        try {
+            JsonObject schema = (JsonObject) Jsoner.deserialize(json);
+            String keyword = filter.toLowerCase(Locale.ROOT);
+            for (String section : List.of("componentProperties", "properties", 
"headers")) {

Review Comment:
   `optionsFilter` only filters `componentProperties`, `properties` and 
`headers`. For API-based components (box, google-drive, twilio, etc.) 
`apiProperties` / `apis` stay unfiltered, and they're often the largest part of 
the output.



##########
extensions-core/core/deployment/src/main/resources/META-INF/quarkus-skill.md:
##########
@@ -0,0 +1,87 @@
+<!--
+
+    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.
+
+-->
+
+### Defining routes
+
+Prefer the type-safe **Endpoint DSL**: extend 
`org.apache.camel.builder.endpoint.EndpointRouteBuilder` (included in 
`camel-quarkus-core`, no extra dependency) and override `configure()`. 
Components and their options are fluent methods, so a wrong option is a compile 
error instead of a runtime failure. No annotation needed; add 
`@ApplicationScoped` only when the builder itself injects beans or config.
+
+```java
+public class MyRoute extends EndpointRouteBuilder {
+    @Override
+    public void configure() {
+        from(timer("tick").period(5000))
+            .log("Hello ${body}");
+    }
+}
+```
+
+Beware: builder methods for **all** components compile via 
`camel-quarkus-core`, even when the component's extension is missing from the 
`pom.xml`. The route then fails at runtime. Always add the 
`camel-quarkus-<name>` dependency for every component used.
+
+Plain `RouteBuilder` with string URIs (`from("timer:tick?period=5000")`) also 
works.
+
+YAML DSL needs `camel-quarkus-yaml-dsl`, and route files are not 
auto-discovered: list them in `camel.main.routes-include-pattern` in 
`application.properties`.
+
+### Dependencies and configuration
+
+- Import the `io.quarkus.platform:quarkus-camel-bom` in 
`<dependencyManagement>` (next to `quarkus-bom`) and declare `camel-quarkus-*` 
dependencies **without versions**.
+- Configure components in `application.properties` with 
`camel.component.<name>.<option> = value` instead of Java configuration code.
+- Before using an extension, read its reference page: 
`https://camel.apache.org/camel-quarkus/latest/reference/extensions/<name>.html`.
 It documents the extension's usage patterns, configuration, and limitations 
(such as JVM-only support or native mode constraints).
+
+### Endpoint options: never guess
+
+Do not invent URI parameters. Look them up, in this order:
+
+1. **Preferred**: the Dev MCP tools `camelCatalogDoc` (component 
documentation, with an `optionsFilter` keyword) and `camelValidateEndpointUri` 
(validates a URI: unknown options with suggestions, invalid values, missing 
required options). They use the exact Camel version and classpath of the 
application. They require dev mode: start it first if it is not running.
+2. When dev mode is not an option, the versioned docs: 
`https://camel.apache.org/components/<camel-major.minor>.x/<name>-component.html`
+3. Last resort, offline: the component JAR embeds its full catalog in 
`META-INF/org/apache/camel/component/<name>/<name>.json` (in the local Maven 
repository once the `camel-quarkus-<name>` dependency is added).
+
+After writing or changing routes with string URIs or YAML, validate endpoint 
URIs and simple expressions (no compile or run needed; Endpoint DSL options are 
already checked by the compiler):
+
+```shell
+mvn org.apache.camel:camel-report-maven-plugin:<camel-version>:validate
+```
+
+Use the project's Camel version (see the `quarkus-camel-bom` import). It also 
flags duplicate route ids, `direct:`/`seda:` endpoints with no consumer, and 
bad Camel options in `application.properties`.
+
+### CDI patterns
+
+```java
+@Inject CamelContext context;
+@Inject ProducerTemplate producerTemplate;
+```
+
+Expose processors to routes by name with `@Named("myProcessor")` + 
`@ApplicationScoped`, then `.process("myProcessor")`. Use 
`@ActivateRequestContext` on methods called from routes that touch 
request-scoped beans. Never use Spring annotations (`@Component`, 
`@Autowired`): CDI only.
+
+### Testing
+
+Before writing any Camel Quarkus test, read the testing guide:
+https://camel.apache.org/camel-quarkus/latest/user-guide/testing.html
+
+The mistakes to avoid:
+
+- JVM tests are `@QuarkusTest` extending `CamelQuarkusTestSupport` (from the 
`camel-quarkus-junit` test dependency). **Never extend `CamelTestSupport`**: it 
is incompatible with Quarkus.

Review Comment:
   `@CamelQuarkusTest` is now on main, and the testing guide has a section on 
migrating from `CamelQuarkusTestSupport`. Since the skill tells agents to read 
that guide first, it'd be good to recommend `@CamelQuarkusTest` here. It could 
also note that it's optional (plain `@QuarkusTest` is fine when no Camel test 
features are needed) and JVM-only.



##########
extensions-core/core/deployment/src/main/resources/META-INF/quarkus-skill.md:
##########
@@ -0,0 +1,87 @@
+<!--
+
+    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.
+
+-->
+
+### Defining routes
+
+Prefer the type-safe **Endpoint DSL**: extend 
`org.apache.camel.builder.endpoint.EndpointRouteBuilder` (included in 
`camel-quarkus-core`, no extra dependency) and override `configure()`. 
Components and their options are fluent methods, so a wrong option is a compile 
error instead of a runtime failure. No annotation needed; add 
`@ApplicationScoped` only when the builder itself injects beans or config.
+
+```java
+public class MyRoute extends EndpointRouteBuilder {
+    @Override
+    public void configure() {
+        from(timer("tick").period(5000))
+            .log("Hello ${body}");
+    }
+}
+```
+
+Beware: builder methods for **all** components compile via 
`camel-quarkus-core`, even when the component's extension is missing from the 
`pom.xml`. The route then fails at runtime. Always add the 
`camel-quarkus-<name>` dependency for every component used.
+
+Plain `RouteBuilder` with string URIs (`from("timer:tick?period=5000")`) also 
works.
+
+YAML DSL needs `camel-quarkus-yaml-dsl`, and route files are not 
auto-discovered: list them in `camel.main.routes-include-pattern` in 
`application.properties`.

Review Comment:
   YAML routes are auto-discovered from the default 
`camel.main.routes-include-pattern` (`classpath:camel/*`, `camel-template/*`, 
`camel-rest/*`). The statement could be changed to say that, and to note that 
setting the property replaces the defaults. Otherwise agents may add an 
explicit pattern and stop other route files from loading.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to