OpenAPIUtils.java
package net.morimekta.providence.jax.rs;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.callbacks.Callback;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.BinarySchema;
import io.swagger.v3.oas.models.media.BooleanSchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Encoding;
import io.swagger.v3.oas.models.media.FileSchema;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.NumberSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import net.morimekta.providence.jax.rs.schema.CompactObjectSchema;
import net.morimekta.providence.jax.rs.schema.SchemaWrapper;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.TreeMap;
public class OpenAPIUtils {
public static void setIncludeNonNullOnSchema(ObjectMapper objectMapper) {
objectMapper.addMixIn(Schema.class, SchemaWrapper.class);
objectMapper.addMixIn(ArraySchema.class, CompactObjectSchema.class);
setIncludeNonNull(objectMapper,
Schema.class,
ObjectSchema.class,
ArraySchema.class,
MapSchema.class,
BooleanSchema.class,
NumberSchema.class,
IntegerSchema.class,
StringSchema.class,
FileSchema.class,
BinarySchema.class,
// And core classes
OpenAPI.class,
Encoding.class,
Server.class,
Info.class,
Components.class,
Paths.class,
PathItem.class,
Operation.class,
Content.class,
MediaType.class,
Schema.class,
Parameter.class,
Contact.class,
ApiResponse.class,
Callback.class,
RequestBody.class,
Header.class);
}
private static void setIncludeNonNull(ObjectMapper objectMapper, Class<?>... types) {
JsonInclude.Value includeNonNull = new JsonInclude.Value(SchemaWrapper.class.getAnnotation(JsonInclude.class));
for (Class<?> type : types) {
objectMapper.configOverride(type).setInclude(includeNonNull);
}
}
private static final ObjectMapper JSON = new ObjectMapper();
private static final ObjectMapper YAML;
static {
setIncludeNonNullOnSchema(JSON);
ObjectMapper yaml;
try {
com.fasterxml.jackson.dataformat.yaml.YAMLFactory yamlFactory = new com.fasterxml.jackson.dataformat.yaml.YAMLFactory();
yamlFactory.configure(com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES, true);
yamlFactory.configure(com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER, false);
yamlFactory.configure(com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID, false);
yamlFactory.configure(com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.USE_NATIVE_TYPE_ID, false);
yaml = new ObjectMapper(yamlFactory);
setIncludeNonNullOnSchema(yaml);
} catch (Error e) {
yaml = JSON;
}
YAML = yaml;
}
@Nonnull
public static OpenAPI parseOpenAPIYaml(String yaml) {
try {
return YAML.readValue(yaml, OpenAPI.class);
} catch (JsonProcessingException e) {
throw new AssertionError(e.getMessage(), e);
}
}
@Nonnull
public static OpenAPI parseOpenAPIJson(String json) {
try {
return JSON.readValue(json, OpenAPI.class);
} catch (JsonProcessingException e) {
throw new AssertionError(e.getMessage(), e);
}
}
/**
* Normalize openAPI definition by sorting main known maps. This should make
* the resulting definition consequentially ordered and thus testable.
*
* @param openAPI The OpenAPI definition to normalize.
* @return The normalized definition. Same instance and passed.
*/
@Nonnull
public static OpenAPI normalizeOpenAPI(@Nonnull OpenAPI openAPI) {
Map<String, PathItem> paths = new TreeMap<>(openAPI.getPaths());
openAPI.getPaths().clear();
openAPI.getPaths().putAll(paths);
Components components = openAPI.getComponents();
if (components.getSchemas() != null) {
components.setSchemas(new TreeMap<>(components.getSchemas()));
for (Schema<?> schema : components.getSchemas().values()) {
if (schema.getProperties() != null) {
schema.setProperties(new TreeMap<>(schema.getProperties()));
}
}
}
if (components.getParameters() != null) {
components.setParameters(new TreeMap<>(components.getParameters()));
}
if (components.getResponses() != null) {
components.setResponses(new TreeMap<>(components.getResponses()));
}
if (components.getCallbacks() != null) {
components.setCallbacks(new TreeMap<>(components.getCallbacks()));
}
if (components.getRequestBodies() != null) {
components.setRequestBodies(new TreeMap<>(components.getRequestBodies()));
}
if (components.getExtensions() != null) {
components.setExtensions(new TreeMap<>(components.getExtensions()));
}
if (components.getHeaders() != null) {
components.setHeaders(new TreeMap<>(components.getHeaders()));
}
if (components.getLinks() != null) {
components.setLinks(new TreeMap<>(components.getLinks()));
}
if (components.getSecuritySchemes() != null) {
components.setSecuritySchemes(new TreeMap<>(components.getSecuritySchemes()));
}
return openAPI;
}
@Nonnull
public static String toYaml(OpenAPI openAPI) {
try {
return YAML.writerWithDefaultPrettyPrinter().writeValueAsString(openAPI);
} catch (JsonProcessingException e) {
throw new AssertionError(e.getMessage(), e);
}
}
@Nonnull
public static String toJson(OpenAPI openAPI) {
try {
return JSON.writerWithDefaultPrettyPrinter().writeValueAsString(openAPI);
} catch (JsonProcessingException e) {
throw new AssertionError(e.getMessage(), e);
}
}
}