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

com.ly.doc.builder.openapi.AbstractOpenApiBuilder Maven / Gradle / Ivy

Go to download

Smart-doc is a tool that supports both JAVA RESTFUL API and Apache Dubbo RPC interface document generation.

There is a newer version: 3.0.5
Show newest version
/*
 * smart-doc
 *
 * Copyright (C) 2018-2023 smart-doc
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.ly.doc.builder.openapi;

import com.ly.doc.constants.ComponentTypeEnum;
import com.ly.doc.model.*;
import com.power.common.util.CollectionUtil;
import com.power.common.util.StringUtil;
import com.ly.doc.builder.DocBuilderTemplate;
import com.ly.doc.builder.ProjectDocConfigBuilder;
import com.ly.doc.constants.DocGlobalConstants;
import com.ly.doc.factory.BuildTemplateFactory;
import com.ly.doc.model.openapi.OpenApiTag;
import com.ly.doc.template.IDocBuildTemplate;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.OpenApiSchemaUtil;
import com.thoughtworks.qdox.JavaProjectBuilder;

import java.util.*;
import java.util.stream.Collectors;

import static com.ly.doc.constants.DocGlobalConstants.*;


/**
 * @author xingzi
 * Date 2022/10/12 18:49
 */
@SuppressWarnings("all")
public abstract class AbstractOpenApiBuilder {

    private String componentKey;

    public String getComponentKey() {
        return componentKey;
    }

    public void setComponentKey(String componentKey) {
        this.componentKey = componentKey;
    }

    abstract String getModuleName();

    /**
     * Create OpenAPI definition
     *
     * @param apiConfig  Configuration of smart-doc
     * @param apiDocList List of API DOC
     */
    abstract void openApiCreate(ApiConfig apiConfig, List apiDocList);

    /**
     * Build request
     *
     * @param apiConfig    Configuration of smart-doc
     * @param apiMethodDoc Data of method
     * @param apiDoc       singe api doc
     * @return Map of request urls
     */
    abstract Map buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, ApiDoc apiDoc);

    /**
     * response body
     *
     * @param apiMethodDoc ApiMethodDoc
     */
    abstract Map buildResponsesBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc);

    protected static final Map STRING_COMPONENT = new HashMap<>();

    static {
        STRING_COMPONENT.put("type", "string");
        STRING_COMPONENT.put("format", "string");
    }

    /**
     * Build openapi paths
     *
     * @param apiConfig  Configuration of smart-doc
     * @param apiDocList List of API DOC
     * @param tags       tags
     */
    public Map buildPaths(ApiConfig apiConfig, List apiDocList, Set tags) {
        Map pathMap = new HashMap<>(500);
        Set methodDocs = DocMapping.METHOD_DOCS;
        for (ApiMethodDoc methodDoc : methodDocs) {
            String [] paths = methodDoc.getPath().split(";");
            for(String path : paths) {
                path = path.trim();
                Map request = buildPathUrls(apiConfig, methodDoc, methodDoc.getClazzDoc());
                if (!pathMap.containsKey(path)) {
                    pathMap.put(path, request);
                } else {
                    Map oldRequest = (Map) pathMap.get(path);
                    oldRequest.putAll(request);
                }
            }
        }
        for (Map.Entry docEntry : DocMapping.TAG_DOC.entrySet()) {
            String tag = docEntry.getKey();
            tags.addAll(docEntry.getValue().getClazzDocs()
                    .stream()
                    //optimize tag content for compatible to swagger
                    .map(doc -> OpenApiTag.of(doc.getName(), doc.getDesc()))
                    .collect(Collectors.toSet()));
        }
        return pathMap;
    }

    /**
     * Build path urls
     *
     * @param apiConfig    ApiConfig
     * @param apiMethodDoc Method
     * @param apiDoc       ApiDoc
     */
    public Map buildPathUrls(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, ApiDoc apiDoc) {
        Map request = new HashMap<>(4);
        request.put(apiMethodDoc.getType().toLowerCase(), buildPathUrlsRequest(apiConfig, apiMethodDoc, apiDoc));
        return request;
    }

    /**
     * Build content for responses and requestBody
     *
     * @param apiConfig    ApiConfig
     * @param apiMethodDoc ApiMethodDoc
     * @param isRep        is response
     */
    public Map buildContent(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, boolean isRep) {
        Map content = new HashMap<>(8);
        String contentType = apiMethodDoc.getContentType();
        if (isRep) {
            contentType = "*/*";
        }
        content.put(contentType, buildContentBody(apiConfig, apiMethodDoc, isRep));
        return content;

    }

    /**
     * Build data of content
     *
     * @param apiConfig    ApiConfig
     * @param apiMethodDoc ApiMethodDoc
     * @param isRep        is response
     */
    public Map buildContentBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, boolean isRep) {
        Map content = new HashMap<>(8);
        if (Objects.nonNull(apiMethodDoc.getReturnSchema()) && isRep) {
            content.put("schema", apiMethodDoc.getReturnSchema());
        } else if (!isRep && Objects.nonNull(apiMethodDoc.getRequestSchema())) {
            content.put("schema", apiMethodDoc.getRequestSchema());
        } else {
            content.put("schema", buildBodySchema(apiMethodDoc, isRep));
        }

        if (OPENAPI_2_COMPONENT_KRY.equals(componentKey) && !isRep) {
            content.put("name", apiMethodDoc.getName());
        }
        if (OPENAPI_3_COMPONENT_KRY.equals(componentKey) &&
                (!isRep && apiConfig.isRequestExample() || (isRep && apiConfig.isResponseExample()))) {
            content.put("examples", buildBodyExample(apiMethodDoc, isRep));
        }
        return content;

    }

    /**
     * Build schema of Body
     *
     * @param apiMethodDoc ApiMethodDoc
     * @param isRep        is response
     */
    public Map buildBodySchema(ApiMethodDoc apiMethodDoc, boolean isRep) {
        Map schema = new HashMap<>(10);
        Map innerScheme = new HashMap<>(10);
        // For response
        if (isRep) {
            String responseRef = componentKey + OpenApiSchemaUtil.getClassNameFromParams(apiMethodDoc.getResponseParams());
            if (apiMethodDoc.getIsResponseArray() == 1) {
                schema.put("type", ARRAY);
                innerScheme.put("$ref", responseRef);
                schema.put("items", innerScheme);
            } else if (CollectionUtil.isNotEmpty(apiMethodDoc.getResponseParams())) {
                schema.put("$ref", responseRef);
            }
            return schema;
        }

        // for request
        String requestRef;
        String randomName = ComponentTypeEnum.getRandomName(ApiConfig.getInstance().getComponentType(), apiMethodDoc);
        if (apiMethodDoc.getContentType().equals(DocGlobalConstants.URL_CONTENT_TYPE)) {
            requestRef = componentKey + OpenApiSchemaUtil.getClassNameFromParams(apiMethodDoc.getQueryParams());
        } else {
            requestRef = componentKey + OpenApiSchemaUtil.getClassNameFromParams(apiMethodDoc.getRequestParams());
        }
        //remove special characters in url
        if (CollectionUtil.isNotEmpty(apiMethodDoc.getRequestParams())) {
            if (apiMethodDoc.getIsRequestArray() == 1) {
                schema.put("type", ARRAY);
                innerScheme.put("$ref", requestRef);
                schema.put("items", innerScheme);
            } else {
                schema.put("$ref", requestRef);
            }
        }
        return schema;
    }


    /**
     * Build body example
     *
     * @param apiMethodDoc ApiMethodDoc
     * @param isRep        is response
     */
    public static Map buildBodyExample(ApiMethodDoc apiMethodDoc, boolean isRep) {
        Map content = new HashMap<>(8);
        content.put("json", buildExampleData(apiMethodDoc, isRep));
        return content;

    }

    /**
     * Build example data
     *
     * @param apiMethodDoc ApiMethodDoc
     * @param isRep        is response
     */
    public static Map buildExampleData(ApiMethodDoc apiMethodDoc, boolean isRep) {
        Map content = new HashMap<>(8);
        content.put("summary", "test data");
        if (!isRep) {
            content.put("value", StringUtil.isEmpty(
                    apiMethodDoc.getRequestExample().getJsonBody()) ? apiMethodDoc.getRequestExample().getExampleBody() :
                    apiMethodDoc.getRequestExample().getJsonBody());
        } else {
            content.put("value", apiMethodDoc.getResponseUsage());
        }
        return content;

    }

    /**
     * Build request parameters
     *
     * @param apiMethodDoc API data for the method
     */
    abstract List> buildParameters(ApiMethodDoc apiMethodDoc);

    abstract Map getStringParams(ApiParam apiParam, boolean hasItems);

    /**
     * If it is a get request or @PathVariable set the request parameters
     *
     * @param apiParam Parameter information
     * @return parameters schema
     */
    public Map buildParametersSchema(ApiParam apiParam) {
        Map schema = new HashMap<>(10);
        String openApiType = DocUtil.javaTypeToOpenApiTypeConvert(apiParam.getType());
        // The values of openApiType are "file", "object", "array","string",  "integer","number"
        schema.put("type", openApiType);
        if ("file".equals(openApiType)) {
            schema.put("format", "binary");
            schema.put("type", "string");
        } else if ("object".equals(openApiType)) {
            if ("enum".equals(apiParam.getType())) {
                schema.put("enum", apiParam.getEnumValues());
            }
        } else if (ARRAY.equals(openApiType)) {
            if (CollectionUtil.isNotEmpty(apiParam.getEnumValues())) {
                schema.put("type", "string");
                schema.put("items", apiParam.getEnumValues());
            } else {
                schema.put("type", ARRAY);
                Map map = new HashMap<>(4);
                map.put("type", "string");
                map.put("format", "string");
                schema.put("items", map);
            }
        } else {
            // "string", "integer", "number"
            schema.put("format", apiParam.getType());
        }
        return schema;
    }

    /**
     * If the header is included, set the request parameters
     *
     * @param header header
     */
    public static Map buildParametersSchema(ApiReqParam header) {
        Map schema = new HashMap<>(10);
        String openApiType = DocUtil.javaTypeToOpenApiTypeConvert(header.getType());
        schema.put("type", openApiType);
        schema.put("format", openApiType);
        return schema;
    }

    /**
     * build response
     *
     * @param apiMethodDoc ApiMethodDoc
     * @return response info
     */
    public Map buildResponses(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc) {
        Map response = new HashMap<>(10);
        response.put("200", buildResponsesBody(apiConfig, apiMethodDoc));
        return response;
    }

    /**
     * component schema
     *
     * @param apiDocs List of ApiDoc
     */
    abstract public Map buildComponentsSchema(List apiDocs);

    /**
     * component schema properties
     *
     * @param apiParam list of ApiParam
     */
    public Map buildProperties(List apiParam, Map component, boolean isResp) {
        Map properties = new HashMap<>();
        Map propertiesData = new LinkedHashMap<>();
        List requiredList = new ArrayList<>();
        if (apiParam != null) {
            int paramsSize = apiParam.size();
            for (ApiParam param : apiParam) {
                if (param.isRequired()) {
                    requiredList.add(param.getField());
                }
                if (param.getType().equals("map") && StringUtil.isEmpty(param.getClassName())) {
                    continue;
                }
                if (param.isQueryParam() || param.isPathParam()) {
                    continue;
                }
                String field = param.getField();
                propertiesData.put(field, buildPropertiesData(param, component, isResp));
            }
            if (!propertiesData.isEmpty()) {
                properties.put("properties", propertiesData);
            }
            if (!CollectionUtil.isEmpty(requiredList)) {
                properties.put("required", requiredList);
            }
            return properties;
        } else {
            return new HashMap<>();
        }

    }

    /**
     * component schema properties data
     *
     * @param apiParam ApiParam
     */
    private Map buildPropertiesData(ApiParam apiParam, Map component, boolean isResp) {
        Map propertiesData = new HashMap<>();
        String openApiType = DocUtil.javaTypeToOpenApiTypeConvert(apiParam.getType());
        //array object file map
        propertiesData.put("description", apiParam.getDesc());
        if (StringUtil.isNotEmpty(apiParam.getValue())) {
            propertiesData.put("example", StringUtil.removeDoubleQuotes(apiParam.getValue()));
        }

        if (!"object".equals(openApiType)) {
            propertiesData.put("type", openApiType);
            propertiesData.put("format", "int16".equals(apiParam.getType()) ? "int32" : apiParam.getType());
        }
        if ("map".equals(apiParam.getType())) {
            propertiesData.put("type", "object");
            propertiesData.put("description", apiParam.getDesc() + "(map data)");
        }
        if ("array".equals(apiParam.getType())) {
            propertiesData.put("type", "array");
            if (CollectionUtil.isNotEmpty(apiParam.getChildren())) {
                if (!apiParam.isSelfReferenceLoop()) {
                    Map arrayRef = new HashMap<>(4);
                    String childSchemaName = OpenApiSchemaUtil.getClassNameFromParams(apiParam.getChildren());
                    if (childSchemaName.contains(OpenApiSchemaUtil.NO_BODY_PARAM)) {
                        propertiesData.put("type", "object");
                        propertiesData.put("description", apiParam.getDesc() + "(object)");
                    } else {
                        component.put(childSchemaName, buildProperties(apiParam.getChildren(), component, isResp));
                        arrayRef.put("$ref", componentKey + childSchemaName);
                        propertiesData.put("items", arrayRef);
                    }
                }
            } else {
                Map arrayRef = new HashMap<>(4);
                arrayRef.put("type", "string");
                propertiesData.put("items", arrayRef);
            }
        }
        if ("file".equals(apiParam.getType())) {
            propertiesData.put("type", "string");
            propertiesData.put("format", "binary");
        }
        if ("object".equals(apiParam.getType())) {
            if (CollectionUtil.isNotEmpty(apiParam.getChildren())) {
                propertiesData.put("type", "object");
                propertiesData.put("description", apiParam.getDesc() + "(object)");
                if (!apiParam.isSelfReferenceLoop()) {
                    String childSchemaName = OpenApiSchemaUtil.getClassNameFromParams(apiParam.getChildren());
                    if (childSchemaName.contains(OpenApiSchemaUtil.NO_BODY_PARAM)) {
                        propertiesData.put("type", "object");
                        propertiesData.put("description", apiParam.getDesc() + "(object)");
                    } else {
                        component.put(childSchemaName, buildProperties(apiParam.getChildren(), component, isResp));
                        propertiesData.put("$ref", componentKey + childSchemaName);
                    }
                }
            } else {
                propertiesData.put("type", "object");
                propertiesData.put("description", apiParam.getDesc() + "(object)");
            }
        }

        return propertiesData;
    }

    /**
     * Get a list of OpenAPI's document data
     *
     * @param config         Configuration of smart-doc
     * @param projectBuilder JavaDocBuilder of QDox
     */
    public List getOpenApiDocs(ApiConfig config, JavaProjectBuilder projectBuilder) {
        config.setShowJavaType(false);
        DocBuilderTemplate builderTemplate = new DocBuilderTemplate();
        builderTemplate.checkAndInit(config, Boolean.TRUE);
        ProjectDocConfigBuilder configBuilder = new ProjectDocConfigBuilder(config, projectBuilder);
        config.setParamsDataToTree(true);
        IDocBuildTemplate docBuildTemplate = BuildTemplateFactory.getDocBuildTemplate(config.getFramework());
        return docBuildTemplate.getApiData(configBuilder);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy