Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 42 additions & 85 deletions src/main/java/io/vertx/openapi/contract/OpenAPIContract.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,134 +12,91 @@

package io.vertx.openapi.contract;

import static io.vertx.core.Future.failedFuture;
import static io.vertx.openapi.contract.OpenAPIContractException.createInvalidContract;
import static io.vertx.openapi.impl.Utils.readYamlOrJson;
import static java.util.Collections.emptyMap;

import io.vertx.codegen.annotations.Nullable;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.json.JsonObject;
import io.vertx.json.schema.JsonSchema;
import io.vertx.json.schema.JsonSchemaValidationException;
import io.vertx.json.schema.SchemaRepository;
import io.vertx.openapi.contract.impl.OpenAPIContractImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@VertxGen
public interface OpenAPIContract {

/**
* Instantiates a new builder for an openapi-contract.
*
* @param vertx The vert.x instance
* @return A new builder.
*/
static OpenAPIContractBuilder builder(Vertx vertx) {
return new OpenAPIContractBuilder(vertx);
}

/**
* Resolves / dereferences the passed contract and creates an {@link OpenAPIContract} instance.
*
* @param vertx The related Vert.x instance.
* @param unresolvedContractPath The path to the unresolved contract.
* @param vertx The related Vert.x instance.
* @param contractPath The path to the contract.
* @return A succeeded {@link Future} holding an {@link OpenAPIContract} instance, otherwise a failed {@link Future}.
*/
static Future<OpenAPIContract> from(Vertx vertx, String unresolvedContractPath) {
return readYamlOrJson(vertx, unresolvedContractPath).compose(json -> from(vertx, json));
static Future<OpenAPIContract> from(Vertx vertx, String contractPath) {
return builder(vertx).setContractPath(contractPath).build();
}

/**
* Resolves / dereferences the passed contract and creates an {@link OpenAPIContract} instance.
*
* @param vertx The related Vert.x instance.
* @param unresolvedContract The unresolved contract.
* @param vertx The related Vert.x instance.
* @param contract The contract.
* @return A succeeded {@link Future} holding an {@link OpenAPIContract} instance, otherwise a failed {@link Future}.
*/
static Future<OpenAPIContract> from(Vertx vertx, JsonObject unresolvedContract) {
return from(vertx, unresolvedContract, emptyMap());
static Future<OpenAPIContract> from(Vertx vertx, JsonObject contract) {
return builder(vertx)
.setContract(contract)
.build();
}

/**
* Resolves / dereferences the passed contract and creates an {@link OpenAPIContract} instance.
* <p>
* This method can be used in case that the contract is split into several files. These files can be passed in a
* Map that has the reference as key and the path to the file as value.
* This method can be used in case that the contract is split into several parts. These parts can be passed in a
* Map that has the reference as key and the path to the part as value.
*
* @param vertx The related Vert.x instance.
* @param unresolvedContractPath The path to the unresolved contract.
* @param additionalContractFiles The additional contract files
* @param vertx The related Vert.x instance.
* @param contractPath The path to the contract.
* @param additionalContractPartPaths The additional contract part paths
* @return A succeeded {@link Future} holding an {@link OpenAPIContract} instance, otherwise a failed {@link Future}.
*/
static Future<OpenAPIContract> from(Vertx vertx, String unresolvedContractPath,
Map<String, String> additionalContractFiles) {
static Future<OpenAPIContract> from(Vertx vertx, String contractPath,
Map<String, String> additionalContractPartPaths) {

Map<String, Future<JsonObject>> jsonFilesFuture = new HashMap<>();
jsonFilesFuture.put(unresolvedContractPath, readYamlOrJson(vertx, unresolvedContractPath));
additionalContractFiles.forEach((key, value) -> jsonFilesFuture.put(key, readYamlOrJson(vertx, value)));

return Future.all(new ArrayList<>(jsonFilesFuture.values())).compose(compFut -> {
Map<String, JsonObject> resolvedFiles = new HashMap<>();
additionalContractFiles.keySet().forEach(key -> resolvedFiles.put(key, jsonFilesFuture.get(key).result()));
return from(vertx, jsonFilesFuture.get(unresolvedContractPath).result(), resolvedFiles);
});
return builder(vertx)
.setContractPath(contractPath)
.setAdditionalContractPartPaths(additionalContractPartPaths)
.build();
}

/**
* Resolves / dereferences the passed contract and creates an {@link OpenAPIContract} instance.
* <p>
* This method can be used in case that the contract is split into several files. These files can be passed in a
* Map that has the reference as key and the path to the file as value.
* This method can be used in case that the contract is split into several parts. These parts can be passed in a
* Map that has the reference as key and the part as value.
*
* @param vertx The related Vert.x instance.
* @param unresolvedContract The unresolved contract.
* @param additionalContractFiles The additional contract files
* @param contract The unresolved contract.
* @param additionalContractParts The additional contract parts
* @return A succeeded {@link Future} holding an {@link OpenAPIContract} instance, otherwise a failed {@link Future}.
*/
static Future<OpenAPIContract> from(Vertx vertx, JsonObject unresolvedContract,
Map<String, JsonObject> additionalContractFiles) {
if (unresolvedContract == null) {
return failedFuture(createInvalidContract("Spec must not be null"));
}

OpenAPIVersion version = OpenAPIVersion.fromContract(unresolvedContract);
String baseUri = "app://";

ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext();
Promise<OpenAPIContract> promise = ctx.promise();

version.getRepository(vertx, baseUri)
.compose(repository -> {
List<Future<?>> validationFutures = new ArrayList<>(additionalContractFiles.size());
for (String ref : additionalContractFiles.keySet()) {
// Todo: As soon a more modern Java version is used the validate part could be extracted in a private static
// method and reused below.
JsonObject file = additionalContractFiles.get(ref);
Future<?> validationFuture = version.validateAdditionalContractFile(vertx, repository, file)
.compose(v -> vertx.executeBlocking(() -> repository.dereference(ref, JsonSchema.of(ref, file))));

validationFutures.add(validationFuture);
}
return Future.all(validationFutures).map(repository);
}).compose(repository -> version.validateContract(vertx, repository, unresolvedContract).compose(res -> {
try {
res.checkValidity();
return version.resolve(vertx, repository, unresolvedContract);
} catch (JsonSchemaValidationException | UnsupportedOperationException e) {
return failedFuture(createInvalidContract(null, e));
}
})
.map(resolvedSpec -> (OpenAPIContract) new OpenAPIContractImpl(resolvedSpec, version, repository)))
.recover(e -> {
// Convert any non-openapi exceptions into an OpenAPIContractException
if (e instanceof OpenAPIContractException) {
return failedFuture(e);
}

return failedFuture(
createInvalidContract("Found issue in specification for reference: " + e.getMessage(), e));
}).onComplete(promise);

return promise.future();
static Future<OpenAPIContract> from(Vertx vertx, JsonObject contract,
Map<String, JsonObject> additionalContractParts) {
return builder(vertx)
.setContract(contract)
.setAdditionalContractParts(additionalContractParts)
.build();

}

/**
Expand Down
Loading