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]