You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "Croway (via GitHub)" <gi...@apache.org> on 2026/10/16 15:48:20 UTC
[PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Croway opened a new pull request, #27960:
URL: https://github.com/apache/camel/pull/27960
Section C of [CAMEL-25642](https://issues.apache.org/jira/browse/CAMEL-25642): the OpenAPI tools of camel-jbang-mcp produced operation ids and mock file locations that the `rest-openapi` component does not use, so the scaffolded routes of an operation without `operationId` were never bound and the listed mock files were never served. Sections A (runtime tools) and B (migration data) are separate PRs.
### Changes
**camel-rest-openapi**
- New `RestOpenApiHelper.generateOperationId(String path, PathItem.HttpMethod method)`, the rule for an operation without `operationId` (`GET /pets/{petId}` becomes `GENOPID_GET.pets._petId_`). `DefaultRestOpenapiProcessorStrategy` now calls it; behaviour unchanged.
**camel-jbang-mcp (`OpenApiTools`)**
- `camel_openapi_scaffold` and `camel_openapi_validate` take the generated id from that helper (they built `GENOPID_GET_pets_petId`). `camel-rest-openapi` is a dependency of the module now; it only adds the component itself, `camel-rest` and `camel-platform-http` were already on the classpath through camel-jbang-core.
- `camel_openapi_scaffold` returns `modeExplanation`, and with `missingOperation=mock` it returns `mockFiles`: one file per path of the spec, named the way the component matches it (the location without extension ends with the request path, no HTTP method suffix, a path parameter is matched by its value: `camel-mock/pets/42.json` for `GET /pets/42`), with what Camel answers without the file. The wrong `directoryStructure` is dropped.
- `camel_openapi_mock_guidance` is removed: its per-operation walk duplicated the scaffold's and produced the wrong `camel-mock/<path>.<method><ext>` layout.
- Mode explanations corrected against the strategy: `ignore` does not answer 404 (the request is not routed and Camel answers with the request message); without a mock file mock mode answers the example of the `200` response, or 204; under the `dev` profile (the default of `camel run`) the default mode is `mock`.
- Security schemes: the component enforces none of them; every scheme is a `SECURITY_<type>` warning saying so and pointing to `oauthProfile` (an apiKey in query used to be reported as "supported", which came from the producer side).
- OpenAPI 3.1: swagger-parser 2.1.48 (the parent POM's `swagger-openapi3-java-parser-version`) parses 3.1 documents and the component reads only `getPaths()` with no version-specific code (its tests already run `petstore-3.1.yaml`), so `OPENAPI_31` is an info message; webhooks are still reported as not served.
**Docs**: OpenAPI tool table and examples in `camel-jbang-mcp.adoc`; upgrade guide 4.24 (removed tool, changed ids, mock paths and diagnostic codes).
### Tests
- New `OpenApiScaffoldRestOpenApiTest` (2 tests, `src/test/resources/petstore.yaml` with no `operationId` on `GET /pets/{petId}`): the scaffolded YAML is loaded into a `CamelContext` with the real `rest-openapi` component and an in-process `PlatformHttpEngine`; in `fail` mode the context starts (every operation is bound) and `GET /v1/pets/42` reaches `direct:GENOPID_GET.pets._petId_`; in `mock` mode a file at the scaffolded `camel-mock/pets/42.json` is served with 200.
- `OpenApiToolsTest`: the mock guidance tests are replaced by two scaffold tests; the security, 3.1 and generated-id assertions updated.
- After the rebase: `mvnd -P apache-snapshots test -pl dsl/camel-jbang/camel-jbang-core,dsl/camel-jbang/camel-jbang-mcp -Dtest='OpenApi*Test'`: all pass; `RestOpenapiProcessorStrategyTest` and `RestOpenApiHelperTest` of camel-rest-openapi pass (25 tests).
Not in this PR: in mock mode a non-body operation without a `200` response and without a mock file fails with a `NullPointerException` in `DefaultRestOpenapiProcessorStrategy.loadMockData`, filed as CAMEL-25663.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4297091954
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +306,100 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ if (path.contains("{")) {
+ String sample = path.replaceAll("\\{[^}]+}", "1");
+ note.append(" Camel matches the request path, so replace each {parameter} with a value:"
+ + " one file per value (camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample).append(").");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ files.add(new MockFileInfo("camel-mock" + path + ext, path, operationIds, example, note.toString()));
Review Comment:
Good catch on the hidden file, though the suggested `-` layout would not be matched by the component. `DefaultRestOpenapiProcessorStrategy` strips the leading slash of the request path and matches a resource whose location without extension ends with it; for `/` that target is the empty string, so any mock file matches (the first one found). In 101ef07c95fd the root path is listed as `camel-mock/index.json` (visible, and matched like any other file) and its note says that with more than one mock file the one answering `/` is not predictable. Test added.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -422,99 +415,43 @@ private Map.Entry<String, ApiResponse> findFirstSuccessResponse(ApiResponses res
return null;
}
- private void checkSecurityScheme(
- String name, SecurityScheme scheme,
- List<DiagnosticMessage> warnings, List<DiagnosticMessage> info) {
-
+ private void checkSecurityScheme(String name, SecurityScheme scheme, List<DiagnosticMessage> warnings) {
if (scheme.getType() == null) {
return;
}
-
- switch (scheme.getType()) {
- case APIKEY:
- if (scheme.getIn() == SecurityScheme.In.QUERY) {
- info.add(new DiagnosticMessage(
- "SECURITY_APIKEY_QUERY",
- "Security scheme '" + name + "' (apiKey in query) is supported by Camel.",
- null));
- } else {
- warnings.add(new DiagnosticMessage(
- "SECURITY_APIKEY_NOT_QUERY",
- "Security scheme '" + name + "' (apiKey in " + scheme.getIn()
- + ") is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- }
- break;
- case HTTP:
- warnings.add(new DiagnosticMessage(
- "SECURITY_HTTP",
- "Security scheme '" + name + "' (HTTP " + scheme.getScheme()
- + ") is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case OAUTH2:
- warnings.add(new DiagnosticMessage(
- "SECURITY_OAUTH2",
- "Security scheme '" + name
- + "' (OAuth2) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case OPENIDCONNECT:
- warnings.add(new DiagnosticMessage(
- "SECURITY_OPENIDCONNECT",
- "Security scheme '" + name
- + "' (OpenID Connect) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case MUTUALTLS:
- warnings.add(new DiagnosticMessage(
- "SECURITY_MUTUALTLS",
- "Security scheme '" + name
- + "' (Mutual TLS) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- default:
- break;
- }
+ String kind = switch (scheme.getType()) {
+ case APIKEY -> "apiKey in " + scheme.getIn();
+ case HTTP -> "HTTP " + scheme.getScheme();
Review Comment:
Done in 101ef07c95fd with `Objects.requireNonNullElse(..., "<unset>")` for both.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway merged PR #27960:
URL: https://github.com/apache/camel/pull/27960
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6220406225
Follow-ups in 101ef07c95fd: a parameterized test of `RestOpenApiHelper.generateOperationId` in `RestOpenApiHelperTest` (plain path, one and two path parameters, root path, trailing slash, dots and dashes in the path), null-safe security scheme text, and a visible `camel-mock/index.json` for the root path with a note on how the component matches it. Tests: `RestOpenApiHelperTest` 29, `RestOpenapiProcessorStrategyTest` 3, `OpenApiToolsTest` 20, `OpenApiScaffoldRestOpenApiTest` 2, all passing.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4297693069
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
Review Comment:
⚠️ **Stale advisory text:** Since CAMEL-25663, `loadMockData` handles `getResponses().get("200") == null` gracefully by returning HTTP 204 with an empty body (see `DefaultRestOpenapiProcessorStrategy`, lines 336–387). The message `"fails with an error, as the operation has no '200' response"` no longer reflects runtime behavior. Update to something like:
```suggestion
withoutFile.put(method, "answers HTTP 204 with an empty body (no '200' response defined)");
```
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
Review Comment:
🔍 **Nit:** `firstExample()` picks the first non-null example across all media types without content-type matching, while `loadMockData` does content negotiation at runtime. The description `"answers the example of its '200' response with HTTP 200"` conflates the two. Consider `"answers an example of its '200' response with HTTP 200 (content type matched by the request)"`.
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -543,22 +488,16 @@ public record ScaffoldResult(
int operationCount,
String specFilename,
String missingOperation,
- String apiTitle) {
+ String apiTitle,
+ String modeExplanation,
+ List<MockFileInfo> mockFiles) {
}
public record MockFileInfo(
String filePath,
Review Comment:
💡 **Naming:** `filePath` for parametric routes holds the OpenAPI template (`camel-mock/pets/{petId}.json`), not the ready-to-use path. The test explicitly calls `.replace("{petId}", "42")` on it before writing the file. Consider renaming to `filePathTemplate`, or at least add a Javadoc comment: `/** OpenAPI path template — callers must substitute path parameters (e.g. {petId}) before using this as a filesystem path. */`
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4300265300
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +309,110 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, and the listed file carries the sample value {@code 1}, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200 when the request's"
+ + " Content-Type or Accept header names a media type of that response"
+ + " (Accept */* takes application/json, then application/xml), otherwise"
+ + " HTTP 204 with an empty body");
+ if (example == null) {
+ example = opExample;
+ }
+ } else {
+ // no example in the '200' response, or no '200' response at all (CAMEL-25663): an empty answer
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ // the component matches the request path, not the template: a path parameter takes a sample value
+ String sample = path.replaceAll("\\{[^}]+}", SAMPLE_PATH_PARAMETER_VALUE);
+ if (path.contains("{")) {
+ note.append(" Camel matches the request path, so the file name carries the value the request uses:"
+ + " camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample)
+ .append(", add one file per value.");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ // the component matches a resource whose location without extension ends with the request path without its
+ // leading slash; for the root path that is the empty string, so any mock file matches
+ String fileName = "/".equals(path) ? "camel-mock/index" + ext : "camel-mock" + sample + ext;
+ if ("/".equals(path)) {
+ note.append(" Camel matches the root path to any mock file (the first one found), so with more than"
+ + " one file the one that answers / is not predictable.");
+ }
+ files.add(new MockFileInfo(fileName, path, operationIds, example, note.toString()));
+ }
+ return files;
+ }
+
+ /**
+ * The example camel-rest-openapi answers in mock mode for an operation without mock file: the example, or the first
+ * of the examples, of a media type of its "200" response.
Review Comment:
🔍 **Nit:** `firstExample()` iterates all media types and returns the first non-null example, regardless of content type. The Javadoc says it's "the example camel-rest-openapi answers" — but at runtime, `loadMockData` does content-type negotiation: it picks the media type that matches the request's `Content-Type`/`Accept`, then takes the example from that specific media type. When a `200` response has multiple media types with different examples (e.g. `application/json` and `application/xml` with distinct values), `firstExample()` may return the JSON example while an `Accept: application/xml` request would receive the XML one.
The `exampleContent` field is advisory (it shows a sample to the MCP caller, not what Camel necessarily serves), so this is a documentation inaccuracy rather than a runtime bug. Suggest:
```suggestion
* An example from the "200" response of the spec, used as a preview hint. At runtime,
* camel-rest-openapi picks the example from the media type that matches the request's
* Content-Type or Accept header; this method returns the first non-null example across
* all media types of the response.
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4301786789
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +309,110 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, and the listed file carries the sample value {@code 1}, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200 when the request's"
Review Comment:
Nit: this sentence is now written into the note of every path with a `200` example, and `modeExplanation` says the same once. For a spec with a few dozen paths (an APIkit project in a migration, say) that is a few thousand characters of repeated text in one answer. A short form here, such as "answers the example of its '200' response with HTTP 200 for a matching Content-Type or Accept (see modeExplanation), otherwise HTTP 204", keeps the answer smaller.
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6226661157
Review follow-ups are in b66295875a5d, one commit on top of the three existing ones; the branch is rebased on main (the only conflict was the 4.24 upgrade guide, both sides kept), hence the force push.
- An operation without a `200` response is listed as answering HTTP 204 with an empty body, as the fixed `loadMockData` does; the PR description no longer presents CAMEL-25663 as out of scope.
- `filePath` of a parameterized path carries the sample value (`camel-mock/pets/1.json`), `requestPath` keeps the template; the MCP page says so, and the component test writes the file at `filePath` as given.
- The note and the mode explanation name the media type the example is taken for (Content-Type or Accept, `*/*` falling back to application/json then application/xml), otherwise HTTP 204.
Tests: `OpenApiToolsTest` 21, `OpenApiScaffoldRestOpenApiTest` 2, whole camel-jbang-mcp module (468) and camel-jbang-core suite, all passing.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6219229223
:star2: Thank you for your contribution to the Apache Camel project! :star2:
:robot: CI automation will test this PR automatically.
:camel: Apache Camel Committers, please review the following items:
* First-time contributors **require MANUAL approval** for the GitHub Actions to run
* You can use the command `/component-test (camel-)component-name1 (camel-)component-name2..` to request a test from the test bot although they are normally detected and executed by CI.
* You can label PRs using `skip-tests` and `test-dependents` to fine-tune the checks executed by this PR.
* Build and test logs are available in the summary page. **Only** [Apache Camel committers](https://camel.apache.org/community/team/#committers) have access to the summary.
:warning: Be careful when sharing logs. Review their contents before sharing them publicly.
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4302646413
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +309,110 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, and the listed file carries the sample value {@code 1}, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200 when the request's"
Review Comment:
Done in 4e4c2b0a4461: the per-path note now reads "answers the example of its '200' response with HTTP 200 for a matching Content-Type or Accept (see modeExplanation), otherwise HTTP 204"; the full rule stays in `modeExplanation` only.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +309,110 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, and the listed file carries the sample value {@code 1}, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200 when the request's"
+ + " Content-Type or Accept header names a media type of that response"
+ + " (Accept */* takes application/json, then application/xml), otherwise"
+ + " HTTP 204 with an empty body");
+ if (example == null) {
+ example = opExample;
+ }
+ } else {
+ // no example in the '200' response, or no '200' response at all (CAMEL-25663): an empty answer
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ // the component matches the request path, not the template: a path parameter takes a sample value
+ String sample = path.replaceAll("\\{[^}]+}", SAMPLE_PATH_PARAMETER_VALUE);
+ if (path.contains("{")) {
+ note.append(" Camel matches the request path, so the file name carries the value the request uses:"
+ + " camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample)
+ .append(", add one file per value.");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ // the component matches a resource whose location without extension ends with the request path without its
+ // leading slash; for the root path that is the empty string, so any mock file matches
+ String fileName = "/".equals(path) ? "camel-mock/index" + ext : "camel-mock" + sample + ext;
+ if ("/".equals(path)) {
+ note.append(" Camel matches the root path to any mock file (the first one found), so with more than"
+ + " one file the one that answers / is not predictable.");
+ }
+ files.add(new MockFileInfo(fileName, path, operationIds, example, note.toString()));
+ }
+ return files;
+ }
+
+ /**
+ * The example camel-rest-openapi answers in mock mode for an operation without mock file: the example, or the first
+ * of the examples, of a media type of its "200" response.
Review Comment:
Done in 4e4c2b0a4461: the javadoc says the example is a preview of the file content and that at runtime camel-rest-openapi takes the example of the media type matching the request. The related point from the review is in the same commit: the example is taken from the media type that names the extension when it has one, else from the first media type with an example, and the extension then follows that media type, so content and file name agree (test `scaffoldNamesTheMockFileAfterTheMediaTypeOfItsExample`: a `200` with `application/json` without example and `application/xml` with one gives `camel-mock/pets.xml` with the XML).
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6220400495
<!-- ci-tested-modules -->
:test_tube: **CI tested the following changed modules:**
- `components/camel-rest-openapi`
- `docs`
- `dsl/camel-jbang/camel-jbang-mcp`
---
<details><summary>:microscope: Scalpel shadow comparison — Scalpel: 20 of 706 tested, 2 compile-only — current: 20 all tested</summary>
[Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **20 affected modules** (current approach: 20).
**Skip-tests mode would test 20 modules** (3 direct + 0 downstream), **skip tests for 2** (generated code, meta-modules)
<details><summary>Modules Scalpel would test (20)</summary>
- `camel-a2a` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-jbang-mcp` ← dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiScaffoldRestOpenApiTest.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiToolsTest.java, dsl/camel-jbang/camel-jbang-mcp/src/test/resources/petstore.yaml, own pom dsl/camel-jbang/camel-jbang-mcp/pom.xml changed
- `camel-jbang-plugin-mcp` ← depends on affected reactor module org.apache.camel:camel-jbang-core
- `camel-jbang-plugin-tui` ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
- `camel-jbang-plugin-validate` ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
- `camel-jsonpath` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-knative-http` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-launcher-container` ← depends on affected reactor module org.apache.camel:camel-launcher
- `camel-mcp-server` ← depends on affected reactor module org.apache.camel:camel-openai
- `camel-micrometer-prometheus` ← depends on affected reactor module org.apache.camel:camel-platform-http-main
- `camel-oauth` ← depends on affected reactor module org.apache.camel:camel-platform-http-main
- `camel-observability-services` ← depends on affected reactor module org.apache.camel:camel-micrometer-prometheus
- `camel-openai` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-openapi-validator` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-platform-http-main` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
- `camel-platform-http-vertx` ← depends on affected reactor module org.apache.camel:camel-rest-openapi
- `camel-rest-openapi` ← components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java, components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiHelper.java
- `camel-semantic-openai` ← depends on affected reactor module org.apache.camel:camel-openai
- `camel-yaml-dsl-validator` ← depends on affected reactor module org.apache.camel:camel-catalog
- `camel-yaml-dsl-validator-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
</details>
<details><summary>Modules with tests skipped (2)</summary>
- `camel-itest`
- `camel-yaml-dsl-deserializers`
</details>
> :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
</details>
:warning: **Some tests are disabled on GitHub Actions** (`@DisabledIfSystemProperty(named = "ci.env.name")`) and require manual verification:
- `dsl/camel-jbang/camel-jbang-mcp`: 2 test(s) disabled on GitHub Actions
<details><summary>All tested modules (47 modules, 8m 23s total)</summary>
**Total reactor time:** 8m 23s
| Module | Duration | Status |
| --- | --- | --- |
| Camel :: AI :: MCP Server | 49.9s | SUCCESS |
| Camel :: AI :: OpenAI | 40.9s | SUCCESS |
| Camel :: Launcher | 40.3s | SUCCESS |
| Camel :: JBang :: MCP | 33.9s | SUCCESS |
| Camel :: JBang :: Plugin :: Validate | 33.5s | SUCCESS |
| Camel :: OAuth | 28.6s | SUCCESS |
| Camel :: Knative HTTP | 27.2s | SUCCESS |
| Camel :: REST OpenApi | 22.3s | SUCCESS |
| Camel :: Platform HTTP :: Vert.x | 20.5s | SUCCESS |
| Camel :: AI :: A2A | 18.6s | SUCCESS |
| Camel :: Catalog :: Camel Catalog | 18.4s | SUCCESS |
| Camel :: Component DSL | 17.2s | SUCCESS |
| Camel :: YAML DSL :: Validator | 15.8s | SUCCESS |
| Camel :: YAML DSL | 15.4s | SUCCESS |
| Camel :: Docs | 12.8s | SUCCESS |
| Camel :: Platform HTTP :: Main | 11.6s | SUCCESS |
| Camel :: Kamelet Main | 11.0s | SUCCESS |
| Camel :: JSon Path | 10.6s | SUCCESS |
| Camel :: JBang :: Plugin :: Kubernetes | 10.2s | SUCCESS |
| Camel :: OpenAPI :: Validator | 8.3s | SUCCESS |
| Camel :: YAML DSL :: Deserializers | 6.1s | SUCCESS |
| Camel :: JBang :: Plugin :: Testing | 6.0s | SUCCESS |
| Camel :: YAML DSL :: Validator Maven Plugin | 5.8s | SUCCESS |
| Camel :: Catalog :: Camel Route Parser | 5.7s | SUCCESS |
| Camel :: Catalog :: Camel Report Maven Plugin | 5.6s | SUCCESS |
| Camel :: AI :: Semantic OpenAI | 4.6s | SUCCESS |
| Camel :: All Components Sync point | 4.0s | SUCCESS |
| Camel :: YAML DSL :: Maven Plugins | 2.3s | SUCCESS |
| Camel :: Micrometer :: Prometheus | 2.1s | SUCCESS |
| Camel :: Catalog :: Maven | 2.0s | SUCCESS |
| Camel :: Catalog :: Suggest (deprecated) | 1.9s | SUCCESS |
| Camel :: JBang :: Plugin :: Edit | 1.3s | SUCCESS |
| Camel :: JBang :: Integration tests | 1.2s | SUCCESS |
| Camel :: Assembly | 1.2s | SUCCESS |
| Camel :: JBang :: Plugin :: Generate | 0.9s | SUCCESS |
| Camel :: Coverage | 0.9s | SUCCESS |
| Camel :: Launcher :: Container | 0.7s | SUCCESS |
| Camel :: JBang :: Main | 0.7s | SUCCESS |
| Camel :: Endpoint DSL :: Support | 0.6s | SUCCESS |
| Camel :: Catalog :: Console | 0.6s | SUCCESS |
| Camel :: JBang :: Plugin :: MCP | 0.5s | SUCCESS |
| Camel :: Catalog :: Dummy Component | 0.5s | SUCCESS |
| Camel :: Observability Services | 0.4s | SUCCESS |
| Camel :: Endpoint DSL | n/a | |
| Camel :: Integration Tests | n/a | |
| Camel :: JBang :: Core | n/a | |
| Camel :: JBang :: Plugin :: TUI | n/a | |
**Top 20 slowest modules:**
- `Camel :: AI :: MCP Server` (49.9s)
- `Camel :: AI :: OpenAI` (40.9s)
- `Camel :: Launcher` (40.3s)
- `Camel :: JBang :: MCP` (33.9s)
- `Camel :: JBang :: Plugin :: Validate` (33.5s)
- `Camel :: OAuth` (28.6s)
- `Camel :: Knative HTTP` (27.2s)
- `Camel :: REST OpenApi` (22.3s)
- `Camel :: Platform HTTP :: Vert.x` (20.5s)
- `Camel :: AI :: A2A` (18.6s)
- `Camel :: Catalog :: Camel Catalog` (18.4s)
- `Camel :: Component DSL` (17.2s)
- `Camel :: YAML DSL :: Validator` (15.8s)
- `Camel :: YAML DSL` (15.4s)
- `Camel :: Docs` (12.8s)
- `Camel :: Platform HTTP :: Main` (11.6s)
- `Camel :: Kamelet Main` (11.0s)
- `Camel :: JSon Path` (10.6s)
- `Camel :: JBang :: Plugin :: Kubernetes` (10.2s)
- `Camel :: OpenAPI :: Validator` (8.3s)
</details>
---
:gear: [View full build and test results](https://github.com/apache/camel/actions/runs/38929893117)
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4297379988
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ if (path.contains("{")) {
+ String sample = path.replaceAll("\\{[^}]+}", "1");
+ note.append(" Camel matches the request path, so replace each {parameter} with a value:"
+ + " one file per value (camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample).append(").");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ // the component matches a resource whose location without extension ends with the request path without its
+ // leading slash; for the root path that is the empty string, so any mock file matches
+ String fileName = "/".equals(path) ? "camel-mock/index" + ext : "camel-mock" + path + ext;
Review Comment:
For a path with a parameter this is the template, `camel-mock/pets/{petId}.json`. The component matches the request path (`loc.endsWith(target)` in `loadMockData`), so a file written under this name is never served, and an agent that writes `filePath` as given is where it was before this PR. The note explains it, but the field is what a model acts on, and the MCP page says the opposite ("a path parameter is replaced by its value, such as `camel-mock/pets/42.json`"). Could `filePath` carry the sample value the note already uses (`camel-mock/pets/1.json`), with the template kept in `requestPath`? The test then no longer needs its `replace("{petId}", "42")`.
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
Review Comment:
Nit: `loadMockData` takes the example only from the media type that matches the request's Content-Type or Accept header (`*/*` falls back to `application/json`, then `application/xml`); for any other request, such as one without an Accept header, the answer is HTTP 204 with an empty body. So "answers the example of its '200' response" holds for a request that accepts that media type. Worth a few words here and in the mode explanation (line 457).
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
Review Comment:
CAMEL-25663 is fixed: #27897 (merged 2026-10-15, in the base of this branch) treats a missing `200` response as one without content, so the mock answers HTTP 204 with an empty body (`DefaultRestOpenapiProcessorStrategy.java:337-342`). The note sends the agent to add a file it does not need.
```suggestion
} else {
// no example in the '200' response, or no '200' response at all (CAMEL-25663): an empty answer
withoutFile.put(method, "answers HTTP 204 with an empty body");
}
```
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4300241210
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
Review Comment:
Done in b66295875a5d with your text: the two branches are one `else`, and the PR description no longer lists CAMEL-25663 as out of scope. Checked `loadMockData` on the rebased branch: a missing `200` response gives a null content, and the answer is HTTP 204 with an empty body. Test `scaffoldNamesThePathParameterMockFileWithASampleValue` covers a GET with only a `201` response.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ if (path.contains("{")) {
+ String sample = path.replaceAll("\\{[^}]+}", "1");
+ note.append(" Camel matches the request path, so replace each {parameter} with a value:"
+ + " one file per value (camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample).append(").");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ // the component matches a resource whose location without extension ends with the request path without its
+ // leading slash; for the root path that is the empty string, so any mock file matches
+ String fileName = "/".equals(path) ? "camel-mock/index" + ext : "camel-mock" + path + ext;
Review Comment:
Done in b66295875a5d: `filePath` carries the sample value the note already used, `camel-mock/pets/1.json`, and `requestPath` keeps the template; the note says the file name carries the value the request uses, one file per value, and the `MockFileInfo` javadoc says the same. The MCP page now reads "a path parameter carries a sample value, such as `camel-mock/pets/1.json` for `/pets/{petId}`, which answers `/pets/1`". `OpenApiScaffoldRestOpenApiTest` writes the file at `filePath()` as given, without the `replace`, and requests `/v1/pets/1`.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
Review Comment:
Done in b66295875a5d, in the per-method note and in the mode explanation: the example of the `200` response is answered when the request's Content-Type or Accept header names a media type of that response (Accept `*/*` takes application/json, then application/xml), otherwise HTTP 204 with an empty body, a request without an Accept header included. The MCP page has the same sentence.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
Review Comment:
Done in b66295875a5d: one `else` branch, "answers HTTP 204 with an empty body", with a comment naming CAMEL-25663.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
Review Comment:
Done in b66295875a5d (same change as the thread above).
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -543,22 +488,16 @@ public record ScaffoldResult(
int operationCount,
String specFilename,
String missingOperation,
- String apiTitle) {
+ String apiTitle,
+ String modeExplanation,
+ List<MockFileInfo> mockFiles) {
}
public record MockFileInfo(
String filePath,
Review Comment:
Done in b66295875a5d, the other way round: `filePath` now carries the sample value (`camel-mock/pets/1.json`), so it is the file to write as given, and `requestPath` keeps the template; the `MockFileInfo` javadoc documents both, and the test no longer substitutes the parameter.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
Review Comment:
Done in b66295875a5d: the note and the mode explanation say which media type is taken (the request's Content-Type or Accept header, with the `*/*` fallback order) and that any other request gets HTTP 204.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4302185191
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +309,110 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, and the listed file carries the sample value {@code 1}, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200 when the request's"
Review Comment:
@Croway, not blocking, but could you shorten this per-path note (pointing to `modeExplanation` for the media type rule) before merge? For a spec with a few dozen paths the repeated sentence adds a few thousand characters to one answer. The gnodet-bot Javadoc nit on `firstExample()` can go in the same commit if you like. Note that #27959 also adds a section to the 4.24 upgrade guide, so a rebase may be needed once it is merged.
_Claude Code on behalf of Claus Ibsen_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway closed pull request #27960: CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi
URL: https://github.com/apache/camel/pull/27960
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6219628173
The PR doc validation failed on the upgrade guide: `{petId}` in `GET /pets/{petId}` was read as an AsciiDoc attribute ("skipping reference to missing attribute: petid"). Pushed the literal form; the local Antora build of the page is clean.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4296847489
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -422,99 +415,43 @@ private Map.Entry<String, ApiResponse> findFirstSuccessResponse(ApiResponses res
return null;
}
- private void checkSecurityScheme(
- String name, SecurityScheme scheme,
- List<DiagnosticMessage> warnings, List<DiagnosticMessage> info) {
-
+ private void checkSecurityScheme(String name, SecurityScheme scheme, List<DiagnosticMessage> warnings) {
if (scheme.getType() == null) {
return;
}
-
- switch (scheme.getType()) {
- case APIKEY:
- if (scheme.getIn() == SecurityScheme.In.QUERY) {
- info.add(new DiagnosticMessage(
- "SECURITY_APIKEY_QUERY",
- "Security scheme '" + name + "' (apiKey in query) is supported by Camel.",
- null));
- } else {
- warnings.add(new DiagnosticMessage(
- "SECURITY_APIKEY_NOT_QUERY",
- "Security scheme '" + name + "' (apiKey in " + scheme.getIn()
- + ") is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- }
- break;
- case HTTP:
- warnings.add(new DiagnosticMessage(
- "SECURITY_HTTP",
- "Security scheme '" + name + "' (HTTP " + scheme.getScheme()
- + ") is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case OAUTH2:
- warnings.add(new DiagnosticMessage(
- "SECURITY_OAUTH2",
- "Security scheme '" + name
- + "' (OAuth2) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case OPENIDCONNECT:
- warnings.add(new DiagnosticMessage(
- "SECURITY_OPENIDCONNECT",
- "Security scheme '" + name
- + "' (OpenID Connect) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- case MUTUALTLS:
- warnings.add(new DiagnosticMessage(
- "SECURITY_MUTUALTLS",
- "Security scheme '" + name
- + "' (Mutual TLS) is defined but not enforced by Camel's REST OpenAPI integration. "
- + "You must handle authentication in your route logic.",
- null));
- break;
- default:
- break;
- }
+ String kind = switch (scheme.getType()) {
+ case APIKEY -> "apiKey in " + scheme.getIn();
+ case HTTP -> "HTTP " + scheme.getScheme();
Review Comment:
⚠️ **Potential NPE on malformed specs**
`scheme.getIn()` (APIKEY) and `scheme.getScheme()` (HTTP) are not null-checked before string concatenation. On a malformed or incomplete OpenAPI spec, these can return `null`, producing misleading diagnostics like `"apiKey in null"` or `"HTTP null"`. Consider guarding with a fallback:
```suggestion
case APIKEY -> "apiKey in " + Objects.requireNonNullElse(scheme.getIn(), "<unset>");
case HTTP -> "HTTP " + Objects.requireNonNullElse(scheme.getScheme(), "<unset>");
```
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +306,100 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
+ }
+ }
+
+ String ext = responseType != null && responseType.contains("xml") ? ".xml" : ".json";
+ StringBuilder note = new StringBuilder("Served with HTTP 200 for any method of ").append(path)
+ .append(" without a route.");
+ if (path.contains("{")) {
+ String sample = path.replaceAll("\\{[^}]+}", "1");
+ note.append(" Camel matches the request path, so replace each {parameter} with a value:"
+ + " one file per value (camel-mock")
+ .append(sample).append(ext).append(" answers ").append(sample).append(").");
+ }
+ note.append(" Without the file, ");
+ withoutFile.forEach((method, answer) -> note.append(method).append(' ').append(answer).append("; "));
+ note.setLength(note.length() - 2);
+ note.append('.');
+
+ files.add(new MockFileInfo("camel-mock" + path + ext, path, operationIds, example, note.toString()));
Review Comment:
🐛 **Root path `/` produces a hidden file**
When an OpenAPI spec includes a path `"/"`, this expression produces `"camel-mock" + "/" + ".json"` = `"camel-mock/.json"`, which is a hidden file on Unix (the filename starts with `.` after the directory separator). Most file managers, editors, and `ls` without `-a` will silently ignore it. Consider stripping the leading `/` and replacing empty strings with a placeholder:
```suggestion
files.add(new MockFileInfo("camel-mock/" + (path.equals("/") ? "root" : path.substring(1).replace('/', '-')) + ext, path, operationIds, example, note.toString()));
```
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #27960:
URL: https://github.com/apache/camel/pull/27960#discussion_r4297220404
##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java:
##########
@@ -407,6 +307,107 @@ private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
return stubs;
}
+ /**
+ * One mock file per path of the spec. For an operation without a route, camel-rest-openapi serves the first
+ * resource of mockIncludePattern (default classpath:camel-mock/**) whose location without extension ends with the
+ * request path, whatever the method: a path parameter is matched by its value, so {@code /pets/{petId}} takes one
+ * file per value, such as {@code camel-mock/pets/1.json}.
+ */
+ private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+ List<MockFileInfo> files = new ArrayList<>();
+ if (openAPI.getPaths() == null) {
+ return files;
+ }
+ for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet()) {
+ String path = pathEntry.getKey();
+ Map<PathItem.HttpMethod, Operation> operations = pathEntry.getValue().readOperationsMap();
+ if (operations == null || operations.isEmpty()) {
+ continue;
+ }
+
+ List<String> operationIds = new ArrayList<>();
+ Map<String, String> withoutFile = new LinkedHashMap<>();
+ String responseType = null;
+ String example = null;
+ for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : operations.entrySet()) {
+ String method = opEntry.getKey().name();
+ Operation op = opEntry.getValue();
+ operationIds.add(operationId(path, opEntry.getKey(), op));
+
+ if (op.getResponses() != null) {
+ Map.Entry<String, ApiResponse> success = findFirstSuccessResponse(op.getResponses());
+ if (responseType == null && success != null && success.getValue().getContent() != null
+ && !success.getValue().getContent().isEmpty()) {
+ responseType = success.getValue().getContent().keySet().iterator().next();
+ }
+ }
+ if (BODY_VERBS.contains(method)) {
+ withoutFile.put(method, "echoes the request body with HTTP 200");
+ continue;
+ }
+ ApiResponse ok = op.getResponses() != null ? op.getResponses().get("200") : null;
+ String opExample = firstExample(ok);
+ if (opExample != null) {
+ withoutFile.put(method, "answers the example of its '200' response with HTTP 200");
+ if (example == null) {
+ example = opExample;
+ }
+ } else if (ok != null || op.getResponses() == null) {
+ withoutFile.put(method, "answers HTTP 204 with an empty body");
+ } else {
+ // camel-rest-openapi reads the '200' response of the operation without checking it exists
+ withoutFile.put(method, "fails with an error, as the operation has no '200' response: add the file");
Review Comment:
🐛 **Stale advisory text — `loadMockData` no longer NPEs here**
CAMEL-25663 (merged 2026-10-15) fixed `DefaultRestOpenapiProcessorStrategy.loadMockData` to null-check the `200` response's content — an operation without a `200` response now returns HTTP 204 with an empty body instead of throwing a `NullPointerException`. The advisory text on this line ("fails with an error") describes the old pre-fix behavior and will mislead users who read it.
The `else if` branch on line 355 already covers `ok != null || responses == null` → HTTP 204, but the `else` branch (operations that *do* have responses, just none with key `"200"`) now also returns HTTP 204 post-fix. Both branches should say the same thing.
```suggestion
withoutFile.put(method, "answers HTTP 204 with an empty body");
```
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of camel-rest-openapi [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on PR #27960:
URL: https://github.com/apache/camel/pull/27960#issuecomment-6233844561
Both nits are in 4e4c2b0a4461, and the branch is rebased on main after #27959 (the upgrade guide conflict kept both sides, hence the force push): the per-path note is the short form pointing at `modeExplanation`, the `firstExample` javadoc is right, and the example now comes from the media type that names the file (or the extension follows the media type of the example), with a test. `OpenApiToolsTest` 22, `OpenApiScaffoldRestOpenApiTest` 2, mcp test package 484, all passing.
_Claude Code on behalf of Croway_
--
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: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org