All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zuunr.openapi.OpenApiMerger Maven / Gradle / Ivy

/*
 * Copyright 2020 Zuunr AB
 *
 * 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 com.zuunr.openapi;

import com.zuunr.forms.Form;
import com.zuunr.forms.FormFields;
import com.zuunr.forms.util.JsonValuePathsFinder;
import com.zuunr.json.*;
import com.zuunr.jsonschema.JsonSchemaMerger;

import java.util.Iterator;

/**
 * @author Niklas Eldberger
 */
public class OpenApiMerger {

    private final JsonSchemaMerger jsonSchemaMerger = JsonSchemaMerger.OPENAPI_3_0_STYLE;
    private final JsonValuePathsFinder pathsFinder = new JsonValuePathsFinder();
    private final OpenApiSchemaConverter openApiSchemaConverter = new OpenApiSchemaConverter();
    private final OpenApiUtil openApiUtil = new OpenApiUtil();
    private final OpenApiUnionMaker unionMaker = new OpenApiUnionMaker();

    public JsonObject unionOf(JsonObject openApiDoc1, JsonObject openApiDoc2) {
        return unionMaker.unionOf(openApiDoc1, openApiDoc2);
    }

    private JsonArray openApiParameters(JsonValue parametersJsonSchema, JsonArray parameters) {

        JsonArrayBuilder builder = JsonArray.EMPTY.builder();
        for (JsonValue parameterJsonValue : parameters) {
            JsonObject parameter = parameterJsonValue.getValue(JsonObject.class);
            String in = parameter.get("in").getString();
            String parameterName = parameter.get("name").getString();
            JsonValue parameterTypeSchema = parametersJsonSchema.get("properties").get(in);


            if (!parameterTypeSchema.is(Boolean.class) &&
                    !parameterTypeSchema.get(JsonArray.of("properties", parameterName), JsonValue.FALSE).is(Boolean.class)) {
                boolean required = parameterTypeSchema.get("required", JsonArray.EMPTY).getValue(JsonArray.class).contains(parameterName);

                builder.add(parameter
                        .put("schema", parameterTypeSchema.get("properties").get(parameterName))
                        .put("required", required));
            }
        }
        return builder.build();

    }

    public JsonObject getRequestBodySchemaPaths(JsonObject jsonSchemaDoc) {

        JsonArray pathsPattern = JsonArray.EMPTY
                .add("paths")
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("requestBody")
                .add("content")
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("schema");

        return getPathsAndValuesPerPath(pathsPattern, jsonSchemaDoc);
    }


    public JsonObject getPathsAndValuesPerPath(JsonArray pathsPattern, JsonObject jsonSchemaDoc) {
        JsonObjectBuilder schemaPerPath = JsonObject.EMPTY.builder();

        JsonArray paths = pathsFinder.getPaths(jsonSchemaDoc.jsonValue(), pathsPattern, true);

        for (JsonValue path : paths) {
            schemaPerPath.put(path.getValue(JsonArray.class).allButLast().asJson(), path.getValue(JsonArray.class));
        }
        return schemaPerPath.build();
    }


    public JsonObject getResponseBodySchemaPaths(JsonObject jsonSchemaDoc) {

        JsonObjectBuilder schemaPerPath = JsonObject.EMPTY.builder();
        JsonArray pathsPattern = JsonArray.EMPTY
                .add("paths")

                // path template
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                // method
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("responses")
                // status code
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("content")
                // content type
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("schema");

        JsonArray paths = pathsFinder.getPaths(jsonSchemaDoc.jsonValue(), pathsPattern, true);

        for (JsonValue path : paths) {
            schemaPerPath.put(path.getValue(JsonArray.class).allButLast().asJson(), path.getValue(JsonArray.class));
        }
        return schemaPerPath.build();
    }

    public JsonArray pathAndMethodPlusValue(JsonObject jsonSchemaDoc) {
        JsonArray pathsPattern = JsonArray.EMPTY
                .add("paths")

                // path template
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                // method
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()));

        JsonArray paths = pathsFinder.getPaths(jsonSchemaDoc.jsonValue(), pathsPattern, true);
        return paths;
    }

    public JsonObject pathAndMethodAndValuePerPathAndMethod(JsonObject jsonSchemaDoc) {
        JsonObject result = JsonObject.EMPTY;
        for (JsonValue jsonValue : pathAndMethodPlusValue(jsonSchemaDoc)) {
            result = result.put(jsonValue.getValue(JsonArray.class).subArray(0, 3).asJson(), jsonValue);
        }
        return result;
    }

    public JsonObject intersectionOf(JsonObject jsonSchemaDoc1, JsonObject jsonSchemaDoc2) {

        JsonObject result = jsonSchemaDoc1.put("paths", JsonObject.EMPTY); // defaults to empty
        JsonObject pathsObject1 = pathAndMethodAndValuePerPathAndMethod(jsonSchemaDoc1);
        JsonObject pathsObject2 = pathAndMethodAndValuePerPathAndMethod(jsonSchemaDoc2);

        Iterator valuesIter = pathsObject1.values().iterator();
        for (String path : pathsObject1.keys().asList(String.class)) {
            JsonArray pathAndValue2 = pathsObject2.get(path, JsonValue.NULL).getJsonArray();
            if (pathAndValue2 == null) {
                continue;
            }
            //JsonArray pathAndValue1old = valuesIter.next().getValue(JsonArray.class); // TODO: Check why order of iterator was wrong!

            JsonArray pathAndValue1 = pathsObject1.get(path).getJsonArray();
            JsonObject resultOfUpdate = updatePath(pathAndValue1, pathAndValue2, result);
            if (resultOfUpdate != null) {
                result = resultOfUpdate;
            }
        }
        return result;
    }

    private JsonObject updatePath(JsonArray pathAndValue1, JsonArray pathAndValue2, JsonObject resultSoFar) {

        String method = pathAndValue1.get(2).getString();

        JsonObject originalPerMethodSpec1 = pathAndValue1.last().getValue(JsonObject.class);
        JsonObject originalPerMethodSpec2 = pathAndValue2.last().getValue(JsonObject.class);

        JsonObject perMethodSpec = originalPerMethodSpec1
                .remove("parameters")
                .remove("requestBody")
                .remove("responses");

        //merge parameters

        JsonArray parameters1 = originalPerMethodSpec1.get("parameters", JsonArray.EMPTY).getValue(JsonArray.class);
        JsonArray parameters2 = originalPerMethodSpec2.get("parameters", JsonArray.EMPTY).getValue(JsonArray.class);

        if (!parameters1.isEmpty() && !parameters2.isEmpty()) {
            JsonObject parametersJsonSchema1 = openApiUtil.openApiParametersAsJsonObjectSchema(parameters1);
            JsonObject parametersJsonSchema2 = openApiUtil.openApiParametersAsJsonObjectSchema(parameters2);

            JsonValue mergedSchema = jsonSchemaMerger.intersectionOf(parametersJsonSchema1.jsonValue(), parametersJsonSchema2.jsonValue(), true);
            if (mergedSchema.is(Boolean.class) && JsonValue.FALSE.equals(mergedSchema)) {
                return null; // This method for this URL path is not possible to call
            } else {
                perMethodSpec = perMethodSpec.put("parameters", openApiParameters(mergedSchema, parameters1));
            }
        }


        // Request body

        if (mayHaveRequestBody(method)) {
            JsonArray requestBodyPathsAndSchemas = requestBodies(originalPerMethodSpec1, pathAndValue2.last().getValue(JsonObject.class));
            for (JsonValue pathAndSchemaJsonValue : requestBodyPathsAndSchemas) {
                perMethodSpec = perMethodSpec.put(pathAndSchemaJsonValue.getValue(JsonArray.class).allButLast(), pathAndSchemaJsonValue.getValue(JsonArray.class).last());
            }
            if (perMethodSpec.get("requestBody") == null) {
                return null; // This method for this URL path is not possible to call
            }
        }
        // Responses
        for (JsonValue pathAndSchemaJsonValue : responses(originalPerMethodSpec1, pathAndValue2.last().getValue(JsonObject.class))) {

            JsonArray pathToDescriptionOfStatusCode = pathAndSchemaJsonValue.getValue(JsonArray.class).subArray(0, 2).add("description");

            perMethodSpec = perMethodSpec
                    .put(pathAndSchemaJsonValue.getValue(JsonArray.class).allButLast(), pathAndSchemaJsonValue.getValue(JsonArray.class).last())
                    .put(pathToDescriptionOfStatusCode, originalPerMethodSpec1.get(pathToDescriptionOfStatusCode));

        }
        JsonObject defaultResponses = perMethodSpec.get(JsonArray.of("responses", "default"), JsonObject.EMPTY.put("description", "")).getValue(JsonObject.class);
        perMethodSpec = perMethodSpec.put(JsonArray.of("responses", "default"), defaultResponses);
        resultSoFar = resultSoFar.put(pathAndValue1.allButLast(), perMethodSpec);
        return resultSoFar;
    }

    private boolean mayHaveRequestBody(String method) {
        return method.equals("post") || method.equals("patch") || method.equals("put");
    }


    private JsonArray requestBodies(JsonObject perMethodSpec1, JsonObject perMethodSpec2) {
        JsonArray responsesBodyPathsPattern = JsonArray.EMPTY
                .add("requestBody")
                .add("content")
                // content type
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("schema");
        return methodSpecPathsAndSchemas(perMethodSpec1, perMethodSpec2, responsesBodyPathsPattern, true);
    }

    private JsonArray responses(JsonObject perMethodSpec1, JsonObject perMethodSpec2) {
        JsonArray responsesBodyPathsPattern = JsonArray.EMPTY
                .add("responses")
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("content")
                // content type
                .add(JsonObject.EMPTY
                        .put("objectFormat", Form.EMPTY.builder()
                                .exclusive(false)
                                .value(FormFields.EMPTY)
                                .build()))
                .add("schema");
        return methodSpecPathsAndSchemas(perMethodSpec1, perMethodSpec2, responsesBodyPathsPattern, false);
    }

    private JsonArray methodSpecPathsAndSchemas(JsonObject perMethodSpec1, JsonObject perMethodSpec2, JsonArray perMethodPathPattern, boolean simplifyAlwaysFailingSchema) {

        JsonArrayBuilder perMethodSpecPathAndValues = JsonArray.EMPTY.builder(); // ['responses', '200', 'content', 'application/json', 'schema', {jsonSchema}]


        JsonArray responsesSchemaPaths1 = pathsFinder.getPaths(perMethodSpec1.jsonValue(), perMethodPathPattern, true);
        JsonArray responsesSchemaPaths2 = pathsFinder.getPaths(perMethodSpec2.jsonValue(), perMethodPathPattern, true);

        JsonObjectBuilder perResponseContentPathBuilder2 = JsonObject.EMPTY.builder();
        for (JsonValue schemaPath2 : responsesSchemaPaths2) {
            perResponseContentPathBuilder2.put(schemaPath2.getValue(JsonArray.class).allButLast().asJson(), schemaPath2);
        }

        JsonObject perResponseContentPath2 = perResponseContentPathBuilder2.build();

        for (JsonValue schemaPath1 : responsesSchemaPaths1) {

            JsonArray responsesContentPath = schemaPath1.getValue(JsonArray.class).allButLast();
            JsonValue schemaPath2 = perResponseContentPath2.get(responsesContentPath.asJson());
            if (schemaPath2 == null) {
                continue;
            }

            JsonObject openApiSchema1 = schemaPath1.getValue(JsonArray.class).last().getValue(JsonObject.class);
            JsonObject openApiSchema2 = schemaPath2.getValue(JsonArray.class).last().getValue(JsonObject.class);

            JsonValue schema1 = openApiSchemaConverter.toJsonSchema(openApiSchema1);
            JsonValue schema2 = openApiSchemaConverter.toJsonSchema(openApiSchema2);

            JsonValue mergedJsonSchema = jsonSchemaMerger.intersectionOf(schema1, schema2, simplifyAlwaysFailingSchema);
            if (!JsonValue.FALSE.equals(mergedJsonSchema)) {
                perMethodSpecPathAndValues.add(responsesContentPath.add(mergedJsonSchema));
            }
        }
        return perMethodSpecPathAndValues.build();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy