davsclaus commented on code in PR #25390:
URL: https://github.com/apache/camel/pull/25390#discussion_r3730996752
##########
docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc:
##########
@@ -1385,6 +1385,43 @@ from("direct:events")
See the xref:next@components::duckdb-component.adoc[DuckDB component]
documentation for details.
Test support is provided by the `camel-test-infra-duckdb` module (in-process
embedded database).
Review Comment:
This section should be removed — the upgrade guide is for
migration-impacting changes only (changed defaults, removed/renamed options,
API changes, etc.), not for new component announcements. The component's own
`rest-postman-component.adoc` page is the right place for this documentation.
##########
components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanProcessor.java:
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.component.rest.postman;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.camel.AfterPropertiesConfigured;
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Consumer;
+import org.apache.camel.Exchange;
+import org.apache.camel.RouteAware;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumerAware;
+import org.apache.camel.component.rest.postman.model.PostmanKeyValue;
+import org.apache.camel.component.rest.postman.model.PostmanResponse;
+import org.apache.camel.component.rest.postman.support.PostmanRequestBinding;
+import org.apache.camel.http.base.HttpHelper;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.support.AsyncProcessorSupport;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+import org.apache.camel.support.processor.RestBindingAdvice;
+import org.apache.camel.support.processor.RestBindingAdviceFactory;
+import org.apache.camel.support.processor.RestBindingConfiguration;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * Routes incoming HTTP requests to the route that implements the matching
request of a Postman collection.
+ */
+public class RestPostmanProcessor extends AsyncProcessorSupport implements
CamelContextAware, AfterPropertiesConfigured {
+
+ private static final List<String> METHODS = Arrays.asList("GET", "HEAD",
"POST", "PUT", "DELETE", "PATCH");
+
+ private CamelContext camelContext;
+ private final List<PostmanRequestBinding> bindings;
+ private final JsonObject redactedDocument;
+ private final String collectionSource;
+ private final String basePath;
+ private final String apiContextPath;
+ private final boolean clientRequestValidation;
+ private final RestPostmanProcessorStrategy strategy;
+ private final
List<RestConsumerContextPathMatcher.ConsumerPath<PostmanRequestBinding>> paths
= new ArrayList<>();
+ private PlatformHttpConsumerAware platformHttpConsumer;
+ private Consumer consumer;
+ private RestRegistry restRegistry;
+
+ public RestPostmanProcessor(List<PostmanRequestBinding> bindings,
JsonObject redactedDocument,
+ String collectionSource, String basePath,
String apiContextPath,
+ boolean clientRequestValidation,
RestPostmanProcessorStrategy strategy) {
+ this.bindings = List.copyOf(bindings);
+ this.redactedDocument = redactedDocument;
+ this.collectionSource = collectionSource;
+ this.basePath = basePath;
+ this.apiContextPath
+ = apiContextPath != null && !apiContextPath.startsWith("/") ?
"/" + apiContextPath : apiContextPath;
+ this.clientRequestValidation = clientRequestValidation;
+ this.strategy = strategy;
+ }
+
+ @Override
+ public boolean process(Exchange exchange, AsyncCallback callback) {
+ String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH,
String.class);
+ if (path != null && path.startsWith(basePath)) {
+ path = path.substring(basePath.length());
+ }
+ String verb = exchange.getMessage().getHeader(Exchange.HTTP_METHOD,
String.class);
+
+ RestConsumerContextPathMatcher.ConsumerPath<PostmanRequestBinding>
match
+ = RestConsumerContextPathMatcher.matchBestPath(verb, path,
paths);
+ if (match instanceof RestPostmanConsumerPath rcp) {
+ PostmanRequestBinding binding = rcp.getConsumer();
+ String consumerPath = rcp.getConsumerPath();
+ if (consumerPath.startsWith("/") && path != null &&
!path.startsWith("/")) {
+ consumerPath = consumerPath.substring(1);
+ }
+
+ // turn the {name} markers of the matched template into message
headers
+ HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(),
path, consumerPath);
+
+ if (restRegistry != null) {
+ restRegistry.hit(verb, basePath, consumerPath);
+ }
+ return strategy.process(binding, rcp.getDispatchId(), verb, path,
rcp.getBinding(), exchange, callback);
+ }
+
+ if (path != null && path.equals(apiContextPath)) {
+ return strategy.processCollectionDocument(redactedDocument,
exchange, callback);
+ }
+
+ // neither a known request nor the api context path: distinguish "no
such path" from "wrong method"
+ final String contextPath = path;
+ List<String> allow = METHODS.stream()
+ .filter(v -> RestConsumerContextPathMatcher.matchBestPath(v,
contextPath, paths) != null).toList();
+ if (allow.isEmpty()) {
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 404);
+ } else {
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 405);
+ exchange.getMessage().setHeader("Allow", String.join(", ", allow));
+ }
+ exchange.setRouteStop(true);
+ callback.done(true);
+ return true;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ super.doInit();
+ CamelContextAware.trySetCamelContext(strategy, getCamelContext());
+ }
+
+ @Override
+ public void afterPropertiesConfigured(CamelContext camelContext) {
+ this.restRegistry = PluginHelper.getRestRegistry(camelContext);
+
+ String routeId = consumer instanceof RouteAware ra ?
ra.getRoute().getRouteId() : null;
+
+ for (PostmanRequestBinding binding : bindings) {
+ RestBindingConfiguration bc =
createRestBindingConfiguration(binding);
+
+ String url = basePath + binding.uriTemplate();
+ if (platformHttpConsumer != null) {
+ url =
platformHttpConsumer.getPlatformHttpConsumer().getEndpoint().getServiceUrl() +
url;
+ }
+ restRegistry.addRestService(consumer, true, url,
binding.uriTemplate(), basePath, null,
+ binding.method(), bc.getConsumes(), bc.getProduces(),
null, null, routeId,
+ binding.id(), collectionSource,
binding.item().getRequest().getDescription());
+
+ try {
+ RestBindingAdvice advice =
RestBindingAdviceFactory.build(camelContext, bc);
+ ServiceHelper.buildService(advice);
+ paths.add(new RestPostmanConsumerPath(
+ binding.method(), binding.uriTemplate(), binding,
advice,
+ strategy.resolveDispatchId(binding)));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (apiContextPath != null) {
+ restRegistry.addRestSpecification(consumer, true, basePath +
apiContextPath, apiContextPath, basePath,
+ "GET", "application/json", null);
+ }
+
+ for (var p : paths) {
+ if (p instanceof RestPostmanConsumerPath rcp) {
Review Comment:
Minor: these two `new RuntimeException(e)` wrappings (here and a few lines
below) should use `RuntimeCamelException.wrapRuntimeCamelException(e)` instead,
which is the Camel convention for wrapping checked exceptions.
##########
components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConfiguration.java:
##########
@@ -0,0 +1,424 @@
+/*
+ * 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.component.rest.postman;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriParams;
+import org.apache.camel.support.jsse.SSLContextParameters;
+
+/**
+ * Configuration shared by the {@code rest-postman} component and its
endpoints.
+ * <p>
+ * This class deliberately does not generate a {@code toString}, because it
holds the Postman API key.
+ */
+@Configurer(extended = true)
+@UriParams
+public class RestPostmanConfiguration implements Cloneable {
+
+ public static final String DEFAULT_COLLECTION_SOURCE =
"postman-collection.json";
+ public static final String DEFAULT_POSTMAN_API_URL =
"https://api.getpostman.com";
+ public static final String DEFAULT_POSTMAN_API_KEY_HEADER = "X-Api-Key";
+ public static final String DEFAULT_BASE_PATH = "/";
+
+ @UriParam(label = "common", enums = "auto,resource,cloud", defaultValue =
"auto")
+ @Metadata(description = "How to interpret collectionSource. With auto, a
bare collection UUID or"
+ + " {ownerId}-{uuid} is fetched from the Postman
cloud and anything else is resolved as a"
+ + " resource (classpath:, file:, http:). Use
resource or cloud to decide explicitly.",
+ defaultValue = "auto")
+ private String collectionSourceType = "auto";
+
+ @UriParam(label = "common")
+ @Metadata(description = "API basePath, for example \"`/v2`\". Default is
unset, if set overrides the value"
+ + " derived from the request URL in the
collection.")
+ private String basePath = "";
+
+ @UriParam(label = "common", prefix = "variable.", multiValue = true)
+ @Metadata(description = "Values for the {{variable}} placeholders used in
the collection. These override the"
+ + " variables declared by the collection and its
folders.")
+ private Map<String, Object> variables;
+
+ @UriParam(label = "common,advanced")
+ @Metadata(description = "Whether to fail if a {{variable}} placeholder
used by the selected request cannot be"
+ + " resolved. When false the placeholder is left
as-is.")
+ private boolean failOnUnresolvedVariable;
+
+ @UriParam(label = "producer")
+ @Metadata(description = "Scheme hostname and port to direct the HTTP
requests to in the form of"
+ + " `http[s]://hostname[:port]`. If set overrides
any value derived from the collection.")
+ private String host;
+
+ @UriParam(label = "producer,advanced")
+ @Metadata(description = "Name of the Camel component that will perform the
requests. The component must be"
+ + " present in Camel registry and it must
implement RestProducerFactory service provider"
+ + " interface. If not set CLASSPATH is searched
for single component that implements"
Review Comment:
The description for `consumerComponentName` references
`RestOpenApiConsumerFactory` by name. Since this is a Postman component (not
OpenAPI), consider describing the capability generically — e.g. "the component
must implement the REST consumer factory SPI" — rather than naming the
OpenAPI-specific interface, which may confuse users.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]