This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feat/camel-tui in repository https://gitbox.apache.org/repos/asf/camel.git
commit 67ba8b739b48ac4950acc8fec864f7a471dcbb62 Author: Claus Ibsen <[email protected]> AuthorDate: Mon May 18 15:12:56 2026 +0200 TUI: add RestSpecDevConsole to expose OpenAPI spec content via dev-console New rest-spec dev console loads and returns the full content of OpenAPI specification files registered in RestRegistry (contract-first routes). Supports a FILTER option for pattern-matching on specificationUri. Both TEXT and JSON outputs; deduplicates specs shared across operations. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../org/apache/camel/dev-console/rest-spec.json | 15 +++ .../org/apache/camel/dev-console/rest-spec | 2 + .../org/apache/camel/dev-consoles.properties | 2 +- .../camel/impl/console/RestSpecDevConsole.java | 138 +++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/rest-spec.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/rest-spec.json new file mode 100644 index 000000000000..5a88465ffee8 --- /dev/null +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/rest-spec.json @@ -0,0 +1,15 @@ +{ + "console": { + "kind": "console", + "group": "camel", + "name": "rest-spec", + "title": "Rest Spec", + "description": "OpenAPI specification content for contract-first REST services", + "deprecated": false, + "javaType": "org.apache.camel.impl.console.RestSpecDevConsole", + "groupId": "org.apache.camel", + "artifactId": "camel-console", + "version": "4.21.0-SNAPSHOT" + } +} + diff --git a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/rest-spec b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/rest-spec new file mode 100644 index 000000000000..242bf4b9225a --- /dev/null +++ b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/rest-spec @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.impl.console.RestSpecDevConsole diff --git a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties index 7e237034a2d8..90006f59d203 100644 --- a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties +++ b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties @@ -1,5 +1,5 @@ # Generated by camel build tools - do NOT edit this file! -dev-consoles=bean blocked browse circuit-breaker consumer context debug endpoint errors eval-language event gc health inflight internal-tasks java-security jvm log memory message-history processor producer properties receive reload rest route route-controller route-dump route-group route-structure send service simple-language source startup-recorder system-properties thread top trace transformers type-converters variables +dev-consoles=bean blocked browse circuit-breaker consumer context debug endpoint errors eval-language event gc health inflight internal-tasks java-security jvm log memory message-history processor producer properties receive reload rest rest-spec route route-controller route-dump route-group route-structure send service simple-language source startup-recorder system-properties thread top trace transformers type-converters variables groupId=org.apache.camel artifactId=camel-console version=4.21.0-SNAPSHOT diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RestSpecDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RestSpecDevConsole.java new file mode 100644 index 000000000000..9e29fe2285b0 --- /dev/null +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RestSpecDevConsole.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.impl.console; + +import java.io.InputStream; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.spi.Resource; +import org.apache.camel.spi.RestRegistry; +import org.apache.camel.spi.annotations.DevConsole; +import org.apache.camel.support.PatternHelper; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.console.AbstractDevConsole; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; + +@DevConsole(name = "rest-spec", displayName = "Rest Spec", + description = "OpenAPI specification content for contract-first REST services") +public class RestSpecDevConsole extends AbstractDevConsole { + + /** + * Filters specifications matching the given URI pattern (e.g. {@code *.yaml}, {@code petstore*}) + */ + public static final String FILTER = "filter"; + + public RestSpecDevConsole() { + super("camel", "rest-spec", "Rest Spec", "OpenAPI specification content for contract-first REST services"); + } + + @Override + protected String doCallText(Map<String, Object> options) { + String filter = (String) options.get(FILTER); + StringBuilder sb = new StringBuilder(); + + for (SpecEntry entry : collectSpecs(filter)) { + if (!sb.isEmpty()) { + sb.append("\n"); + } + sb.append(String.format("Specification: %s", entry.uri())); + if (entry.routeId() != null) { + sb.append(String.format("%n Route Id: %s", entry.routeId())); + } + if (entry.content() != null) { + sb.append("\n"); + sb.append(entry.content()); + } else { + sb.append(String.format("%n (content not available)")); + } + sb.append("\n"); + } + + return sb.toString(); + } + + @Override + protected Map<String, Object> doCallJson(Map<String, Object> options) { + String filter = (String) options.get(FILTER); + JsonObject root = new JsonObject(); + JsonArray list = new JsonArray(); + root.put("specs", list); + + for (SpecEntry entry : collectSpecs(filter)) { + JsonObject jo = new JsonObject(); + jo.put("specificationUri", entry.uri()); + if (entry.routeId() != null) { + jo.put("routeId", entry.routeId()); + } + if (entry.content() != null) { + jo.put("content", entry.content()); + } + list.add(jo); + } + + return root; + } + + private Set<SpecEntry> collectSpecs(String filter) { + Set<SpecEntry> result = new LinkedHashSet<>(); + RestRegistry rr = PluginHelper.getRestRegistry(getCamelContext()); + if (rr == null) { + return result; + } + + // track which URIs we've already loaded to avoid duplicates (many operations share one spec) + Set<String> seen = new LinkedHashSet<>(); + + for (RestRegistry.RestService rs : rr.listAllRestServices()) { + String specUri = rs.getSpecificationUri(); + if (specUri == null || seen.contains(specUri)) { + continue; + } + if (filter != null && !PatternHelper.matchPattern(specUri, filter)) { + continue; + } + seen.add(specUri); + + String content = loadContent(specUri); + result.add(new SpecEntry(specUri, rs.getRouteId(), content)); + } + + return result; + } + + private String loadContent(String specUri) { + try { + Resource resource = PluginHelper.getResourceLoader(getCamelContext()).resolveResource(specUri); + if (resource != null && resource.exists()) { + InputStream is = resource.getInputStream(); + String data = IOHelper.loadText(is); + IOHelper.close(is); + return data; + } + } catch (Exception e) { + // ignore — content unavailable + } + return null; + } + + private record SpecEntry(String uri, String routeId, String content) { + } +}
