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]

Reply via email to