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

com.reprezen.genflow.swagger.nswag.Config Maven / Gradle / Ivy

package com.reprezen.genflow.swagger.nswag;

import static com.reprezen.genflow.swagger.nswag.Config.Output.CS_CLIENT;
import static com.reprezen.genflow.swagger.nswag.Config.Output.CS_SERVER;
import static com.reprezen.genflow.swagger.nswag.Config.Output.TS_CLIENT;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.io.FileUtils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.collect.Lists;
import com.reprezen.genflow.api.GenerationException;
import com.reprezen.genflow.api.template.GenTemplate;
import com.reprezen.genflow.api.template.GenTemplateContext;

import io.swagger.models.Swagger;

// N.B. Suppress any fields that should NOT appear in gentarget files using @JsonIgnore.
// Currently, there are no such fields, and this setting makes it impossible to forget
// to mark a field for JSON processing.
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Config {

    /*
     * Most of the member variables are part of the "run" file that is produced to drive NSwag. The @NSwagParam
     * annotation captures which "outputs" the parameter applies to, as well as documentation and default value. To get
     * around limitations in annotation member types, default values appear as strings, but any value that begins with
     * "JSON:" is parsed to yield a JSON object, and that object is used.
     * 
     * This class is capable of doing three important and related things:
     * 
     * 1. Define parameter definitions for a gentemplate for whichever output the gentemplate is for.
     * 
     * 2. Create an instance based the values in a gentarget file.
     * 
     * 3. Create a run file for NSwag, based on the output of the gentemplate.
     */

    private String nSwagPath;

    @NSwagParam(output = CS_CLIENT, //
            description = "The exception class (default 'SwaggerException', may use '{controller}' placeholder).", //
            defaultValue = "SwaggerException")
    private String exceptionClass;

    @NSwagParam(output = { CS_CLIENT, TS_CLIENT }, //
            description = "Specifies whether generate client classes.", //
            defaultValue = "JSON:true")
    private Boolean generateClientClasses;

    @NSwagParam(output = { CS_CLIENT, TS_CLIENT }, //
            description = "Specifies whether generate interfaces for the client classes.", //
            defaultValue = "JSON:false")
    private Boolean generateClientInterfaces;

    @NSwagParam(output = { CS_CLIENT, TS_CLIENT }, //
            description = "Specifies whether to generate DTO classes.", //
            defaultValue = "JSON:true")
    private Boolean generateDtoTypes;

    @NSwagParam(output = CS_CLIENT, //
            description = "Specifies whether to call CreateHttpClientAsync on the base class to create a new HttpClient.", //
            defaultValue = "JSON:false")
    private Boolean useHttpClientCreationMethod;

    @NSwagParam(output = CS_CLIENT, //
            description = "", // TODO
            defaultValue = "JSON:false")
    private Boolean useHttpRequestMessageCreationMethod;

    @NSwagParam(output = CS_CLIENT, //
            description = "Specifies whether to generate contracts output (interface and models in a separate file set with the ContractsOutput parameter).", //
            defaultValue = "JSON:false")
    private String generateContractsOutput;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER, TS_CLIENT }, //
            description = "The class name of the generated client.", //
            csServerDefault = "{controller}", //
            csClientDefault = "{controller}Client", //
            tsClientDefault = "{controller}Client")
    private String className;

    @NSwagParam(output = TS_CLIENT, //
            description = "The TypeScript module name ('' for no module).", //
            defaultValue = "")
    private String moduleName;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The namespace of the generated classes ('' for no namespace in TypeScript client).", //
            defaultValue = "MyNamespaceX", //
            tsClientDefault = "")
    private String namespace;

    @NSwagParam(output = TS_CLIENT, //
            description = "The target TypeScript version.", //
            defaultValue = "1.8")
    private String typeScriptVersion;

    @NSwagParam(output = TS_CLIENT, //
            description = "The type of the asynchronism handling ('JQueryCallbacks', 'JQueryPromises', 'AngularJS', 'Angular2', 'Fetch', 'Aurelia')", //
            defaultValue = "JQueryCallbacks")
    private String template;

    @NSwagParam(output = TS_CLIENT, //
            description = "The promise type ('Promise' or 'QPromise').", //
            defaultValue = "Promise")
    private String promiseType;

    @NSwagParam(output = TS_CLIENT, //
            description = "Specifies whether DTO exceptions are wrapped in a SwaggerException instance.", //
            defaultValue = "JSON:false")
    private Boolean wrapDtoExceptions;

    @NSwagParam(output = TS_CLIENT, //
            description = "The base class of the generated client classes (optional, must be imported or implemented in the extension code).", //
            defaultValue = "JSON:null")
    private String clientBaseClass;

    @NSwagParam(output = TS_CLIENT, //
            description = "Call 'transformOptions' on the base class or extension class.", //
            defaultValue = "JSON:false")
    private Boolean useTransformOptionsMethod;

    @NSwagParam(output = TS_CLIENT, //
            description = "Call 'transformResult' on the base class or extension class.", //
            defaultValue = "JSON:false")
    private Boolean useTransformResultMethod;

    @NSwagParam(output = TS_CLIENT, //
            description = "Specifies whether to mark optional properties with '?'.", // TODO
            defaultValue = "JSON:false")
    private Boolean markOptionalProperties;

    @NSwagParam(output = TS_CLIENT, //
            description = "The type style.", //
            defaultValue = "Class")
    private String typeStyle;

    @NSwagParam(output = TS_CLIENT, //
            description = "The list of extended classes.", //
            defaultValue = "JSON:[]")
    private List extendedClasses;

    @NSwagParam(output = TS_CLIENT, //
            description = "The extension code (string or file path", //
            defaultValue = "JSON:null")
    private String extensionCode;

    @NSwagParam(output = TS_CLIENT, //
            description = "Overrides the service host of the web document (optional, use '.' to remove the hostname).", //
            defaultValue = "JSON:null")
    private String serviceHost;

    @NSwagParam(output = TS_CLIENT, //
            description = "Overrides the allowed schemes of the web service (optional array, 'http', 'https', 'ws', 'wss').", //
            defaultValue = "JSON:null")
    private List serviceSchemes;

    @NSwagParam(output = CS_SERVER, //
            description = "Additional namespaces to appear in 'using' directives", //
            defaultValue = "JSON:[\"System.Web.Http\"]")
    private List additionalNamespaceUsages;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "Specifies whether a required property must be defined in JSON (sets Required.Always when the property is required).", //
            defaultValue = "JSON:true")
    private Boolean requiredPropertiesMustBeDefined;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The date .NET type.", //
            defaultValue = "DateTime")
    private String dateType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER, TS_CLIENT }, //
            description = "The date time type ('Date', 'MomentJS', 'string' for TypeScript Client).", //
            defaultValue = "DateTime", //
            tsClientDefault = "Date")
    private String dateTimeType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The time .NET type.", //
            defaultValue = "TimeSpan")
    private String timeType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The time span .NET type.", //
            defaultValue = "TimeSpan")
    private String timespanType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The generic array .NET type.", //
            csServerDefault = "IEnumerable", //
            csClientDefault = "ObservableCollection")
    private String arrayType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The generic dictionary .NET type.", //
            defaultValue = "Dictionary")
    private String dictionaryType;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER }, //
            description = "The CSharp class style, 'Poco' or 'Inpc'.", //
            defaultValue = "Inpc")
    private String classStyle;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER, TS_CLIENT }, //
            description = "The operation generation mode, one of: 'SingleClientFromOperationId', 'MultipleClientsFromPathSegments', 'MultipleClientsFromOperationId'", //
            defaultValue = "MultipleClientsFromOperationId")
    private String operationGenerationMode;

    @NSwagParam(output = { CS_CLIENT, CS_SERVER, TS_CLIENT }, //
            description = "Specifies whether to generate default values for properties (may generate CSharp 6 code, default: true).", //
            defaultValue = "JSON:true")
    private Boolean generateDefaultValues;

    private static ObjectMapper jsonMapper = new ObjectMapper();
    static {
        jsonMapper.setSerializationInclusion(Include.NON_NULL);
    }
    private static ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

    public static void defineGenTemplateParameters(GenTemplate gt, Output output) throws GenerationException {
        gt.define(gt.parameter() //
                .named("nSwagPath") //
                .withDescription("Location of NSwag command line tool (nswag.exe). May be omitted if NSwag is in PATH",
                        "or, when run from within API Studio, when the location is set in Preferences (RepreZen/Code Generation)."));
        for (Field field : getNSwagConfigFields(output)) {
            gt.define( //
                    gt.parameter() //
                            .named(field.getName()) //
                            .withDescription(getFieldDescription(field)) //
                            .withDefault(getFieldDefault(field, output)));
        }
    }

    public static Config fromContext(GenTemplateContext context) {
        return yamlMapper.convertValue(context.getGenTargetParameters(), Config.class);
    }

    public File getNSwagRunFile(Swagger swagger, Output output, File outputFile) throws IOException {
        List serverFields = getNSwagConfigFields(output);
        return buildNSwagRunFile(swagger, getSectionName(output), serverFields, outputFile);
    }

    private String getSectionName(Output output) {
        switch (output) {
        case CS_SERVER:
            return "swaggerToCSharpController";
        case CS_CLIENT:
            return "swaggerToCSharpClient";
        case TS_CLIENT:
            return "swaggerToTypeScriptClient";
        default:
            throw new IllegalArgumentException("Invalid Output option supplied by GenTemplate");
        }
    }

    public static final String NSWAG_TOOL_LOCATION = "com.reprezen.genflow.nswag.location";

    public static String getNSwagLocationDefault() {
        return System.getProperty(NSWAG_TOOL_LOCATION);
    }

    private static List getNSwagConfigFields(Output output) {
        List fields = Lists.newArrayList();
        for (Field field : Config.class.getDeclaredFields()) {
            NSwagParam param = field.getAnnotation(NSwagParam.class);
            if (param != null && paramIsForOutput(param, output)) {
                fields.add(field);
            }
        }
        return fields;
    }

    private static boolean paramIsForOutput(NSwagParam param, Output output) {
        return Arrays.asList(param.output()).contains(output);
    }

    private static String getFieldDescription(Field field) {
        return field.getAnnotation(NSwagParam.class).description();
    }

    private static Object getFieldDefault(Field field, Output output) throws GenerationException {
        NSwagParam param = field.getAnnotation(NSwagParam.class);
        String value = "";
        switch (output) {
        case CS_CLIENT:
            value = param.csClientDefault();
            break;
        case CS_SERVER:
            value = param.csServerDefault();
            break;
        case TS_CLIENT:
            value = param.tsClientDefault();
            break;
        default:
        }
        if (value.isEmpty()) {
            value = param.defaultValue();
        }
        if (value.startsWith("JSON:")) {
            try {
                return jsonMapper.readValue(value.substring("JSON:".length()), Object.class);
            } catch (IOException e) {
                throw new GenerationException("Internal GenTemplate error", e);
            }
        } else {
            return value;
        }
    }

    private File buildNSwagRunFile(Swagger swagger, String sectionName, List sectionFields, File outputFile)
            throws IOException {
        File swaggerFile = writeSwaggerTempFile(swagger);
        File nswagFile = File.createTempFile("NSwagConfig", ".json");
        nswagFile.deleteOnExit();
        ObjectNode root = jsonMapper.createObjectNode();
        mkNodePath(root, "swaggerGenerator", "fromSwagger").put("url", swaggerFile.getAbsolutePath());
        mkNodePath(root, "codeGenerators").set(sectionName, getRunFileObject(sectionFields, outputFile));
        // ZEN-4270 Safe to use: existed in Apache Commins IO v.2.2
        // See
        // https://commons.apache.org/proper/commons-io/javadocs/api-2.2/org/apache/commons/io/FileUtils.html#writeStringToFile(java.io.File,%20java.lang.String)
        FileUtils.writeStringToFile(nswagFile, jsonMapper.writeValueAsString(root));
        return nswagFile;
    }

    private File writeSwaggerTempFile(Swagger swagger) throws IOException {
        File file = File.createTempFile("NSwagInput", ".json");
        file.deleteOnExit();
        // ZEN-4270 Safe to use: existed in Apache Commins IO v.2.2
        // See
        // https://commons.apache.org/proper/commons-io/javadocs/api-2.2/org/apache/commons/io/FileUtils.html#writeStringToFile(java.io.File,%20java.lang.String)
        FileUtils.writeStringToFile(file, jsonMapper.writeValueAsString(swagger));
        return file;
    }

    private ObjectNode mkNodePath(ObjectNode parent, String... path) {
        for (String component : path) {
            ObjectNode child = yamlMapper.createObjectNode();
            parent.set(component, child);
            parent = child;
        }
        return parent;
    }

    private ObjectNode getRunFileObject(List fields, File outputFile) {
        ObjectNode node = yamlMapper.createObjectNode();
        for (Field field : fields) {
            try {
                Object value = field.get(this);
                if (value != null) {
                    node.set(field.getName(), jsonMapper.convertValue(value, JsonNode.class));
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
            }
        }
        node.put("output", outputFile.getAbsolutePath());
        return node;
    }

    public String getnSwagPath() {
        return nSwagPath;
    }

    public enum Output {
        CS_CLIENT, CS_SERVER, TS_CLIENT,
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface NSwagParam {

        public Output[] output() default {};

        public String description() default "";

        public String defaultValue() default "";

        public String csClientDefault() default "";

        public String csServerDefault() default "";

        public String tsClientDefault() default "";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy