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

org.openapitools.codegen.languages.JetbrainsHttpClientClientCodegen Maven / Gradle / Ivy

There is a newer version: 7.9.0
Show newest version
/*
 * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
 *
 * 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 org.openapitools.codegen.languages;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.examples.Example;
import lombok.Getter;
import lombok.Setter;
import org.openapitools.codegen.*;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
Note : This code has been MASSIVELY inspired by PostmanCollectionCodegen from @gcatanese.

Hopefully one day we can merge the similar code, as both generators stabilize
 */
public class JetbrainsHttpClientClientCodegen extends DefaultCodegen implements CodegenConfig {

    private final Logger LOGGER = LoggerFactory.getLogger(JetbrainsHttpClientClientCodegen.class);

    public static final String JSON_ESCAPE_NEW_LINE = "\n";
    public static final String JSON_ESCAPE_DOUBLE_QUOTE = "\"";

    public static final String REQUEST_PARAMETER_GENERATION_DEFAULT_VALUE = "Example";
    protected String requestParameterGeneration = REQUEST_PARAMETER_GENERATION_DEFAULT_VALUE; // values: Example, Schema


    public static final String PROJECT_NAME = "Jetbrains HTTP Client";

    public static final String BODY_VARIABLES = "bodyVariables";

    public List bodyVariables = new ArrayList<>();

    public static final String CUSTOM_HEADERS = "customHeaders";

    public List customHeaders = new ArrayList<>();

    // A map is nice, because that way I easily override variables across APIs, for pagination for example. This should add nice defaults
    private final Map customVariables = new HashMap<>();


    @Override
    public CodegenType getTag() {
        return CodegenType.CLIENT;
    }

    @Override
    public String getName() {
        return "jetbrains-http-client";
    }

    @Override
    public String getHelp() {
        return "Generates a jetbrains-http client. See https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html";
    }

    @Override
    public GeneratorLanguage generatorLanguage() {
        return GeneratorLanguage.HTTP;
    }

    public JetbrainsHttpClientClientCodegen() {
        super();

        generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
                .stability(Stability.EXPERIMENTAL)
                .build();

        outputFolder = "generated-code" + File.separator + "jetbrains-http-client";
        apiTemplateFiles.put("api.mustache", ".http");
        embeddedTemplateDir = templateDir = "jetbrains-http-client";
        apiPackage = "Apis";
        supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
        supportingFiles.add(new SupportingFile("http-client.template.env.mustache", "Apis", "http-client.template.env.json"));


        cliOptions.clear();
        cliOptions.add(CliOption.newString(BODY_VARIABLES, "whether to convert body placeholders (i.e. VAR_1) into variables (i.e. {{VAR_1}})"));
        cliOptions.add(CliOption.newString(CUSTOM_HEADERS, "custom headers that can be set for each request. Can be used for unsupported features, for example auth methods like oauth."));
    }

    @Override
    public void processOpts() {
        super.processOpts();

        var additionalProperties = additionalProperties();

        if (additionalProperties.containsKey(BODY_VARIABLES)) {
            bodyVariables = Arrays.asList(additionalProperties.get(BODY_VARIABLES).toString().split("-"));
        }

        if (additionalProperties.containsKey(CUSTOM_HEADERS)) {
            customHeaders = Arrays.asList(additionalProperties.get(CUSTOM_HEADERS).toString().split("&"));
        }

        bodyVariables.forEach(variable -> customVariables.put(variable, ""));
        for(String header: customHeaders) {
            List variables = extractDoubleCurlyBraces(header);
            if(!variables.isEmpty()) {
                variables.forEach(v -> customVariables.put(v, ""));
            }
        }
    }

    @Override
    protected ImmutableMap.Builder addMustacheLambdas() {

        return super.addMustacheLambdas()
                .put("doubleMustache", new DoubleMustacheLambda());
    }

    public static class DoubleMustacheLambda implements Mustache.Lambda {
        @Override
        public void execute(Template.Fragment fragment, Writer writer) throws IOException {
            String text = fragment.execute();
            writer.write(text
                    .replaceAll("\\{", "{{")
                    .replaceAll("}", "}}")
            );
        }
    }

    @Override
    public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
        OperationsMap results = super.postProcessOperationsWithModels(objs, allModels);

        OperationMap ops = results.getOperations();
        List opList = ops.getOperation();

        for (CodegenOperation codegenOperation : opList) {
            List requests = getRequests(codegenOperation);

            if (requests != null) {
                codegenOperation.vendorExtensions.put("requests", requests);
                //Adding to each operation for now, we may be smarter later on
                codegenOperation.vendorExtensions.put("customHeaders", customHeaders);
            }
        }

        return results;
    }

    @Override
    public Map postProcessSupportingFileData(Map objs) {
        var variables = new ArrayList<>(customVariables.keySet());
        objs.put("vendorExtensionsVariables", variables);
        return objs;
    }

    List getRequests(CodegenOperation codegenOperation) {
        List items = new ArrayList<>();

        if (codegenOperation.getHasBodyParam()) {
            // operation with bodyParam
            if (requestParameterGeneration.equalsIgnoreCase("Schema")) {
                // get from schema
                items.add(new RequestItem(codegenOperation.summary, getJsonFromSchema(codegenOperation.bodyParam)));
            } else {
                // get from examples
                if (codegenOperation.bodyParam.example != null) {
                    // find in bodyParam example
                    items.add(new RequestItem(codegenOperation.summary, formatJson(codegenOperation.bodyParam.example)));
                } else if (codegenOperation.bodyParam.getContent().get("application/json") != null &&
                        codegenOperation.bodyParam.getContent().get("application/json").getExamples() != null) {
                    // find in components/examples
                    for (Map.Entry entry : codegenOperation.bodyParam.getContent().get("application/json").getExamples().entrySet()) {
                        String exampleRef = entry.getValue().get$ref();
                        if (exampleRef != null) {
                            Example example = this.openAPI.getComponents().getExamples().get(extractExampleByName(exampleRef));
                            String exampleAsString = getJsonFromExample(example);
                            items.add(new RequestItem(example.getSummary(), exampleAsString));
                        }
                    }
                } else if (codegenOperation.bodyParam.getSchema() != null) {
                    // find in schema example
                    String exampleAsString = (codegenOperation.bodyParam.getSchema().getExample());
                    items.add(new RequestItem(codegenOperation.summary, exampleAsString));
                } else {
                    // example not found
                    // get from schema
                    items.add(new RequestItem(codegenOperation.summary, getJsonFromSchema(codegenOperation.bodyParam)));

                }
            }
        } else {
            // operation without bodyParam
            items.add(new RequestItem(codegenOperation.summary, null));
        }

        codegenOperation.headerParams.forEach(param -> customVariables.put(param.baseName, ""));
        codegenOperation.queryParams.forEach(param -> customVariables.put(param.paramName, ""));

        // I also need to grab the parameters from the path
        List pathVariables = extractSingleCurlyBraces(codegenOperation.path);
        pathVariables.forEach(pv -> customVariables.put(pv, ""));

        // Handling custom variables now
        return handleCustomVariablesInRequests(items);
    }

    public static List extractDoubleCurlyBraces(String input) {
        List result = new ArrayList<>();
        Pattern pattern = Pattern.compile("\\{\\{([^}]+)\\}\\}");
        Matcher matcher = pattern.matcher(input);

        while (matcher.find()) {
            result.add(matcher.group(1));
        }

        return result;
    }

    public static List extractSingleCurlyBraces(String input) {
        List result = new ArrayList<>();
        Pattern pattern = Pattern.compile("\\{([^}]+)\\}");
        Matcher matcher = pattern.matcher(input);

        while (matcher.find()) {
            result.add(matcher.group(1));
        }

        return result;
    }


    private List handleCustomVariablesInRequests(List items) {
        if (!bodyVariables.isEmpty()) {
            for (var item : items) {
                for (var customVariable : bodyVariables) {
                    var body = item.getBody();
                    if (body != null) {
                        body = body.replace(customVariable, "{{" + customVariable + "}}");
                        item.setBody(body);
                    }
                }
            }
        }

        return items;
    }


    @Override
    public void postProcess() {
        System.out.println("##########################################################################################");
        System.out.println("# Thanks for using OpenAPI Generator.                                                    #");
        System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F                 #");
        System.out.println("# https://opencollective.com/openapi_generator/donate                                    #");
        System.out.println("#                                                                                        #");
        System.out.println("# This generator was written by Julien Lengrand-Lambert (https://github.com/jlengrand)   #");
        System.out.println("##########################################################################################");
    }

    @Getter @Setter
    public class RequestItem {

        private String name;
        private String body;

        public RequestItem(String name, String body) {
            this.name = name;
            this.body = body;
        }
    }

    /*
    Helpers
     */

    public String getJsonFromSchema(CodegenParameter codegenParameter) {

        String ret = "{" + JSON_ESCAPE_NEW_LINE + " ";

        int numVars = codegenParameter.vars.size();
        int counter = 1;

        for (CodegenProperty codegenProperty : codegenParameter.vars) {
            ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + codegenProperty.baseName + JSON_ESCAPE_DOUBLE_QUOTE + ": " +
                    JSON_ESCAPE_DOUBLE_QUOTE + "<" + getType(codegenProperty) + ">" + JSON_ESCAPE_DOUBLE_QUOTE;

            if (counter < numVars) {
                // add comma unless last attribute
                ret = ret + "," + JSON_ESCAPE_NEW_LINE + " ";
            }
            counter++;

        }

        ret = ret + JSON_ESCAPE_NEW_LINE + "}";

        return ret;
    }

    public String getType(CodegenProperty codegenProperty) {
        if (codegenProperty.isNumeric) {
            return "number";
        } else if (codegenProperty.isDate) {
            return "date";
        } else {
            return "string";
        }
    }

    public String formatJson(String json) {

        ObjectMapper objectMapper = new ObjectMapper();

        try {
            // convert to JSON object and prettify
            JsonNode actualObj = objectMapper.readTree(json);
            json = Json.pretty(actualObj);

        } catch (JsonProcessingException e) {
            LOGGER.warn("Error formatting JSON", e);
            json = "";
        }

        return json;
    }

    public String extractExampleByName(String ref) {
        return ref.substring(ref.lastIndexOf("/") + 1);
    }

    public String getJsonFromExample(Example example) {
        String ret = "";

        if (example == null) {
            return ret;
        }

        if (example.getValue() instanceof ObjectNode) {
            ret = convertToJson((ObjectNode) example.getValue());
        } else if (example.getValue() instanceof LinkedHashMap) {
            ret = convertToJson((LinkedHashMap) example.getValue());
        }

        return ret;
    }

    public String convertToJson(ObjectNode objectNode) {
        return formatJson(objectNode.toString());
    }

    public String convertToJson(LinkedHashMap linkedHashMap) {
        String ret = "";

        return traverseMap(linkedHashMap, ret);
    }

    private String traverseMap(LinkedHashMap linkedHashMap, String ret) {

        ret = ret + "{" + JSON_ESCAPE_NEW_LINE + " ";

        int numVars = linkedHashMap.entrySet().size();
        int counter = 1;

        for (Map.Entry mapElement : linkedHashMap.entrySet()) {
            String key = mapElement.getKey();
            Object value = mapElement.getValue();

            if (value instanceof String) {
                // unescape double quotes already escaped
                value = ((String) value).replace("\\\"", "\"");

                ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " +
                        JSON_ESCAPE_DOUBLE_QUOTE + value + JSON_ESCAPE_DOUBLE_QUOTE;
            } else if (value instanceof Integer || value instanceof Boolean) {
                ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " +
                        value;
            } else if (value instanceof HashMap) {
                String in = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": ";
                ret = traverseMap(((LinkedHashMap) value), in);
            } else if (value instanceof List) {
                // TODO : What if it's a list of objects? _urgh_
                var items = ((List) value);
                StringBuilder jsonBuilder = new StringBuilder("[");

                for (int i = 0; i < items.size(); i++) {
                    jsonBuilder.append(JSON_ESCAPE_DOUBLE_QUOTE).append(items.get(i)).append(JSON_ESCAPE_DOUBLE_QUOTE);
                    if (i < items.size() - 1) {
                        jsonBuilder.append(",");
                    }
                }
                jsonBuilder.append("]");

                ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " + jsonBuilder;
            } else {
                LOGGER.warn("Value type unrecognised: " + value.getClass());
                //WARNING: here we are undoing what is done in "add comma unless last attribute"
                // This is meant to avoid dangling commas if we encounter an unknown type
                ret = ret.substring(0, ret.length() - 3);
            }

            if (counter < numVars) {
                // add comma unless last attribute
                ret = ret + "," + JSON_ESCAPE_NEW_LINE + " ";
            }
            counter++;
        }

        ret = ret + JSON_ESCAPE_NEW_LINE + "}";

        return ret;
    }

    @Override
    public String escapeUnsafeCharacters(String input) {
        return input;
    }

    @Override
    public String escapeQuotationMark(String input) {
        return input;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy