Skip to content

Commit

Permalink
feat(#1175): Add OpenApiRepository and Validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorsten Schlathoelter committed Jun 25, 2024
1 parent aeabf0e commit 4ea9f72
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
*/
public final class OpenApiResourceLoader {

static final RawResolver RAW_RESOLVER = new RawResolver();
private static final RawResolver RAW_RESOLVER = new RawResolver();

static final OasResolver OAS_RESOLVER = new OasResolver();
private static final OasResolver OAS_RESOLVER = new OasResolver();

/**
* Prevent instantiation of utility class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ public static String createValidationExpression(OasSchema schema, Map<String, Oa
* Create validation expression using functions according to schema type and format.
*/
private static String createValidationExpression(OasSchema schema) {

if (OasModelHelper.isCompositeSchema(schema)) {
/*
* Currently these schemas are not supported by validation expressions. They are supported
* by {@link org.citrusframework.openapi.validation.OpenApiValidator} though.
*/
return "@ignore@";
}

switch (schema.type) {
case "string":
if (schema.format != null && schema.format.equals("date")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
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.v3.models.Oas30Document;
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.Oas30Schema;
import jakarta.annotation.Nullable;
import org.citrusframework.openapi.model.v2.Oas20ModelHelper;
import org.citrusframework.openapi.model.v3.Oas30ModelHelper;
Expand Down Expand Up @@ -119,6 +121,10 @@ public static boolean isReferenceType(@Nullable OasSchema schema) {
return schema != null && schema.$ref != null;
}

public static boolean isCompositeSchema(OasSchema schema) {
return delegate(schema, Oas20ModelHelper::isCompositeSchema, Oas30ModelHelper::isCompositeSchema);
}

public static String getHost(OasDocument openApiDoc) {
return delegate(openApiDoc, Oas20ModelHelper::getHost, Oas30ModelHelper::getHost);
}
Expand Down Expand Up @@ -389,6 +395,24 @@ private static <T> T delegate(OasParameter parameter, Function<Oas20Parameter, T
throw new IllegalArgumentException(String.format("Unsupported operation parameter type: %s", parameter.getClass()));
}

/**
* Delegate method to version specific model helpers for Open API v2 or v3.
* @param schema
* @param oas20Function function to apply in case of v2
* @param oas30Function function to apply in case of v3
* @param <T> generic return value
* @return
*/
private static <T> T delegate(OasSchema schema, Function<Oas20Schema, T> oas20Function, Function<Oas30Schema, T> oas30Function) {
if (schema instanceof Oas20Schema oas20Schema) {
return oas20Function.apply(oas20Schema);
} else if (schema instanceof Oas30Schema oas30Schema) {
return oas30Function.apply(oas30Schema);
}

throw new IllegalArgumentException(String.format("Unsupported operation parameter type: %s", schema.getClass()));
}

/**
* Delegate method to version specific model helpers for Open API v2 or v3.
* @param operation
Expand Down Expand Up @@ -537,4 +561,5 @@ public static List<String> resolveAllTypes(@Nullable List<String> acceptedMediaT
return acceptedMediaTypes.stream()
.flatMap(types -> Arrays.stream(types.split(","))).map(String::trim).toList();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@
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.Oas20Schema.Oas20AllOfSchema;
import io.apicurio.datamodels.openapi.v2.models.Oas20SchemaDefinition;
import jakarta.annotation.Nullable;
import org.citrusframework.openapi.model.OasAdapter;
import org.citrusframework.openapi.model.OasModelHelper;

import java.util.Arrays;
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.OasAdapter;
import org.citrusframework.openapi.model.OasModelHelper;

/**
* @author Christoph Deppisch
Expand Down Expand Up @@ -90,6 +90,11 @@ public static Optional<OasAdapter<OasSchema, String>> getSchema(Oas20Operation o
return selectedSchema == null && selectedMediaType == null ? Optional.empty() : Optional.of(new OasAdapter<>(selectedSchema, selectedMediaType));
}

public static boolean isCompositeSchema(Oas20Schema schema) {
// Note that oneOf and anyOf is not supported by Oas20.
return schema instanceof Oas20AllOfSchema;
}

public static Optional<OasSchema> getRequestBodySchema(@Nullable Oas20Document ignoredOpenApiDoc, Oas20Operation operation) {
if (operation.parameters == null) {
return Optional.empty();
Expand Down Expand Up @@ -204,4 +209,5 @@ public static Optional<OasSchema> getParameterSchema(Oas20Parameter parameter) {

return Optional.of(schema);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ public static List<String> getSchemes(Oas30Document openApiDoc) {
.toList();
}

public static boolean isCompositeSchema(Oas30Schema schema) {
return schema.anyOf != null || schema.oneOf != null || schema.allOf != null;
}

public static String getBasePath(Oas30Document openApiDoc) {
if (openApiDoc.servers == null || openApiDoc.servers.isEmpty()) {
return "/";
Expand Down Expand Up @@ -248,4 +252,5 @@ private static String resolveUrl(Server server) {
public static Optional<OasSchema> getParameterSchema(Oas30Parameter parameter) {
return Optional.ofNullable((OasSchema) parameter.schema);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,55 @@

package org.citrusframework.openapi;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.apicurio.datamodels.openapi.models.OasSchema;
import org.citrusframework.openapi.model.OasModelHelper;
import org.citrusframework.spi.Resources;
import org.testng.Assert;
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertEquals;

import io.apicurio.datamodels.openapi.v2.models.Oas20Schema;
import io.apicurio.datamodels.openapi.v2.models.Oas20Schema.Oas20AllOfSchema;
import io.apicurio.datamodels.openapi.v3.models.Oas30Schema;
import java.util.HashMap;
import java.util.List;
import org.testng.annotations.Test;

import java.util.Map;
public class OpenApiTestDataGeneratorTest {

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
@Test
public void anyOfIsIgnoredForOas3() {

// TODO: Add more tests
public class OpenApiTestDataGeneratorTest {
Oas30Schema anyOfSchema = new Oas30Schema();
anyOfSchema.anyOf = List.of(new Oas30Schema(), new Oas30Schema());

assertEquals(OpenApiTestDataGenerator.createValidationExpression(
anyOfSchema, new HashMap<>(), true, mock()), "\"@ignore@\"");
}

@Test
public void allOfIsIgnoredForOas3() {

private final OpenApiSpecification pingSpec = OpenApiSpecification.from(
Resources.create("classpath:org/citrusframework/openapi/ping/ping-api.yaml"));
Oas30Schema allOfSchema = new Oas30Schema();
allOfSchema.allOf = List.of(new Oas30Schema(), new Oas30Schema());

assertEquals(OpenApiTestDataGenerator.createValidationExpression(
allOfSchema, new HashMap<>(), true, mock()), "\"@ignore@\"");
}

@Test
public void oneOfIsIgnoredForOas3() {

Oas30Schema oneOfSchema = new Oas30Schema();
oneOfSchema.oneOf = List.of(new Oas30Schema(), new Oas30Schema());

assertEquals(OpenApiTestDataGenerator.createValidationExpression(
oneOfSchema, new HashMap<>(), true, mock()), "\"@ignore@\"");
}

// TODO: fix this by introducing mature validation
@Test
public void failsToValidateAnyOf() throws JsonProcessingException {
public void allOfIsIgnoredForOas2() {

Map<String, OasSchema> schemaDefinitions = OasModelHelper.getSchemaDefinitions(
pingSpec.getOpenApiDoc(null));
assertNotNull(schemaDefinitions);
assertFalse(schemaDefinitions.isEmpty());
Assert.assertEquals(schemaDefinitions.size(), 15);
Oas20AllOfSchema allOfSchema = new Oas20AllOfSchema();
allOfSchema.allOf = List.of(new Oas20Schema(), new Oas20Schema());

Assert.assertThrows(() -> OpenApiTestDataGenerator.createValidationExpression(
schemaDefinitions.get("PingRespType"), schemaDefinitions, true, pingSpec));
assertEquals(OpenApiTestDataGenerator.createValidationExpression(
allOfSchema, new HashMap<>(), true, mock()), "\"@ignore@\"");
}
}

0 comments on commit 4ea9f72

Please sign in to comment.