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

io.micronaut.openapi.visitor.SchemaUtils Maven / Gradle / Ivy

/*
 * Copyright 2017-2023 original 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
 *
 * https://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 io.micronaut.openapi.visitor;

import com.fasterxml.jackson.databind.JsonNode;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpMethod;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.SimpleSchema;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
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.SpecVersion;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.links.Link;
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.ByteArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.DateSchema;
import io.swagger.v3.oas.models.media.DateTimeSchema;
import io.swagger.v3.oas.models.media.EmailSchema;
import io.swagger.v3.oas.models.media.FileSchema;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.JsonSchema;
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.PasswordSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.UUIDSchema;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static io.micronaut.openapi.visitor.ContextUtils.warn;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NAME;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_PARSE_VALUE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_PROPERTIES;
import static io.micronaut.openapi.visitor.Utils.resolveComponents;
import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;

/**
 * Some schema util methods.
 *
 * @since 4.5.0
 */
@Internal
public final class SchemaUtils {

    public static final String COMPONENTS_CALLBACKS_PREFIX = "#/components/callbacks/";
    public static final String COMPONENTS_SCHEMAS_PREFIX = "#/components/schemas/";

    public static final String TYPE_NULL = "null";
    public static final String TYPE_OBJECT = "object";
    public static final String TYPE_ARRAY = "array";
    public static final String TYPE_STRING = "string";
    public static final String TYPE_INTEGER = "integer";
    public static final String TYPE_NUMBER = "number";
    public static final String TYPE_BOOLEAN = "boolean";

    public static final Schema EMPTY_SCHEMA = new Schema<>();
    public static final Schema EMPTY_ARRAY_SCHEMA = new ArraySchema();
    public static final Schema EMPTY_BINARY_SCHEMA = new BinarySchema();
    public static final Schema EMPTY_BOOLEAN_SCHEMA = new BooleanSchema();
    public static final Schema EMPTY_BYTE_ARRAY_SCHEMA = new ByteArraySchema();
    public static final Schema EMPTY_COMPOSED_SCHEMA = new ComposedSchema();
    public static final Schema EMPTY_DATE_SCHEMA = new DateSchema();
    public static final Schema EMPTY_DATE_TIME_SCHEMA = new DateTimeSchema();
    public static final Schema EMPTY_EMAIL_SCHEMA = new EmailSchema();
    public static final Schema EMPTY_FILE_SCHEMA = new FileSchema();
    public static final Schema EMPTY_INTEGER_SCHEMA = new IntegerSchema();
    public static final Schema EMPTY_JSON_SCHEMA = new JsonSchema();
    public static final Schema EMPTY_MAP_SCHEMA = new MapSchema();
    public static final Schema EMPTY_NUMBER_SCHEMA = new NumberSchema();
    public static final Schema EMPTY_OBJECT_SCHEMA = new ObjectSchema();
    public static final Schema EMPTY_PASSWORD_SCHEMA = new PasswordSchema();
    public static final Schema EMPTY_STRING_SCHEMA = new StringSchema();
    public static final Schema EMPTY_UUID_SCHEMA = new UUIDSchema();
    public static final Schema EMPTY_SIMPLE_SCHEMA = new SimpleSchema();

    private static final List> ALL_EMPTY_SCHEMAS = List.of(
        EMPTY_SCHEMA,
        EMPTY_ARRAY_SCHEMA,
        EMPTY_BINARY_SCHEMA,
        EMPTY_BOOLEAN_SCHEMA,
        EMPTY_BYTE_ARRAY_SCHEMA,
        EMPTY_COMPOSED_SCHEMA,
        EMPTY_DATE_SCHEMA,
        EMPTY_DATE_TIME_SCHEMA,
        EMPTY_EMAIL_SCHEMA,
        EMPTY_FILE_SCHEMA,
        EMPTY_INTEGER_SCHEMA,
        EMPTY_JSON_SCHEMA,
        EMPTY_MAP_SCHEMA,
        EMPTY_NUMBER_SCHEMA,
        EMPTY_OBJECT_SCHEMA,
        EMPTY_PASSWORD_SCHEMA,
        EMPTY_STRING_SCHEMA,
        EMPTY_UUID_SCHEMA,
        EMPTY_SIMPLE_SCHEMA
    );
    private static final String PREFIX_X = "x-";

    private SchemaUtils() {
    }

    public static boolean isEmptySchema(Schema schema) {
        return ALL_EMPTY_SCHEMAS.contains(schema);
    }

    // Copy of io.swagger.v3.core.util.AnnotationsUtils.getExtensions
    public static void processExtensions(Map map, AnnotationValue extension) {
        String extName = extension.stringValue(PROP_NAME).orElse(StringUtils.EMPTY_STRING);
        String decoratedName = prependIfMissing(extName, PREFIX_X);
        final String key = !extName.isEmpty() ? decoratedName : extName;
        for (var propAnn : extension.getAnnotations(PROP_PROPERTIES, ExtensionProperty.class)) {
            var propertyName = propAnn.getRequiredValue(PROP_NAME, String.class);
            var propertyValue = propAnn.getRequiredValue(String.class);
            JsonNode processedValue;
            final boolean propertyAsJson = propAnn.get(PROP_PARSE_VALUE, boolean.class, false);
            if (StringUtils.hasText(propertyName) && StringUtils.hasText(propertyValue)) {
                if (key.isEmpty()) {
                    decoratedName = prependIfMissing(propertyName, PREFIX_X);
                    if (propertyAsJson) {
                        try {
                            processedValue = Utils.getJsonMapper().readTree(propertyValue);
                            map.put(decoratedName, processedValue);
                        } catch (Exception e) {
                            map.put(decoratedName, propertyValue);
                        }
                    } else {
                        map.put(decoratedName, propertyValue);
                    }
                } else {
                    Object value = map.get(key);
                    if (!(value instanceof Map)) {
                        value = new LinkedHashMap<>();
                        map.put(key, value);
                    }
                    @SuppressWarnings("unchecked")
                    var mapValue = (Map) value;
                    if (propertyAsJson) {
                        try {
                            processedValue = Utils.getJsonMapper().readTree(propertyValue);
                            mapValue.put(propertyName, processedValue);
                        } catch (Exception e) {
                            mapValue.put(propertyName, propertyValue);
                        }
                    } else {
                        mapValue.put(propertyName, propertyValue);
                    }
                }
            }
        }
    }

    public static String prependIfMissing(final String str, final String prefix) {
        if (str == null || StringUtils.isEmpty(prefix) || str.startsWith(prefix)) {
            return str;
        }
        return prefix + str;
    }

    public static Map resolveSchemas(OpenAPI openAPI) {
        Components components = resolveComponents(openAPI);
        Map schemas = components.getSchemas();
        if (schemas == null) {
            schemas = new LinkedHashMap<>();
            components.setSchemas(schemas);
        }
        return schemas;
    }

    public static  T setSpecVersion(T schema) {
        schema.specVersion(Utils.isOpenapi31() ? SpecVersion.V31 : SpecVersion.V30);
        return schema;
    }

    public static ArraySchema arraySchema(Schema schema) {
        if (schema == null) {
            return null;
        }
        var arraySchema = new ArraySchema();
        setSpecVersion(arraySchema);
        arraySchema.items(schema);
        return arraySchema;
    }

    public static String schemaRef(String schemaName) {
        return COMPONENTS_SCHEMAS_REF + schemaName;
    }

    public static Operation getOperationOnPathItem(PathItem pathItem, HttpMethod httpMethod) {

        if (pathItem == null) {
            return null;
        }

        return switch (httpMethod) {
            case GET -> pathItem.getGet();
            case PUT -> pathItem.getPut();
            case POST -> pathItem.getPost();
            case DELETE -> pathItem.getDelete();
            case OPTIONS -> pathItem.getOptions();
            case HEAD -> pathItem.getHead();
            case PATCH -> pathItem.getPatch();
            case TRACE -> pathItem.getTrace();
            default -> null;
        };
    }

    public static void setOperationOnPathItem(PathItem pathItem, HttpMethod httpMethod, Operation operation) {
        if (pathItem == null) {
            return;
        }
        switch (httpMethod) {
            case GET -> pathItem.setGet(operation);
            case PUT -> pathItem.setPut(operation);
            case POST -> pathItem.setPost(operation);
            case DELETE -> pathItem.setDelete(operation);
            case OPTIONS -> pathItem.setOptions(operation);
            case HEAD -> pathItem.setHead(operation);
            case PATCH -> pathItem.setPatch(operation);
            case TRACE -> pathItem.setTrace(operation);
            default -> {
                // do nothing
            }
        }
    }

    public static Operation mergeOperations(Operation op1, Operation op2) {

        if (op1 == null) {
            return op2;
        }
        if (op2 == null) {
            return op1;
        }
        if (op1.equals(op2)) {
            return op1;
        }

        if (CollectionUtils.isEmpty(op1.getTags())) {
            op1.setTags(op2.getTags());
        } else if (CollectionUtils.isNotEmpty(op2.getTags())) {
            var tagsSet = new HashSet<>(op1.getTags());
            tagsSet.addAll(op2.getTags());
            var tags = new ArrayList<>(tagsSet);
            Collections.sort(tags);
            op1.setTags(tags);
        }

        if (StringUtils.isEmpty(op1.getSummary())) {
            op1.setSummary(op2.getSummary());
        }
        if (StringUtils.isEmpty(op1.getDescription())) {
            op1.setDescription(op2.getDescription());
        }
        if (op1.getExternalDocs() == null) {
            op1.setExternalDocs(op2.getExternalDocs());
        }
        if (op1.getDeprecated() == null) {
            op1.setDeprecated(op2.getDeprecated());
        }
        if (CollectionUtils.isEmpty(op1.getSecurity())) {
            op1.setSecurity(op2.getSecurity());
        } else if (CollectionUtils.isNotEmpty(op2.getSecurity())) {
            var securityRequirements = new HashSet<>(op1.getSecurity());
            securityRequirements.addAll(op2.getSecurity());
            op1.setSecurity(new ArrayList<>(securityRequirements));
        }
        if (CollectionUtils.isEmpty(op1.getExtensions())) {
            op1.setExtensions(op2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(op2.getExtensions())) {
            op2.getExtensions().putAll(op1.getExtensions());
            op1.setExtensions(op2.getExtensions());
        }
        if (CollectionUtils.isEmpty(op1.getCallbacks())) {
            op1.setCallbacks(op2.getCallbacks());
        } else if (CollectionUtils.isNotEmpty(op2.getCallbacks())) {
            op2.getCallbacks().putAll(op1.getCallbacks());
            op1.setCallbacks(op2.getCallbacks());
        }
        if (CollectionUtils.isEmpty(op1.getResponses())) {
            op1.setResponses(op2.getResponses());
        } else if (CollectionUtils.isNotEmpty(op2.getResponses())) {
            for (Map.Entry entry1 : op1.getResponses().entrySet()) {
                ApiResponse ar2 = op2.getResponses().get(entry1.getKey());
                entry1.setValue(mergeApiResponse(entry1.getValue(), ar2));
            }
            op2.getResponses().putAll(op1.getResponses());
            op1.setResponses(op2.getResponses());
        }

        if (CollectionUtils.isEmpty(op1.getServers())) {
            op1.setServers(op2.getServers());
        } else if (CollectionUtils.isNotEmpty(op2.getServers())) {
            var serversSet = new HashSet<>(op1.getServers());
            serversSet.addAll(op2.getServers());
            op1.setServers(new ArrayList<>(serversSet));
        }

        mergeRequestBody(op1.getRequestBody(), op2.getRequestBody());

        if (CollectionUtils.isEmpty(op1.getParameters())) {
            op1.setParameters(op2.getParameters());
        } else if (CollectionUtils.isNotEmpty(op2.getParameters())) {

            for (Parameter p2 : op2.getParameters()) {
                Parameter existedParameter = null;
                int i = 0;
                for (Parameter p1 : op1.getParameters()) {
                    if (Objects.equals(p1.getName(), p2.getName())
                        && Objects.equals(p1.getIn(), p2.getIn())) {
                        existedParameter = p1;
                        break;
                    }
                    i++;
                }
                if (existedParameter == null) {
                    op1.addParametersItem(p2);
                } else {
                    op1.getParameters().set(i, mergeParameter(existedParameter, p2));
                }
            }

            var serversSet = new HashSet<>(op1.getParameters());
            serversSet.addAll(op2.getParameters());
            op1.setParameters(new ArrayList<>(serversSet));
        }

        return op1;
    }

    public static ApiResponse mergeApiResponse(ApiResponse ar1, ApiResponse ar2) {

        if (ar1 == null) {
            return ar2;
        }
        if (ar2 == null) {
            return null;
        }
        if (ar1.equals(ar2)) {
            return ar1;
        }

        if (ar1.getDescription() == null) {
            ar1.setDescription(ar2.getDescription());
        }
        if (ar1.get$ref() == null) {
            ar1.set$ref(ar2.get$ref());
        }
        if (CollectionUtils.isEmpty(ar1.getHeaders())) {
            ar1.setHeaders(ar2.getHeaders());
        } else if (CollectionUtils.isNotEmpty(ar2.getHeaders())) {
            for (Map.Entry entry1 : ar1.getHeaders().entrySet()) {
                Header h2 = ar2.getHeaders().get(entry1.getKey());
                entry1.setValue(mergeHeader(entry1.getValue(), h2));
            }
            ar2.getHeaders().putAll(ar1.getHeaders());
            ar1.setHeaders(ar2.getHeaders());
        }

        if (CollectionUtils.isEmpty(ar1.getLinks())) {
            ar1.setLinks(ar2.getLinks());
        } else if (CollectionUtils.isNotEmpty(ar2.getLinks())) {
            for (Map.Entry entry1 : ar1.getLinks().entrySet()) {
                Link l2 = ar2.getLinks().get(entry1.getKey());
                entry1.setValue(mergeLink(entry1.getValue(), l2));
            }
            ar2.getLinks().putAll(ar1.getLinks());
            ar1.setLinks(ar2.getLinks());
        }

        if (CollectionUtils.isEmpty(ar1.getExtensions())) {
            ar1.setExtensions(ar2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(ar2.getExtensions())) {
            ar2.getExtensions().putAll(ar1.getExtensions());
            ar1.setExtensions(ar2.getExtensions());
        }
        ar1.setContent(mergeContent(ar1.getContent(), ar2.getContent()));

        return ar1;
    }

    public static Link mergeLink(Link l1, Link l2) {

        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return null;
        }
        if (l1.equals(l2)) {
            return l1;
        }

        if (l1.getDescription() == null) {
            l1.setDescription(l2.getDescription());
        }
        if (l1.getOperationRef() == null) {
            l1.setOperationRef(l2.getOperationRef());
        }
        if (l1.getOperationId() == null) {
            l1.setOperationId(l2.getOperationId());
        }
        if (l1.getRequestBody() == null) {
            l1.setRequestBody(l2.getRequestBody());
        }
        if (l1.get$ref() == null) {
            l1.set$ref(l2.get$ref());
        }
        if (CollectionUtils.isEmpty(l1.getParameters())) {
            l1.setParameters(l2.getParameters());
        } else if (CollectionUtils.isNotEmpty(l2.getParameters())) {
            l2.getParameters().putAll(l1.getParameters());
            l1.setParameters(l2.getParameters());
        }
        if (CollectionUtils.isEmpty(l1.getExtensions())) {
            l1.setExtensions(l2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(l2.getExtensions())) {
            l2.getExtensions().putAll(l1.getExtensions());
            l1.setExtensions(l2.getExtensions());
        }

        return l1;
    }

    public static Header mergeHeader(Header h1, Header h2) {

        if (h1 == null) {
            return h2;
        }
        if (h2 == null) {
            return null;
        }
        if (h1.equals(h2)) {
            return h1;
        }

        if (h1.getDescription() == null) {
            h1.setDescription(h2.getDescription());
        }
        if (h1.getRequired() == null) {
            h1.setRequired(h2.getRequired());
        }
        if (h1.getDeprecated() == null) {
            h1.setDeprecated(h2.getDeprecated());
        }
        if (h1.get$ref() == null) {
            h1.set$ref(h2.get$ref());
        }
        if (h1.getStyle() == null) {
            h1.setStyle(h2.getStyle());
        }
        if (h1.getExplode() == null) {
            h1.setExplode(h2.getExplode());
        }
        if (h1.getExample() == null) {
            h2.setExample(h1.getExample());
        }

        h1.setContent(mergeContent(h1.getContent(), h2.getContent()));
        if (CollectionUtils.isEmpty(h1.getExtensions())) {
            h1.setExtensions(h2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(h2.getExtensions())) {
            h2.getExtensions().putAll(h1.getExtensions());
            h1.setExtensions(h2.getExtensions());
        }
        h1.setSchema(mergeSchema(h1.getSchema(), h2.getSchema()));
        if (CollectionUtils.isEmpty(h1.getExamples())) {
            h1.setExamples(h2.getExamples());
        } else if (CollectionUtils.isNotEmpty(h2.getExamples())) {
            h1.getExamples().putAll(h2.getExamples());
        }

        return h1;
    }

    public static Parameter mergeParameter(Parameter p1, Parameter p2) {

        if (p1 == null) {
            return p2;
        }
        if (p2 == null) {
            return null;
        }
        if (p1.equals(p2)) {
            return p1;
        }

        if (p1.getDescription() == null) {
            p1.setDescription(p2.getDescription());
        }
        if (p1.getRequired() == null) {
            p1.setRequired(p2.getRequired());
        }
        if (p1.getDeprecated() == null) {
            p1.setDeprecated(p2.getDeprecated());
        }
        if (p1.getAllowEmptyValue() == null) {
            p1.setAllowEmptyValue(p2.getAllowEmptyValue());
        }
        if (p1.get$ref() == null) {
            p1.set$ref(p2.get$ref());
        }
        if (p1.getStyle() == null) {
            p1.setStyle(p2.getStyle());
        }
        if (p1.getExplode() == null) {
            p1.setExplode(p2.getExplode());
        }
        if (p1.getAllowReserved() == null) {
            p1.setAllowReserved(p2.getAllowReserved());
        }
        if (p1.getExample() == null) {
            p2.setExample(p1.getExample());
        }

        p1.setContent(mergeContent(p1.getContent(), p2.getContent()));
        if (CollectionUtils.isEmpty(p1.getExtensions())) {
            p1.setExtensions(p2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(p2.getExtensions())) {
            p2.getExtensions().putAll(p1.getExtensions());
            p1.setExtensions(p2.getExtensions());
        }
        p1.setSchema(mergeSchema(p1.getSchema(), p2.getSchema()));
        if (CollectionUtils.isEmpty(p1.getExamples())) {
            p1.setExamples(p2.getExamples());
        } else if (CollectionUtils.isNotEmpty(p2.getExamples())) {
            p1.getExamples().putAll(p2.getExamples());
        }

        return p1;
    }

    public static RequestBody mergeRequestBody(RequestBody rb1, RequestBody rb2) {

        if (rb1 == null) {
            return rb2;
        }
        if (rb2 == null) {
            return rb1;
        }
        if (rb1.equals(rb2)) {
            return rb1;
        }

        if (StringUtils.isEmpty(rb1.getDescription())) {
            rb1.setDescription(rb2.getDescription());
        }
        if (CollectionUtils.isEmpty(rb1.getExtensions())) {
            rb1.setExtensions(rb2.getExtensions());
        } else if (CollectionUtils.isNotEmpty(rb2.getExtensions())) {
            rb2.getExtensions().putAll(rb1.getExtensions());
            rb1.setExtensions(rb2.getExtensions());
        }
        if (rb1.getRequired() == null) {
            rb1.setRequired(rb2.getRequired());
        }
        if (rb1.get$ref() == null) {
            rb1.set$ref(rb2.get$ref());
        }
        rb1.setContent(mergeContent(rb1.getContent(), rb2.getContent()));

        return rb1;
    }

    public static Content mergeContent(Content c1, Content c2) {
        if (c1 == null) {
            return c2;
        }
        if (c2 == null) {
            return null;
        }
        if (c1.equals(c2)) {
            return c1;
        }
        c2.forEach(c1::putIfAbsent);
        for (Map.Entry entry : c1.entrySet()) {
            MediaType mt1 = entry.getValue();
            MediaType mt2 = c2.get(entry.getKey());
            if (mt2 == null) {
                continue;
            }
            // this can be with different value in @Version annotation
            mt1.setSchema(mergeSchema(mt1.getSchema(), mt2.getSchema()));
            if (CollectionUtils.isEmpty(mt1.getEncoding())) {
                mt1.setEncoding(mt2.getEncoding());
            } else if (CollectionUtils.isNotEmpty(mt2.getEncoding())) {
                mt1.getEncoding().putAll(mt2.getEncoding());
            }
            if (CollectionUtils.isEmpty(mt1.getExtensions())) {
                mt1.setExtensions(mt2.getExtensions());
            } else if (CollectionUtils.isNotEmpty(mt2.getExtensions())) {
                mt2.getExtensions().putAll(mt1.getExtensions());
                mt1.setExtensions(mt2.getExtensions());
            }
            if (mt1.getExample() == null) {
                mt1.setExample(mt2.getExample());
            }
            if (CollectionUtils.isEmpty(mt1.getExamples())) {
                mt1.setExamples(mt2.getExamples());
            } else if (CollectionUtils.isNotEmpty(mt2.getExamples())) {
                mt1.getExamples().putAll(mt2.getExamples());
            }
        }
        return c1;
    }

    public static Schema mergeSchema(Schema s1, Schema s2) {
        if (s1 == null) {
            return s2;
        }
        if (s2 == null) {
            return null;
        }
        if (s1.equals(s2)) {
            return s1;
        }
        if (s1 instanceof ComposedSchema && CollectionUtils.isNotEmpty(s1.getOneOf())) {
            s1.addOneOfItem(s2);
            return s1;
        }
        Schema finalSchema = setSpecVersion(new ComposedSchema());
        finalSchema.addOneOfItem(s1);
        finalSchema.addOneOfItem(s2);
        return finalSchema;
    }

    public static Schema appendSchema(Schema s1, Schema s2) {
        return appendSchema(s1, s2, true, false);
    }

    public static Schema appendSchema(Schema s1, Schema s2, boolean withBlocks, boolean withErase) {
        if (s1 == null) {
            return s2;
        }
        if (s2 == null) {
            return null;
        }
        if (s1.equals(s2)) {
            return s1;
        }

        if ((s1.getType() == null || TYPE_OBJECT.equals(s1.getType())) && s2.getType() != null && !TYPE_OBJECT.equals(s2.getType())) {
            s1.setType(s2.getType());
            if (withErase) {
                s2.setType(null);
            }
        }
        if (s1.getFormat() == null && s2.getFormat() != null) {
            s1.setFormat(s2.getFormat());
            if (withErase) {
                s2.setFormat(null);
            }
        }
        if (s1.getName() == null && s2.getName() != null) {
            s1.setName(s2.getName());
            if (withErase) {
                s2.setName(null);
            }
        }
        if (s1.getTitle() == null && s2.getTitle() != null) {
            s1.setTitle(s2.getTitle());
            if (withErase) {
                s2.setTitle(null);
            }
        }
        if (s1.getMultipleOf() == null && s2.getMultipleOf() != null) {
            s1.setMultipleOf(s2.getMultipleOf());
            if (withErase) {
                s2.setMultipleOf(null);
            }
        }
        if (s1.getMaximum() == null && s2.getMaximum() != null) {
            s1.setMaximum(s2.getMaximum());
            if (withErase) {
                s2.setMaximum(null);
            }
        }
        if (s1.getExclusiveMaximum() == null && s2.getExclusiveMaximum() != null) {
            s1.setExclusiveMaximum(s2.getExclusiveMaximum());
            if (withErase) {
                s2.setExclusiveMaximum(null);
            }
        }
        if (s1.getMinimum() == null && s2.getMinimum() != null) {
            s1.setMinimum(s2.getMinimum());
            if (withErase) {
                s2.setMinimum(null);
            }
        }
        if (s1.getExclusiveMinimum() == null && s2.getExclusiveMinimum() != null) {
            s1.setExclusiveMinimum(s2.getExclusiveMinimum());
            if (withErase) {
                s2.setExclusiveMinimum(null);
            }
        }
        if (s1.getMaxLength() == null && s2.getMaxLength() != null) {
            s1.setMaxLength(s2.getMaxLength());
            if (withErase) {
                s2.setMaxLength(null);
            }
        }
        if (s1.getMinLength() == null && s2.getMinLength() != null) {
            s1.setMinLength(s2.getMinLength());
            if (withErase) {
                s2.setMinLength(null);
            }
        }
        if (s1.getPattern() == null && s2.getPattern() != null) {
            s1.setPattern(s2.getPattern());
            if (withErase) {
                s2.setPattern(null);
            }
        }
        if (s1.getMaxItems() == null && s2.getMaxItems() != null) {
            s1.setMaxItems(s2.getMaxItems());
            if (withErase) {
                s2.setMaxItems(null);
            }
        }
        if (s1.getMinItems() == null && s2.getMinItems() != null) {
            s1.setMinItems(s2.getMinItems());
            if (withErase) {
                s2.setMinItems(null);
            }
        }
        if (s1.getUniqueItems() == null && s2.getUniqueItems() != null) {
            s1.setUniqueItems(s2.getUniqueItems());
            if (withErase) {
                s2.setUniqueItems(null);
            }
        }
        if (s1.getMaxProperties() == null && s2.getMaxProperties() != null) {
            s1.setMaxProperties(s2.getMaxProperties());
            if (withErase) {
                s2.setMaxProperties(null);
            }
        }
        if (s1.getMinProperties() == null && s2.getMinProperties() != null) {
            s1.setMinProperties(s2.getMinProperties());
            if (withErase) {
                s2.setMinProperties(null);
            }
        }
        if (s1.getRequired() == null && s2.getRequired() != null) {
            s1.setRequired(s2.getRequired());
            if (withErase) {
                s2.setRequired(null);
            }
        }
        if (s1.getType() == null && s2.getType() != null) {
            s1.setType(s2.getType());
            if (withErase) {
                s2.setType(null);
            }
        }
        if (s1.getNot() == null && s2.getNot() != null) {
            s1.setNot(s2.getNot());
            if (withErase) {
                s2.setNot(null);
            }
        }
        if (s1.getProperties() == null && s2.getProperties() != null) {
            s1.setProperties(s2.getProperties());
            if (withErase) {
                s2.setProperties(null);
            }
        }
        if (s1.getAdditionalProperties() == null && s2.getAdditionalProperties() != null) {
            s1.setAdditionalProperties(s2.getAdditionalProperties());
            if (withErase) {
                s2.setAdditionalProperties(null);
            }
        }
        if (s1.getDescription() == null && s2.getDescription() != null) {
            s1.setDescription(s2.getDescription());
            if (withErase) {
                s2.setDescription(null);
            }
        }
        if (s1.get$ref() == null && s2.get$ref() != null) {
            s1.set$ref(s2.get$ref());
            if (withErase) {
                s2.set$ref(null);
            }
        }
        if (s1.getNullable() == null && s2.getNullable() != null) {
            s1.setNullable(s2.getNullable());
            if (withErase) {
                s2.setNullable(null);
            }
        }
        if (s1.getReadOnly() == null && s2.getReadOnly() != null) {
            s1.setReadOnly(s2.getReadOnly());
            if (withErase) {
                s2.setReadOnly(null);
            }
        }
        if (s1.getWriteOnly() == null && s2.getWriteOnly() != null) {
            s1.setWriteOnly(s2.getWriteOnly());
            if (withErase) {
                s2.setWriteOnly(null);
            }
        }
        if (!s1.getExampleSetFlag() && s2.getExampleSetFlag()) {
            s1.setExampleSetFlag(s2.getExampleSetFlag());
            s1.setExample(s2.getExample());
            if (withErase) {
                s2.setExample(null);
                s2.setExampleSetFlag(false);
            }
        }
        if (s1.getExample() == null && s2.getExample() != null) {
            s1.setExample(s2.getExample());
            if (withErase) {
                s2.setExample(null);
            }
        }
        if (s1.getExternalDocs() == null && s2.getExternalDocs() != null) {
            s1.setExternalDocs(s2.getExternalDocs());
            if (withErase) {
                s2.setExternalDocs(null);
            }
        }
        if (s1.getDeprecated() == null && s2.getDeprecated() != null) {
            s1.setDeprecated(s2.getDeprecated());
            if (withErase) {
                s2.setDeprecated(null);
            }
        }
        if (s1.getXml() == null && s2.getXml() != null) {
            s1.setXml(s2.getXml());
            if (withErase) {
                s2.setXml(null);
            }
        }
        if (s1.getExtensions() == null && s2.getExtensions() != null) {
            s1.setExtensions(s2.getExtensions());
            if (withErase) {
                s2.setExtensions(null);
            }
        }
        if (s1.getDiscriminator() == null && s2.getDiscriminator() != null) {
            s1.setDiscriminator(s2.getDiscriminator());
            if (withErase) {
                s2.setDiscriminator(null);
            }
        }
        if (s1.getPrefixItems() == null && s2.getPrefixItems() != null) {
            s1.setPrefixItems(s2.getPrefixItems());
            if (withErase) {
                s2.setPrefixItems(null);
            }
        }
        if (s1.getElse() == null && s2.getElse() != null) {
            s1.setElse(s2.getElse());
            if (withErase) {
                s2.setElse(null);
            }
        }
        if (withBlocks) {
            if (s1.getAnyOf() == null && s2.getAnyOf() != null) {
                s1.setAnyOf(s2.getAnyOf());
            }
            if (s1.getOneOf() == null && s2.getOneOf() != null) {
                s1.setOneOf(s2.getOneOf());
            }
        }
        if (s1.getItems() == null && s2.getItems() != null) {
            s1.setItems(s2.getItems());
            if (withErase) {
                s2.setItems(null);
            }
        }
        if (s1.getTypes() == null && s2.getTypes() != null) {
            s1.setTypes(s2.getTypes());
            if (withErase) {
                s2.setTypes(null);
            }
        }

        if (s1.getPatternProperties() == null && s2.getPatternProperties() != null) {
            s1.setPatternProperties(s2.getPatternProperties());
            if (withErase) {
                s2.setPatternProperties(null);
            }
        }
        if (s1.getExclusiveMaximumValue() == null && s2.getExclusiveMaximumValue() != null) {
            s1.setExclusiveMaximumValue(s2.getExclusiveMaximumValue());
            if (withErase) {
                s2.setExclusiveMaximumValue(null);
            }
        }
        if (s1.getExclusiveMinimumValue() == null && s2.getExclusiveMinimumValue() != null) {
            s1.setExclusiveMinimumValue(s2.getExclusiveMinimumValue());
            if (withErase) {
                s2.setExclusiveMinimumValue(null);
            }
        }
        if (s1.getContains() == null && s2.getContains() != null) {
            s1.setContains(s2.getContains());
            if (withErase) {
                s2.setContains(null);
            }
        }
        if (s1.get$id() == null && s2.get$id() != null) {
            s1.set$id(s2.get$id());
            if (withErase) {
                s2.set$id(null);
            }
        }
        if (s1.get$schema() == null && s2.get$schema() != null) {
            s1.set$schema(s2.get$schema());
            if (withErase) {
                s2.set$schema(null);
            }
        }
        if (s1.get$anchor() == null && s2.get$anchor() != null) {
            s1.set$anchor(s2.get$anchor());
            if (withErase) {
                s2.set$anchor(null);
            }
        }
        if (s1.get$vocabulary() == null && s2.get$vocabulary() != null) {
            s1.set$vocabulary(s2.get$vocabulary());
            if (withErase) {
                s2.set$vocabulary(null);
            }
        }
        if (s1.get$dynamicAnchor() == null && s2.get$dynamicAnchor() != null) {
            s1.set$dynamicAnchor(s2.get$dynamicAnchor());
            if (withErase) {
                s2.set$dynamicAnchor(null);
            }
        }
        if (s1.getContentEncoding() == null && s2.getContentEncoding() != null) {
            s1.setContentEncoding(s2.getContentEncoding());
            if (withErase) {
                s2.setContentEncoding(null);
            }
        }
        if (s1.getContentMediaType() == null && s2.getContentMediaType() != null) {
            s1.setContentMediaType(s2.getContentMediaType());
            if (withErase) {
                s2.setContentMediaType(null);
            }
        }
        if (s1.getContentSchema() == null && s2.getContentSchema() != null) {
            s1.setContentSchema(s2.getContentSchema());
            if (withErase) {
                s2.setContentSchema(null);
            }
        }
        if (s1.getPropertyNames() == null && s2.getPropertyNames() != null) {
            s1.setPropertyNames(s2.getPropertyNames());
            if (withErase) {
                s2.setPropertyNames(null);
            }
        }
        if (s1.getUnevaluatedProperties() == null && s2.getUnevaluatedProperties() != null) {
            s1.setUnevaluatedProperties(s2.getUnevaluatedProperties());
            if (withErase) {
                s2.setUnevaluatedProperties(null);
            }
        }
        if (s1.getMaxContains() == null && s2.getMaxContains() != null) {
            s1.setMaxContains(s2.getMaxContains());
            if (withErase) {
                s2.setMaxContains(null);
            }
        }
        if (s1.getMinContains() == null && s2.getMinContains() != null) {
            s1.setMinContains(s2.getMinContains());
            if (withErase) {
                s2.setMinContains(null);
            }
        }
        if (s1.getAdditionalItems() == null && s2.getAdditionalItems() != null) {
            s1.setAdditionalItems(s2.getAdditionalItems());
            if (withErase) {
                s2.setAdditionalItems(null);
            }
        }
        if (s1.getUnevaluatedItems() == null && s2.getUnevaluatedItems() != null) {
            s1.setUnevaluatedItems(s2.getUnevaluatedItems());
            if (withErase) {
                s2.setUnevaluatedItems(null);
            }
        }
        if (s1.getIf() == null && s2.getIf() != null) {
            s1.setIf(s2.getIf());
            if (withErase) {
                s2.setIf(null);
            }
        }
        if (s1.getThen() == null && s2.getThen() != null) {
            s1.setThen(s2.getThen());
            if (withErase) {
                s2.setThen(null);
            }
        }
        if (s1.getDependentSchemas() == null && s2.getDependentSchemas() != null) {
            s1.setDependentSchemas(s2.getDependentSchemas());
            if (withErase) {
                s2.setDependentSchemas(null);
            }
        }
        if (s1.getDependentRequired() == null && s2.getDependentRequired() != null) {
            s1.setDependentRequired(s2.getDependentRequired());
            if (withErase) {
                s2.setDependentRequired(null);
            }
        }
        if (s1.get$comment() == null && s2.get$comment() != null) {
            s1.set$comment(s2.get$comment());
            if (withErase) {
                s2.set$comment(null);
            }
        }
        if (s1.getExamples() == null && s2.getExamples() != null) {
            s1.setExamples((List) s2.getExamples());
            if (withErase) {
                s2.setExamples(null);
            }
        }
        if (s1.getBooleanSchemaValue() == null && s2.getBooleanSchemaValue() != null) {
            s1.setBooleanSchemaValue(s2.getBooleanSchemaValue());
            if (withErase) {
                s2.setBooleanSchemaValue(null);
            }
        }
        if (s1.getJsonSchema() == null && s2.getJsonSchema() != null) {
            s1.setJsonSchema(s2.getJsonSchema());
            if (withErase) {
                s2.setJsonSchema(null);
            }
        }
        if (s1.getJsonSchemaImpl() == null && s2.getJsonSchemaImpl() != null) {
            s1.setJsonSchemaImpl(s2.getJsonSchemaImpl());
            if (withErase) {
                s2.setJsonSchemaImpl(null);
            }
        }
        return s1;
    }

    /**
     * Copy information from one {@link OpenAPI} object to another.
     *
     * @param to The {@link OpenAPI} object to copy to
     * @param from The {@link OpenAPI} object to copy from
     */
    public static void copyOpenApi(OpenAPI to, OpenAPI from) {
        if (to == null || from == null) {
            return;
        }
        if (CollectionUtils.isNotEmpty(from.getTags())) {
            from.getTags().forEach(to::addTagsItem);
        }
        if (CollectionUtils.isNotEmpty(from.getServers())) {
            from.getServers().forEach(to::addServersItem);
        }
        if (CollectionUtils.isNotEmpty(from.getSecurity())) {
            from.getSecurity().forEach(to::addSecurityItem);
        }
        if (CollectionUtils.isNotEmpty(from.getPaths())) {
            from.getPaths().forEach(to::path);
        }
        if (from.getExternalDocs() != null) {
            to.setExternalDocs(from.getExternalDocs());
        }
        if (CollectionUtils.isNotEmpty(from.getExtensions())) {
            from.getExtensions().forEach(to::addExtension);
        }

        if (from.getComponents() != null) {

            var components = from.getComponents();

            Map schemas = components.getSchemas();
            if (CollectionUtils.isNotEmpty(schemas)) {
                schemas.forEach((k, v) -> {
                    if (v.getName() == null) {
                        v.setName(k);
                    }
                    to.schema(k, v);
                });
            }

            var securitySchemes = components.getSecuritySchemes();
            if (CollectionUtils.isNotEmpty(securitySchemes)) {
                securitySchemes.forEach(to::schemaRequirement);
            }
            var links = components.getLinks();
            if (CollectionUtils.isNotEmpty(links)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().links(links);
            }
            var callbacks = components.getCallbacks();
            if (CollectionUtils.isNotEmpty(callbacks)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().callbacks(callbacks);
            }
            var headers = components.getHeaders();
            if (CollectionUtils.isNotEmpty(headers)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().headers(headers);
            }
            var parameters = components.getParameters();
            if (CollectionUtils.isNotEmpty(parameters)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().parameters(parameters);
            }
            var responses = components.getResponses();
            if (CollectionUtils.isNotEmpty(responses)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().responses(responses);
            }
            var requestBodies = components.getRequestBodies();
            if (CollectionUtils.isNotEmpty(requestBodies)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().requestBodies(requestBodies);
            }
            var extensions = components.getExtensions();
            if (CollectionUtils.isNotEmpty(extensions)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().extensions(extensions);
            }
            var pathItems = components.getPathItems();
            if (CollectionUtils.isNotEmpty(extensions)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().pathItems(pathItems);
            }
            var examples = components.getExamples();
            if (CollectionUtils.isNotEmpty(examples)) {
                if (to.getComponents() == null) {
                    to.setComponents(new Components());
                }
                to.getComponents().examples(examples);
            }

        }
    }

    public static boolean isIgnoredHeader(String headerName) {
        // Header parameter named "Authorization" are ignored. Use the `securitySchemes` and `security` sections instead to define authorization
        // Header parameter named "Content-Type" are ignored. The values for the "Content-Type" header are defined by `request.body.content.`
        // Header parameter named "Accept" are ignored. The values for the "Accept" header are defined by `responses..content.`
        return HttpHeaders.AUTHORIZATION.equalsIgnoreCase(headerName)
            || HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(headerName)
            || HttpHeaders.ACCEPT.equalsIgnoreCase(headerName);
    }

    public static Schema setNullable(Schema schema) {
        if (Utils.isOpenapi31()) {
            schema.addType(TYPE_NULL);
            schema.addType(schema.getType() != null ? schema.getType() : TYPE_OBJECT);
        } else {
            schema.setNullable(true);
        }
        return schema;
    }

    public static String getType(Schema schema) {
        return getType(schema.getType(), schema.getTypes());
    }

    public static String getType(String type, Collection types) {
        if (type != null) {
            return type;
        }
        if (Utils.isOpenapi31() && CollectionUtils.isNotEmpty(types)) {
            for (var t : types) {
                if (!t.equals(TYPE_NULL)) {
                    return t;
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static void setAllowableValues(Schema schema, String[] allowableValues, Element element, String elType, String elFormat, VisitorContext context) {
        if (ArrayUtils.isEmpty(allowableValues)) {
            return;
        }
        for (String allowableValue : allowableValues) {
            if (schema.getEnum() != null && schema.getEnum().contains(allowableValue)) {
                continue;
            }
            try {
                schema.addEnumItemObject(ConvertUtils.normalizeValue(allowableValue, elType, elFormat, context));
            } catch (IOException e) {
                warn("Can't convert " + allowableValue + " to " + elType + ", format: " + elFormat + ": " + e.getMessage(), context, element);
                schema.addEnumItemObject(allowableValue);
            }
        }
    }

    public static Schema getSchemaByRef(Schema schema, OpenAPI openApi) {
        return getSchemaByRef(schema.get$ref(), openApi);
    }

    public static Schema getSchemaByRef(String schemaRef, OpenAPI openApi) {
        if (StringUtils.isEmpty(schemaRef)) {
            return null;
        }
        return resolveSchemas(openApi).get(schemaRef.substring(COMPONENTS_SCHEMAS_PREFIX.length()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy