oas30Function) {
- if (operation instanceof Oas20Operation) {
- return oas20Function.apply((Oas20Operation) operation);
- } else if (operation instanceof Oas30Operation) {
- return oas30Function.apply((Oas30Operation) operation);
+ if (operation instanceof Oas20Operation oas20Operation) {
+ return oas20Function.apply(oas20Operation);
+ } else if (operation instanceof Oas30Operation oas30Operation) {
+ return oas30Function.apply(oas30Operation);
}
throw new IllegalArgumentException(String.format("Unsupported operation type: %s", operation.getClass()));
@@ -262,4 +344,32 @@ private static boolean isOas30(OasDocument openApiDoc) {
private static boolean isOas20(OasDocument openApiDoc) {
return OpenApiVersion.fromDocumentType(openApiDoc).equals(OpenApiVersion.V2);
}
+
+ /**
+ * Resolves all responses in the given {@link OasResponses} instance using the provided {@code responseResolver} function.
+ *
+ * This method iterates over the responses contained in the {@link OasResponses} object. If a response has a reference
+ * (indicated by a non-null {@code $ref} field), the reference is resolved using the {@code responseResolver} function. Other responses
+ * will be added to the result list as is.
+ *
+ * @param responses the {@link OasResponses} instance containing the responses to be resolved.
+ * @param responseResolver a {@link Function} that takes a reference string and returns the corresponding {@link OasResponse}.
+ * @return a {@link List} of {@link OasResponse} instances, where all references have been resolved.
+ */
+ public static List resolveResponses(OasResponses responses, Function responseResolver) {
+
+ List responseList = new ArrayList<>();
+ for (OasResponse response : responses.getResponses()) {
+ if (response.$ref != null) {
+ OasResponse resolved = responseResolver.apply(getReferenceName(response.$ref));
+ if (resolved != null) {
+ responseList.add(resolved);
+ }
+ } else {
+ responseList.add(response);
+ }
+ }
+
+ return responseList;
+ }
}
diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v2/Oas20ModelHelper.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v2/Oas20ModelHelper.java
index 36d9596fd4..2ced7254cd 100644
--- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v2/Oas20ModelHelper.java
+++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v2/Oas20ModelHelper.java
@@ -16,21 +16,25 @@
package org.citrusframework.openapi.model.v2;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
import io.apicurio.datamodels.openapi.models.OasHeader;
import io.apicurio.datamodels.openapi.models.OasParameter;
+import io.apicurio.datamodels.openapi.models.OasResponse;
import io.apicurio.datamodels.openapi.models.OasSchema;
import io.apicurio.datamodels.openapi.v2.models.Oas20Document;
import io.apicurio.datamodels.openapi.v2.models.Oas20Header;
import io.apicurio.datamodels.openapi.v2.models.Oas20Operation;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Parameter;
import io.apicurio.datamodels.openapi.v2.models.Oas20Response;
import io.apicurio.datamodels.openapi.v2.models.Oas20Schema;
import io.apicurio.datamodels.openapi.v2.models.Oas20SchemaDefinition;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.citrusframework.openapi.model.OasModelHelper;
+import org.springframework.http.MediaType;
/**
* @author Christoph Deppisch
@@ -89,14 +93,58 @@ public static Optional getRequestContentType(Oas20Operation operation) {
return Optional.empty();
}
- public static Optional getResponseContentType(Oas20Document openApiDoc, Oas20Operation operation) {
+ public static Collection getResponseTypes(Oas20Operation operation, Oas20Response response) {
+ if (operation == null) {
+ return Collections.emptyList();
+ }
+ return operation.produces;
+ }
+
+ /**
+ * Returns the response content for random response generation. Note that this implementation currently
+ * @param openApiDoc
+ * @param operation
+ * @return
+ */
+ public static Optional getResponseContentTypeForRandomGeneration(Oas20Document openApiDoc, Oas20Operation operation) {
if (operation.produces != null) {
- return Optional.of(operation.produces.get(0));
+ for (String mediaType : operation.produces) {
+ if (MediaType.APPLICATION_JSON_VALUE.equals(mediaType)) {
+ return Optional.of(mediaType);
+ }
+ }
}
return Optional.empty();
}
+ public static Optional getResponseForRandomGeneration(Oas20Document openApiDoc, Oas20Operation operation) {
+
+ if (operation.responses == null) {
+ return Optional.empty();
+ }
+
+ List responses = OasModelHelper.resolveResponses(operation.responses,
+ responseRef -> openApiDoc.responses.getResponse(OasModelHelper.getReferenceName(responseRef)));
+
+ // Pick the response object related to the first 2xx return code found
+ Optional response = responses.stream()
+ .filter(Oas20Response.class::isInstance)
+ .filter(r -> r.getStatusCode() != null && r.getStatusCode().startsWith("2"))
+ .map(OasResponse.class::cast)
+ .filter(res -> OasModelHelper.getSchema(res).isPresent())
+ .findFirst();
+
+ if (response.isEmpty()) {
+ // TODO: Although the Swagger specification states that at least one successful response should be specified in the responses,
+ // the Petstore API lacks this specification. As a result, we currently only return a successful response if one is found.
+ // If no successful response is specified, we return an empty response instead, to be backwards compatible.
+ response = Optional.empty();
+ }
+
+ return response;
+ }
+
public static Map getHeaders(Oas20Response response) {
if (response.headers == null) {
return Collections.emptyMap();
@@ -132,4 +180,36 @@ private static OasSchema getHeaderSchema(Oas20Header header) {
schema.exclusiveMinimum = header.exclusiveMinimum;
return schema;
}
+
+ public static Optional getParameterSchema(Oas20Parameter parameter) {
+ if (parameter.schema instanceof Oas20Schema oasSchema) {
+ return Optional.of(oasSchema);
+ }
+
+ Oas20Schema schema = new Oas20Schema();
+ schema.title = parameter.getName();
+ schema.type = parameter.type;
+ schema.format = parameter.format;
+ schema.items = parameter.items;
+ schema.multipleOf = parameter.multipleOf;
+
+ schema.default_ = parameter.default_;
+ schema.enum_ = parameter.enum_;
+
+ schema.pattern = parameter.pattern;
+ schema.description = parameter.description;
+ schema.uniqueItems = parameter.uniqueItems;
+
+ schema.maximum = parameter.maximum;
+ schema.maxItems = parameter.maxItems;
+ schema.maxLength = parameter.maxLength;
+ schema.exclusiveMaximum = parameter.exclusiveMaximum;
+
+ schema.minimum = parameter.minimum;
+ schema.minItems = parameter.minItems;
+ schema.minLength = parameter.minLength;
+ schema.exclusiveMinimum = parameter.exclusiveMinimum;
+
+ return Optional.of(schema);
+ }
}
diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v3/Oas30ModelHelper.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v3/Oas30ModelHelper.java
index 39bdd2486e..9f6492828d 100644
--- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v3/Oas30ModelHelper.java
+++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/model/v3/Oas30ModelHelper.java
@@ -16,17 +16,6 @@
package org.citrusframework.openapi.model.v3;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
import io.apicurio.datamodels.core.models.common.Server;
import io.apicurio.datamodels.core.models.common.ServerVariable;
import io.apicurio.datamodels.openapi.models.OasResponse;
@@ -34,11 +23,24 @@
import io.apicurio.datamodels.openapi.v3.models.Oas30Document;
import io.apicurio.datamodels.openapi.v3.models.Oas30MediaType;
import io.apicurio.datamodels.openapi.v3.models.Oas30Operation;
+import io.apicurio.datamodels.openapi.v3.models.Oas30Parameter;
import io.apicurio.datamodels.openapi.v3.models.Oas30RequestBody;
import io.apicurio.datamodels.openapi.v3.models.Oas30Response;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.citrusframework.openapi.model.OasModelHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
/**
* @author Christoph Deppisch
@@ -47,6 +49,7 @@ public final class Oas30ModelHelper {
/** Logger */
private static final Logger LOG = LoggerFactory.getLogger(Oas30ModelHelper.class);
+ public static final String NO_URL_ERROR_MESSAGE = "Unable to determine base path from server URL: %s";
private Oas30ModelHelper() {
// utility class
@@ -62,7 +65,7 @@ public static String getHost(Oas30Document openApiDoc) {
try {
return new URL(serverUrl).getHost();
} catch (MalformedURLException e) {
- throw new IllegalStateException(String.format("Unable to determine base path from server URL: %s", serverUrl));
+ throw new IllegalStateException(String.format(NO_URL_ERROR_MESSAGE, serverUrl));
}
}
@@ -80,12 +83,12 @@ public static List getSchemes(Oas30Document openApiDoc) {
try {
return new URL(serverUrl).getProtocol();
} catch (MalformedURLException e) {
- LOG.warn(String.format("Unable to determine base path from server URL: %s", serverUrl));
+ LOG.warn(String.format(NO_URL_ERROR_MESSAGE, serverUrl));
return null;
}
})
.filter(Objects::nonNull)
- .collect(Collectors.toList());
+ .toList();
}
public static String getBasePath(Oas30Document openApiDoc) {
@@ -101,7 +104,7 @@ public static String getBasePath(Oas30Document openApiDoc) {
try {
basePath = new URL(serverUrl).getPath();
} catch (MalformedURLException e) {
- throw new IllegalStateException(String.format("Unable to determine base path from server URL: %s", serverUrl));
+ throw new IllegalStateException(String.format(NO_URL_ERROR_MESSAGE, serverUrl));
}
} else {
basePath = serverUrl;
@@ -117,7 +120,7 @@ public static Map getSchemaDefinitions(Oas30Document openApiD
return openApiDoc.components.schemas.entrySet()
.stream()
- .collect(Collectors.toMap(Map.Entry::getKey, entry -> (OasSchema) entry.getValue()));
+ .collect(Collectors.toMap(Map.Entry::getKey, Entry::getValue));
}
public static Optional getSchema(Oas30Response response) {
@@ -172,44 +175,60 @@ public static Optional getRequestContentType(Oas30Operation operation) {
.findFirst();
}
- public static Optional getResponseContentType(Oas30Document openApiDoc, Oas30Operation operation) {
- if (operation.responses == null) {
- return Optional.empty();
+ public static Collection getResponseTypes(Oas30Operation operation, Oas30Response response) {
+ if (operation == null) {
+ return Collections.emptySet();
}
+ return response.content != null ? response.content.keySet() : Collections.emptyList();
+ }
- List responses = new ArrayList<>();
+ public static Optional getResponseContentTypeForRandomGeneration(Oas30Document openApiDoc, Oas30Operation operation) {
+ Optional responseForRandomGeneration = getResponseForRandomGeneration(
+ openApiDoc, operation);
+ return responseForRandomGeneration.map(
+ Oas30Response.class::cast).flatMap(res -> res.content.entrySet()
+ .stream()
+ .filter(entry -> MediaType.APPLICATION_JSON_VALUE.equals(entry.getKey()))
+ .filter(entry -> entry.getValue().schema != null)
+ .map(Map.Entry::getKey)
+ .findFirst());
+ }
- for (OasResponse response : operation.responses.getResponses()) {
- if (response.$ref != null) {
- responses.add(openApiDoc.components.responses.get(OasModelHelper.getReferenceName(response.$ref)));
- } else {
- responses.add(response);
- }
+ public static Optional getResponseForRandomGeneration(Oas30Document openApiDoc, Oas30Operation operation) {
+ if (operation.responses == null) {
+ return Optional.empty();
}
+ List responses = OasModelHelper.resolveResponses(operation.responses,
+ responseRef -> openApiDoc.components.responses.get(OasModelHelper.getReferenceName(responseRef)));
+
// Pick the response object related to the first 2xx return code found
- Optional response = responses.stream()
- .filter(Oas30Response.class::isInstance)
- .filter(r -> r.getStatusCode() != null && r.getStatusCode().startsWith("2"))
- .map(Oas30Response.class::cast)
- .filter(res -> Oas30ModelHelper.getSchema(res).isPresent())
- .findFirst();
+ Optional response = responses.stream()
+ .filter(Oas30Response.class::isInstance)
+ .filter(r -> r.getStatusCode() != null && r.getStatusCode().startsWith("2"))
+ .map(OasResponse.class::cast)
+ .filter(res -> OasModelHelper.getSchema(res).isPresent())
+ .findFirst();
// No 2xx response given so pick the first one no matter what status code
- if (!response.isPresent()) {
+ if (response.isEmpty()) {
+ // TODO: This behavior differs from OAS2 and is very likely a bug because it may result in returning error messages.
+ // According to the specification, there MUST be at least one response, which SHOULD be a successful response.
+ // If the response is not successful, we encounter an error case, which is likely not the intended behavior.
+ // The specification likely does not intend to define operations that always fail.
+ // For testing purposes, note that the difference between OAS2 and OAS3 is evident in the Petstore API.
+ // The Petstore API specifies successful response codes for OAS3 but lacks these definitions for OAS2.
+ // Therefore, while tests pass for OAS3, they fail for OAS2.
+ // I would suggest to return an empty response in case we fail to resolve a good response, as in Oas2.
+ // In case of absence of a response an OK response will be sent as default.
response = responses.stream()
- .filter(Oas30Response.class::isInstance)
- .map(Oas30Response.class::cast)
- .filter(res -> Oas30ModelHelper.getSchema(res).isPresent())
- .findFirst();
+ .filter(Oas30Response.class::isInstance)
+ .map(OasResponse.class::cast)
+ .filter(res -> OasModelHelper.getSchema(res).isPresent())
+ .findFirst();
}
- return response.flatMap(res -> res.content.entrySet()
- .stream()
- .filter(entry -> entry.getValue().schema != null)
- .map(Map.Entry::getKey)
- .findFirst());
-
+ return response;
}
public static Map getRequiredHeaders(Oas30Response response) {
@@ -254,4 +273,8 @@ private static String resolveUrl(Server server) {
return url;
}
+
+ public static Optional getParameterSchema(Oas30Parameter parameter) {
+ return Optional.ofNullable((OasSchema) parameter.schema);
+ }
}
diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java
new file mode 100644
index 0000000000..3c449ff5fe
--- /dev/null
+++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * Licensed 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.citrusframework.openapi;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import org.testng.annotations.Test;
+
+@Test
+public class OpenApiRepositoryTest {
+
+ public static final String ROOT = "/root";
+
+ public void initializeOpenApiRepository() {
+ OpenApiRepository openApiRepository = new OpenApiRepository();
+ openApiRepository.setRootContextPath(ROOT);
+ openApiRepository.setLocations(List.of("org/citrusframework/openapi/petstore/petstore**.json"));
+ openApiRepository.initialize();
+
+ List openApiSpecifications = openApiRepository.getOpenApiSpecifications();
+
+ assertEquals(openApiRepository.getRootContextPath(), ROOT);
+ assertNotNull(openApiSpecifications);
+ assertEquals(openApiSpecifications.size(),3);
+
+ assertEquals(openApiSpecifications.get(0).getRootContextPath(), ROOT);
+ assertEquals(openApiSpecifications.get(1).getRootContextPath(), ROOT);
+
+ assertTrue(SampleOpenApiProcessor.processedSpecifications.contains(openApiSpecifications.get(0)));
+ assertTrue(SampleOpenApiProcessor.processedSpecifications.contains(openApiSpecifications.get(1)));
+ assertTrue(SampleOpenApiProcessor.processedSpecifications.contains(openApiSpecifications.get(2)));
+ }
+}
diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/SampleOpenApiProcessor.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/SampleOpenApiProcessor.java
new file mode 100644
index 0000000000..c39605e35e
--- /dev/null
+++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/SampleOpenApiProcessor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * Licensed 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.citrusframework.openapi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SampleOpenApiProcessor implements OpenApiSpecificationProcessor {
+
+ public static List processedSpecifications = new ArrayList<>();
+
+ @Override
+ public void process(OpenApiSpecification openApiSpecification) {
+ processedSpecifications.add(openApiSpecification);
+ }
+}
diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v2/Oas20ModelHelperTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v2/Oas20ModelHelperTest.java
new file mode 100644
index 0000000000..742641ad6c
--- /dev/null
+++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v2/Oas20ModelHelperTest.java
@@ -0,0 +1,119 @@
+package org.citrusframework.openapi.model.v2;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import io.apicurio.datamodels.openapi.models.OasResponse;
+import io.apicurio.datamodels.openapi.models.OasSchema;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Document;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Items;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Operation;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Parameter;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Response;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Responses;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Schema;
+import java.util.List;
+import java.util.Optional;
+import org.testng.annotations.Test;
+
+public class Oas20ModelHelperTest {
+
+ @Test
+ public void shouldFindRandomResponse() {
+ Oas20Document document = new Oas20Document();
+ Oas20Operation operation = new Oas20Operation("GET");
+
+ operation.responses = new Oas20Responses();
+
+ Oas20Response nokResponse = new Oas20Response("403");
+ nokResponse.schema = new Oas20Schema();
+
+ Oas20Response okResponse = new Oas20Response("200");
+ okResponse.schema = new Oas20Schema();
+
+ operation.responses = new Oas20Responses();
+ operation.responses.addResponse("403", nokResponse);
+ operation.responses.addResponse("200", okResponse);
+
+ Optional responseForRandomGeneration = Oas20ModelHelper.getResponseForRandomGeneration(
+ document, operation);
+ assertTrue(responseForRandomGeneration.isPresent());
+ assertEquals(okResponse, responseForRandomGeneration.get());
+ }
+
+ @Test
+ public void shouldNotFindAnyResponse() {
+ Oas20Document document = new Oas20Document();
+ Oas20Operation operation = new Oas20Operation("GET");
+
+ operation.responses = new Oas20Responses();
+
+ Oas20Response nokResponse403 = new Oas20Response("403");
+ Oas20Response nokResponse407 = new Oas20Response("407");
+
+ operation.responses = new Oas20Responses();
+ operation.responses.addResponse("403", nokResponse403);
+ operation.responses.addResponse("407", nokResponse407);
+
+ Optional responseForRandomGeneration = Oas20ModelHelper.getResponseForRandomGeneration(
+ document, operation);
+ assertTrue(responseForRandomGeneration.isEmpty());
+ }
+
+ @Test
+ public void shouldFindParameterSchema() {
+ Oas20Parameter parameter = new Oas20Parameter();
+ parameter.schema = new Oas20Schema();
+
+ Optional parameterSchema = Oas20ModelHelper.getParameterSchema(parameter);
+ assertTrue(parameterSchema.isPresent());
+ assertEquals(parameter.schema, parameterSchema.get());
+ }
+
+ @Test
+ public void shouldFindSchemaFromParameter() {
+ Oas20Parameter parameter = new Oas20Parameter("testParameter");
+ parameter.type = "string";
+ parameter.format = "date-time";
+ parameter.items = new Oas20Items();
+ parameter.multipleOf = 2;
+ parameter.default_ = "defaultValue";
+ parameter.enum_ = List.of("value1", "value2");
+ parameter.pattern = "pattern";
+ parameter.description = "description";
+ parameter.uniqueItems = true;
+ parameter.maximum = 100.0;
+ parameter.maxItems = 10;
+ parameter.maxLength = 20;
+ parameter.exclusiveMaximum = true;
+ parameter.minimum = 0.0;
+ parameter.minItems = 1;
+ parameter.minLength = 5;
+ parameter.exclusiveMinimum = false;
+
+ Optional schemaOptional = Oas20ModelHelper.getParameterSchema(parameter);
+ assertTrue(schemaOptional.isPresent());
+
+ OasSchema parameterSchema = schemaOptional.get();
+ assertEquals(parameterSchema.title, "testParameter");
+ assertEquals(parameterSchema.type, "string");
+ assertEquals(parameterSchema.format, "date-time");
+ assertEquals(parameter.items, parameterSchema.items);
+ assertEquals(parameter.multipleOf, parameterSchema.multipleOf);
+ assertEquals(parameter.default_, parameterSchema.default_);
+ assertEquals(parameter.enum_, parameterSchema.enum_);
+ assertEquals(parameter.pattern, parameterSchema.pattern);
+ assertEquals(parameter.description, parameterSchema.description);
+ assertEquals(parameter.uniqueItems, parameterSchema.uniqueItems);
+ assertEquals(parameter.maximum, parameterSchema.maximum);
+ assertEquals(parameter.maxItems, parameterSchema.maxItems);
+ assertEquals(parameter.maxLength, parameterSchema.maxLength);
+ assertEquals(parameter.exclusiveMaximum, parameterSchema.exclusiveMaximum);
+ assertEquals(parameter.minimum, parameterSchema.minimum);
+ assertEquals(parameter.minItems, parameterSchema.minItems);
+ assertEquals(parameter.minLength, parameterSchema.minLength);
+ assertEquals(parameter.exclusiveMinimum, parameterSchema.exclusiveMinimum);
+
+ }
+
+}
diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v3/Oas30ModelHelperTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v3/Oas30ModelHelperTest.java
index 38d8b13fde..8735ecd083 100644
--- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v3/Oas30ModelHelperTest.java
+++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/model/v3/Oas30ModelHelperTest.java
@@ -1,13 +1,24 @@
package org.citrusframework.openapi.model.v3;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import io.apicurio.datamodels.openapi.models.OasResponse;
import io.apicurio.datamodels.openapi.models.OasSchema;
+import io.apicurio.datamodels.openapi.v3.models.Oas30Document;
import io.apicurio.datamodels.openapi.v3.models.Oas30Header;
+import io.apicurio.datamodels.openapi.v3.models.Oas30MediaType;
+import io.apicurio.datamodels.openapi.v3.models.Oas30Operation;
+import io.apicurio.datamodels.openapi.v3.models.Oas30Parameter;
import io.apicurio.datamodels.openapi.v3.models.Oas30Response;
+import io.apicurio.datamodels.openapi.v3.models.Oas30Responses;
import io.apicurio.datamodels.openapi.v3.models.Oas30Schema;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
+import java.util.Collection;
import java.util.Map;
+import java.util.Optional;
+import org.springframework.http.MediaType;
+import org.testng.annotations.Test;
public class Oas30ModelHelperTest {
@@ -15,40 +26,135 @@ public class Oas30ModelHelperTest {
public void shouldNotFindRequiredHeadersWithoutRequiredAttribute() {
var header = new Oas30Header("X-TEST");
header.schema = new Oas30Schema();
- header.required = null; // explicitely assigned because this is test case
+ header.required = null; // explicitly assigned because this is test case
var response = new Oas30Response("200");
response.headers.put(header.getName(), header);
Map result = Oas30ModelHelper.getRequiredHeaders(response);
- Assert.assertEquals(result.size(), 0);
+ assertEquals(result.size(), 0);
}
@Test
public void shouldFindRequiredHeaders() {
var header = new Oas30Header("X-TEST");
header.schema = new Oas30Schema();
- header.required = Boolean.TRUE; // explicitely assigned because this is test case
+ header.required = Boolean.TRUE; // explicitly assigned because this is test case
var response = new Oas30Response("200");
response.headers.put(header.getName(), header);
Map result = Oas30ModelHelper.getRequiredHeaders(response);
- Assert.assertEquals(result.size(), 1);
- Assert.assertSame(result.get(header.getName()), header.schema);
+ assertEquals(result.size(), 1);
+ assertSame(result.get(header.getName()), header.schema);
}
@Test
public void shouldNotFindOptionalHeaders() {
var header = new Oas30Header("X-TEST");
header.schema = new Oas30Schema();
- header.required = Boolean.FALSE; // explicitely assigned because this is test case
+ header.required = Boolean.FALSE; // explicitly assigned because this is test case
var response = new Oas30Response("200");
response.headers.put(header.getName(), header);
Map result = Oas30ModelHelper.getRequiredHeaders(response);
- Assert.assertEquals(result.size(), 0);
+ assertEquals(result.size(), 0);
+ }
+
+ @Test
+ public void shouldFindAllRequestTypesForOperation() {
+ Oas30Operation operation = new Oas30Operation("GET");
+ operation.responses = new Oas30Responses();
+
+ Oas30Response response = new Oas30Response("200");
+ response.content = Map.of(MediaType.APPLICATION_JSON_VALUE,
+ new Oas30MediaType(MediaType.APPLICATION_JSON_VALUE),
+ MediaType.APPLICATION_XML_VALUE, new Oas30MediaType(MediaType.APPLICATION_XML_VALUE));
+
+ operation.responses = new Oas30Responses();
+ operation.responses.addResponse("200", response);
+
+ Collection responseTypes = Oas30ModelHelper.getResponseTypes(operation, response);
+
+ assertTrue(responseTypes.contains(MediaType.APPLICATION_JSON_VALUE));
+ assertTrue(responseTypes.contains(MediaType.APPLICATION_XML_VALUE));
+
+ }
+
+ @Test
+ public void shouldFindRandomResponse() {
+ Oas30Document document = new Oas30Document();
+ Oas30Operation operation = new Oas30Operation("GET");
+
+ operation.responses = new Oas30Responses();
+
+ Oas30Response nokResponse = new Oas30Response("403");
+ Oas30MediaType plainTextMediaType = new Oas30MediaType(MediaType.TEXT_PLAIN_VALUE);
+ plainTextMediaType.schema = new Oas30Schema();
+ nokResponse.content = Map.of(MediaType.TEXT_PLAIN_VALUE, plainTextMediaType);
+
+ Oas30Response okResponse = new Oas30Response("200");
+ Oas30MediaType jsonMediaType = new Oas30MediaType(MediaType.APPLICATION_JSON_VALUE);
+ jsonMediaType.schema = new Oas30Schema();
+
+ Oas30MediaType xmlMediaType = new Oas30MediaType(MediaType.APPLICATION_XML_VALUE);
+ xmlMediaType.schema = new Oas30Schema();
+
+ okResponse.content = Map.of(MediaType.APPLICATION_JSON_VALUE, jsonMediaType,
+ MediaType.APPLICATION_XML_VALUE, xmlMediaType);
+
+ operation.responses = new Oas30Responses();
+ operation.responses.addResponse("403", nokResponse);
+ operation.responses.addResponse("200", okResponse);
+
+ Optional responseForRandomGeneration = Oas30ModelHelper.getResponseForRandomGeneration(
+ document, operation);
+ assertTrue(responseForRandomGeneration.isPresent());
+ assertEquals(okResponse, responseForRandomGeneration.get());
+ }
+
+ @Test
+ public void shouldFindAnyResponse() {
+ Oas30Document document = new Oas30Document();
+ Oas30Operation operation = new Oas30Operation("GET");
+
+ operation.responses = new Oas30Responses();
+
+ Oas30Response nokResponse403 = new Oas30Response("403");
+ Oas30MediaType plainTextMediaType = new Oas30MediaType(MediaType.TEXT_PLAIN_VALUE);
+ plainTextMediaType.schema = new Oas30Schema();
+ nokResponse403.content = Map.of(MediaType.TEXT_PLAIN_VALUE, plainTextMediaType);
+
+ Oas30Response nokResponse407 = new Oas30Response("407");
+ nokResponse407.content = Map.of(MediaType.TEXT_PLAIN_VALUE, plainTextMediaType);
+
+ operation.responses = new Oas30Responses();
+ operation.responses.addResponse("403", nokResponse403);
+ operation.responses.addResponse("407", nokResponse407);
+
+ Optional responseForRandomGeneration = Oas30ModelHelper.getResponseForRandomGeneration(
+ document, operation);
+ assertTrue(responseForRandomGeneration.isPresent());
+ assertEquals(nokResponse403, responseForRandomGeneration.get());
+ }
+
+ @Test
+ public void shouldFindParameterSchema() {
+ Oas30Parameter parameter = new Oas30Parameter();
+ parameter.schema = new Oas30Schema();
+
+ Optional parameterSchema = Oas30ModelHelper.getParameterSchema(parameter);
+ assertTrue(parameterSchema.isPresent());
+ assertEquals(parameter.schema, parameterSchema.get());
+ }
+
+ @Test
+ public void shouldNotFindParameterSchema() {
+ Oas30Parameter parameter = new Oas30Parameter();
+
+ Optional parameterSchema = Oas30ModelHelper.getParameterSchema(parameter);
+ assertTrue(parameterSchema.isEmpty());
}
}
\ No newline at end of file
diff --git a/connectors/citrus-openapi/src/test/resources/META-INF/citrus/openapi/processor/sampleOpenApiProcessor b/connectors/citrus-openapi/src/test/resources/META-INF/citrus/openapi/processor/sampleOpenApiProcessor
new file mode 100644
index 0000000000..1092e9da72
--- /dev/null
+++ b/connectors/citrus-openapi/src/test/resources/META-INF/citrus/openapi/processor/sampleOpenApiProcessor
@@ -0,0 +1,2 @@
+name=sampleOpenApiProcessor
+type=org.citrusframework.openapi.SampleOpenApiProcessor
diff --git a/core/citrus-base/src/main/java/org/citrusframework/repository/BaseRepository.java b/core/citrus-base/src/main/java/org/citrusframework/repository/BaseRepository.java
new file mode 100644
index 0000000000..eae38431bd
--- /dev/null
+++ b/core/citrus-base/src/main/java/org/citrusframework/repository/BaseRepository.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * Licensed 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.citrusframework.repository;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.citrusframework.common.InitializingPhase;
+import org.citrusframework.common.Named;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.ClasspathResourceResolver;
+import org.citrusframework.spi.Resource;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.util.FileUtils;
+import org.citrusframework.util.StringUtils;
+
+/**
+ * Base class for repositories providing common functionality for initializing and managing resources.
+ * Implementations must provide the logic for loading and adding resources to the repository.
+ */
+public abstract class BaseRepository implements Named, InitializingPhase {
+
+ private String name;
+
+ /** List of location patterns that will be translated to schema resources */
+ private List locations = new ArrayList<>();
+
+ protected BaseRepository(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void initialize() {
+ try {
+ ClasspathResourceResolver resourceResolver = new ClasspathResourceResolver();
+ for (String location : locations) {
+ Resource found = Resources.create(location);
+ if (found.exists()) {
+ addRepository(found);
+ } else {
+ Set findings;
+ if (StringUtils.hasText(FileUtils.getFileExtension(location))) {
+ String fileNamePattern = FileUtils.getFileName(location).replace(".", "\\.").replace("*", ".*");
+ String basePath = FileUtils.getBasePath(location);
+ findings = resourceResolver.getResources(basePath, fileNamePattern);
+ } else {
+ findings = resourceResolver.getResources(location);
+ }
+
+ for (Path resource : findings) {
+ addRepository(Resources.fromClasspath(resource.toString()));
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to initialize repository", e);
+ }
+ }
+
+ protected abstract void addRepository(Resource resource);
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the name.
+ * @return the name to get.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the locations.
+ * @return the locations to get.
+ */
+ public List getLocations() {
+ return locations;
+ }
+
+ /**
+ * Sets the locations.
+ * @param locations the locations to set
+ */
+ public void setLocations(List locations) {
+ this.locations = locations;
+ }
+
+}
diff --git a/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java b/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java
index b1f208a21b..d99932ea55 100644
--- a/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java
+++ b/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java
@@ -42,4 +42,25 @@ public static boolean hasText(String str) {
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
+
+ public static String appendSegmentToPath(String path, String segment) {
+
+ if (path == null) {
+ return segment;
+ }
+
+ if (segment == null) {
+ return path;
+ }
+
+ if (!path.endsWith("/")) {
+ path = path +"/";
+ }
+
+ if (segment.startsWith("/")) {
+ segment = segment.substring(1);
+ }
+
+ return path+segment;
+ }
}
diff --git a/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java b/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java
new file mode 100644
index 0000000000..e1b1bab770
--- /dev/null
+++ b/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * Licensed 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.citrusframework.util;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class StringUtilsTest {
+
+ @Test
+ public void appendSegmentToPath() {
+ Assert.assertEquals(StringUtils.appendSegmentToPath("s1","s2"), "s1/s2");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("s1/","s2"), "s1/s2");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("s1/","/s2"), "s1/s2");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("/s1","/s2"), "/s1/s2");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("/s1/","/s2"), "/s1/s2");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("/s1/","/s2/"), "/s1/s2/");
+ Assert.assertEquals(StringUtils.appendSegmentToPath("/s1/",null), "/s1/");
+ Assert.assertEquals(StringUtils.appendSegmentToPath(null,"/s2/"), "/s2/");
+ Assert.assertNull(StringUtils.appendSegmentToPath(null,null));
+ }
+}
diff --git a/validation/citrus-validation-json/src/main/java/org/citrusframework/json/JsonSchemaRepository.java b/validation/citrus-validation-json/src/main/java/org/citrusframework/json/JsonSchemaRepository.java
index 754bb63652..c3dfc33a37 100644
--- a/validation/citrus-validation-json/src/main/java/org/citrusframework/json/JsonSchemaRepository.java
+++ b/validation/citrus-validation-json/src/main/java/org/citrusframework/json/JsonSchemaRepository.java
@@ -16,21 +16,11 @@
package org.citrusframework.json;
-import java.io.IOException;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
-
-import org.citrusframework.common.InitializingPhase;
-import org.citrusframework.common.Named;
-import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.json.schema.SimpleJsonSchema;
-import org.citrusframework.spi.ClasspathResourceResolver;
+import org.citrusframework.repository.BaseRepository;
import org.citrusframework.spi.Resource;
-import org.citrusframework.spi.Resources;
-import org.citrusframework.util.FileUtils;
-import org.citrusframework.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,70 +28,35 @@
* Schema repository holding a set of json schema resources known in the test scope.
* @since 2.7.3
*/
-public class JsonSchemaRepository implements Named, InitializingPhase {
+public class JsonSchemaRepository extends BaseRepository {
- /** This repositories name in the Spring application context */
- private String name;
+ private static final String DEFAULT_NAME = "jsonSchemaRepository";
/** List of schema resources */
private List schemas = new ArrayList<>();
- /** List of location patterns that will be translated to schema resources */
- private List locations = new ArrayList<>();
/** Logger */
private static Logger logger = LoggerFactory.getLogger(JsonSchemaRepository.class);
- @Override
- public void setName(String name) {
- this.name = name;
+ public JsonSchemaRepository() {
+ super(DEFAULT_NAME);
}
- @Override
- public void initialize() {
- try {
- ClasspathResourceResolver resourceResolver = new ClasspathResourceResolver();
- for (String location : locations) {
- Resource found = Resources.create(location);
- if (found.exists()) {
- addSchemas(found);
- } else {
- Set findings;
- if (StringUtils.hasText(FileUtils.getFileExtension(location))) {
- String fileNamePattern = FileUtils.getFileName(location).replace(".", "\\.").replace("*", ".*");
- String basePath = FileUtils.getBasePath(location);
- findings = resourceResolver.getResources(basePath, fileNamePattern);
- } else {
- findings = resourceResolver.getResources(location);
- }
-
- for (Path resource : findings) {
- addSchemas(Resources.fromClasspath(resource.toString()));
- }
- }
- }
- } catch (IOException e) {
- throw new CitrusRuntimeException("Failed to initialize Json schema repository", e);
- }
- }
- private void addSchemas(Resource resource) {
+ protected void addRepository(Resource resource) {
if (resource.getLocation().endsWith(".json")) {
if (logger.isDebugEnabled()) {
- logger.debug("Loading json schema resource " + resource.getLocation());
+ logger.debug("Loading json schema resource '{}'", resource.getLocation());
}
SimpleJsonSchema simpleJsonSchema = new SimpleJsonSchema(resource);
simpleJsonSchema.initialize();
schemas.add(simpleJsonSchema);
} else {
- logger.warn("Skipped resource other than json schema for repository (" + resource.getLocation() + ")");
+ logger.warn("Skipped resource other than json schema for repository '{}'", resource.getLocation());
}
}
- public String getName() {
- return name;
- }
-
public List getSchemas() {
return schemas;
}
@@ -118,11 +73,7 @@ public static void setLog(Logger logger) {
JsonSchemaRepository.logger = logger;
}
- public List getLocations() {
- return locations;
- }
-
- public void setLocations(List locations) {
- this.locations = locations;
+ public void addSchema(SimpleJsonSchema simpleJsonSchema) {
+ schemas.add(simpleJsonSchema);
}
}
diff --git a/validation/citrus-validation-json/src/test/java/org/citrusframework/validation/json/schema/JsonSchemaValidationTest.java b/validation/citrus-validation-json/src/test/java/org/citrusframework/validation/json/schema/JsonSchemaValidationTest.java
index 6a7ae17db9..25e5b7307c 100644
--- a/validation/citrus-validation-json/src/test/java/org/citrusframework/validation/json/schema/JsonSchemaValidationTest.java
+++ b/validation/citrus-validation-json/src/test/java/org/citrusframework/validation/json/schema/JsonSchemaValidationTest.java
@@ -34,6 +34,7 @@
import org.citrusframework.validation.json.report.GraciousProcessingReport;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -58,12 +59,21 @@ public class JsonSchemaValidationTest {
private JsonSchemaValidation fixture;
+ private AutoCloseable mocks;
+
@BeforeMethod
void beforeMethodSetup() {
- MockitoAnnotations.openMocks(this);
+ mocks = MockitoAnnotations.openMocks(this);
fixture = new JsonSchemaValidation(jsonSchemaFilterMock);
}
+ @AfterMethod
+ void afterMethod() throws Exception {
+ if (mocks != null) {
+ mocks.close();
+ }
+ }
+
@Test
public void testValidJsonMessageSuccessfullyValidated() {
// Setup json schema repositories
@@ -108,6 +118,54 @@ public void testValidJsonMessageSuccessfullyValidated() {
assertEquals(0, report.getValidationMessages().size());
}
+ @Test
+ public void testValidJsonMessageSuccessfullyValidatedPetStore() {
+ // Setup json schema repositories
+ JsonSchemaRepository jsonSchemaRepository = new JsonSchemaRepository();
+ jsonSchemaRepository.setName("schemaRepository1");
+ Resource schemaResource = Resources.fromClasspath("org/citrusframework/validation/petstore-v3.json");
+ SimpleJsonSchema schema = new SimpleJsonSchema(schemaResource);
+ schema.initialize();
+ jsonSchemaRepository.getSchemas().add(schema);
+
+ // Add json schema repositories to a list
+ List schemaRepositories = Collections.singletonList(jsonSchemaRepository);
+
+ // Mock the filter behavior
+ when(jsonSchemaFilterMock.filter(schemaRepositories, validationContextMock, referenceResolverMock))
+ .thenReturn(Collections.singletonList(schema));
+
+ // Create the received message
+ Message receivedMessage = new DefaultMessage("""
+ [
+ {
+ "it": 0,
+ "category": {
+ "id": 0,
+ "name": "string"
+ },
+ "name": "doggie",
+ "photoUrls": [
+ "string"
+ ],
+ "tags": [
+ {}
+ ],
+ "status": "available"
+ }
+ ]""");
+
+
+ GraciousProcessingReport report = fixture.validate(
+ receivedMessage,
+ schemaRepositories,
+ validationContextMock,
+ referenceResolverMock);
+
+ assertTrue(report.isSuccess());
+ assertEquals(0, report.getValidationMessages().size());
+ }
+
@Test
public void testInvalidJsonMessageValidationIsNotSuccessful() {
// Setup json schema repositories
@@ -264,7 +322,7 @@ public void testJsonSchemaFilterIsCalled() {
@Test
public void testLookup() {
Map> validators = SchemaValidator.lookup();
- assertEquals(validators.size(), 1L);
+ assertEquals(1L, validators.size());
assertNotNull(validators.get("defaultJsonSchemaValidator"));
assertEquals(validators.get("defaultJsonSchemaValidator").getClass(), JsonSchemaValidation.class);
}
diff --git a/validation/citrus-validation-json/src/test/resources/org/citrusframework/validation/petstore-v3.json b/validation/citrus-validation-json/src/test/resources/org/citrusframework/validation/petstore-v3.json
new file mode 100644
index 0000000000..618854948f
--- /dev/null
+++ b/validation/citrus-validation-json/src/test/resources/org/citrusframework/validation/petstore-v3.json
@@ -0,0 +1,292 @@
+{
+ "openapi": "3.0.2",
+ "info": {
+ "title": "Swagger Petstore",
+ "version": "1.0.1",
+ "description": "This is a sample server Petstore server.",
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ }
+ },
+ "servers": [
+ {
+ "url": "http://localhost/petstore/v3"
+ }
+ ],
+ "paths": {
+ "/pet": {
+ "put": {
+ "requestBody": {
+ "description": "Pet object that needs to be added to the store",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "required": true
+ },
+ "tags": [
+ "pet"
+ ],
+ "responses": {
+ "204": {
+ "description": "No content"
+ },
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ },
+ "405": {
+ "description": "Validation exception"
+ }
+ },
+ "operationId": "updatePet",
+ "summary": "Update an existing pet",
+ "description": ""
+ },
+ "post": {
+ "requestBody": {
+ "description": "Pet object that needs to be added to the store",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "required": true
+ },
+ "tags": [
+ "pet"
+ ],
+ "responses": {
+ "201": {
+ "description": "Created"
+ },
+ "405": {
+ "description": "Invalid input"
+ }
+ },
+ "operationId": "addPet",
+ "summary": "Add a new pet to the store",
+ "description": ""
+ }
+ },
+ "/pet/{petId}": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "parameters": [
+ {
+ "name": "petId",
+ "description": "ID of pet to return",
+ "schema": {
+ "format": "int64",
+ "type": "integer"
+ },
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "verbose",
+ "description": "Output details",
+ "schema": {
+ "type": "boolean"
+ },
+ "in": "query",
+ "required": false
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "description": "successful operation"
+ },
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ }
+ },
+ "operationId": "getPetById",
+ "summary": "Find pet by ID",
+ "description": "Returns a single pet"
+ },
+ "delete": {
+ "tags": [
+ "pet"
+ ],
+ "parameters": [
+ {
+ "name": "api_key",
+ "schema": {
+ "type": "string"
+ },
+ "in": "header",
+ "required": false
+ },
+ {
+ "name": "petId",
+ "description": "Pet id to delete",
+ "schema": {
+ "format": "int64",
+ "type": "integer"
+ },
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "No content"
+ },
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ }
+ },
+ "operationId": "deletePet",
+ "summary": "Deletes a pet",
+ "description": ""
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Category": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "format": "int64",
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Category"
+ }
+ },
+ "Tag": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "format": "int64",
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Tag"
+ }
+ },
+ "Pet": {
+ "required": [
+ "category",
+ "name",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "format": "int64",
+ "type": "integer"
+ },
+ "category": {
+ "$ref": "#/components/schemas/Category"
+ },
+ "name": {
+ "type": "string",
+ "example": "doggie"
+ },
+ "photoUrls": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "xml": {
+ "name": "photoUrl",
+ "wrapped": true
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Tag"
+ },
+ "xml": {
+ "name": "tag",
+ "wrapped": true
+ }
+ },
+ "status": {
+ "description": "pet status in the store",
+ "enum": [
+ "available",
+ "pending",
+ "sold"
+ ],
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Pet"
+ }
+ },
+ "ApiResponse": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "format": "int32",
+ "type": "integer"
+ },
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "pet",
+ "description": "Everything about your Pets"
+ }
+ ]
+}
diff --git a/validation/citrus-validation-xml/src/main/java/org/citrusframework/xml/XsdSchemaRepository.java b/validation/citrus-validation-xml/src/main/java/org/citrusframework/xml/XsdSchemaRepository.java
index e696c6316b..7ca3052066 100644
--- a/validation/citrus-validation-xml/src/main/java/org/citrusframework/xml/XsdSchemaRepository.java
+++ b/validation/citrus-validation-xml/src/main/java/org/citrusframework/xml/XsdSchemaRepository.java
@@ -17,20 +17,14 @@
package org.citrusframework.xml;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
-
-import org.citrusframework.common.InitializingPhase;
-import org.citrusframework.common.Named;
import org.citrusframework.exceptions.CitrusRuntimeException;
-import org.citrusframework.spi.ClasspathResourceResolver;
+import org.citrusframework.repository.BaseRepository;
import org.citrusframework.spi.Resource;
import org.citrusframework.spi.Resources;
import org.citrusframework.util.FileUtils;
-import org.citrusframework.util.StringUtils;
import org.citrusframework.xml.schema.TargetNamespaceSchemaMappingStrategy;
import org.citrusframework.xml.schema.WsdlXsdSchema;
import org.citrusframework.xml.schema.XsdSchemaMappingStrategy;
@@ -48,22 +42,23 @@
* @author Christoph Deppisch
*/
@SuppressWarnings("unused")
-public class XsdSchemaRepository implements Named, InitializingPhase {
- /** The name of the repository */
- private String name = "schemaRepository";
+public class XsdSchemaRepository extends BaseRepository {
+
+ private static final String DEFAULT_NAME = "schemaRepository";
/** List of schema resources */
private List schemas = new ArrayList<>();
- /** List of location patterns that will be translated to schema resources */
- private List locations = new ArrayList<>();
-
/** Mapping strategy */
private XsdSchemaMappingStrategy schemaMappingStrategy = new TargetNamespaceSchemaMappingStrategy();
/** Logger */
private static final Logger logger = LoggerFactory.getLogger(XsdSchemaRepository.class);
+ public XsdSchemaRepository() {
+ super(DEFAULT_NAME);
+ }
+
/**
* Find the matching schema for document using given schema mapping strategy.
* @param doc the document instance to validate.
@@ -76,28 +71,8 @@ public boolean canValidate(Document doc) {
@Override
public void initialize() {
+ super.initialize();
try {
- ClasspathResourceResolver resourceResolver = new ClasspathResourceResolver();
- for (String location : locations) {
- Resource found = Resources.create(location);
- if (found.exists()) {
- addSchemas(found);
- } else {
- Set findings;
- if (StringUtils.hasText(FileUtils.getFileExtension(location))) {
- String fileNamePattern = FileUtils.getFileName(location).replace(".", "\\.").replace("*", ".*");
- String basePath = FileUtils.getBasePath(location);
- findings = resourceResolver.getResources(basePath, fileNamePattern);
- } else {
- findings = resourceResolver.getResources(location);
- }
-
- for (Path resource : findings) {
- addSchemas(Resources.fromClasspath(resource.toString()));
- }
- }
- }
-
// Add default Citrus message schemas if available on classpath
addCitrusSchema("citrus-http-message");
addCitrusSchema("citrus-mail-message");
@@ -105,7 +80,7 @@ public void initialize() {
addCitrusSchema("citrus-ssh-message");
addCitrusSchema("citrus-rmi-message");
addCitrusSchema("citrus-jmx-message");
- } catch (SAXException | ParserConfigurationException | IOException e) {
+ } catch (SAXException | ParserConfigurationException e) {
throw new CitrusRuntimeException("Failed to initialize Xsd schema repository", e);
}
}
@@ -114,26 +89,26 @@ public void initialize() {
* Adds Citrus message schema to repository if available on classpath.
* @param schemaName The name of the schema within the citrus schema package
*/
- protected void addCitrusSchema(String schemaName) throws IOException, SAXException, ParserConfigurationException {
+ protected void addCitrusSchema(String schemaName) throws SAXException, ParserConfigurationException {
Resource resource = Resources.fromClasspath("classpath:org/citrusframework/schema/" + schemaName + ".xsd");
if (resource.exists()) {
addXsdSchema(resource);
}
}
- private void addSchemas(Resource resource) {
+ protected void addRepository(Resource resource) {
if (resource.getLocation().endsWith(".xsd")) {
addXsdSchema(resource);
} else if (resource.getLocation().endsWith(".wsdl")) {
addWsdlSchema(resource);
} else {
- logger.warn("Skipped resource other than XSD schema for repository (" + resource.getLocation() + ")");
+ logger.warn("Skipped resource other than XSD schema for repository '{}'", resource.getLocation());
}
}
private void addWsdlSchema(Resource resource) {
if (logger.isDebugEnabled()) {
- logger.debug("Loading WSDL schema resource " + resource.getLocation());
+ logger.debug("Loading WSDL schema resource '{}'", resource.getLocation());
}
WsdlXsdSchema wsdl = new WsdlXsdSchema(resource);
@@ -143,7 +118,7 @@ private void addWsdlSchema(Resource resource) {
private void addXsdSchema(Resource resource) {
if (logger.isDebugEnabled()) {
- logger.debug("Loading XSD schema resource " + resource.getLocation());
+ logger.debug("Loading XSD schema resource '{}'", resource.getLocation());
}
SimpleXsdSchema schema = new SimpleXsdSchema(new ByteArrayResource(FileUtils.copyToByteArray(resource)));
@@ -186,33 +161,4 @@ public void setSchemaMappingStrategy(XsdSchemaMappingStrategy schemaMappingStrat
public XsdSchemaMappingStrategy getSchemaMappingStrategy() {
return schemaMappingStrategy;
}
-
- @Override
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * Gets the name.
- * @return the name to get.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Gets the locations.
- * @return the locations to get.
- */
- public List getLocations() {
- return locations;
- }
-
- /**
- * Sets the locations.
- * @param locations the locations to set
- */
- public void setLocations(List locations) {
- this.locations = locations;
- }
}