Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openapitools.codegen.languages.N4jsClientCodegen Maven / Gradle / Ivy
/*
* 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 static org.openapitools.codegen.CodegenConstants.API_NAME_PREFIX;
import static org.openapitools.codegen.CodegenConstants.API_NAME_PREFIX_DESC;
import static org.openapitools.codegen.CodegenConstants.API_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.API_PACKAGE_DESC;
import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE_DESC;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.CodegenSecurity;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.IJsonSchemaValidationProperties;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.config.GlobalSettings;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
public class N4jsClientCodegen extends DefaultCodegen implements CodegenConfig {
public static final String CHECK_REQUIRED_PARAMS_NOT_NULL = "checkRequiredParamsNotNull";
public static final String CHECK_SUPERFLUOUS_BODY_PROPS = "checkSuperfluousBodyProps";
public static final String GENERATE_DEFAULT_API_EXECUTER = "generateDefaultApiExecuter";
final Logger LOGGER = LoggerFactory.getLogger(N4jsClientCodegen.class);
final Set forbiddenChars = new HashSet<>();
private boolean checkRequiredBodyPropsNotNull = true;
private boolean checkSuperfluousBodyProps = true;
private boolean generateDefaultApiExecuter = true;
public CodegenType getTag() {
return CodegenType.CLIENT;
}
public String getName() {
return "n4js";
}
public String getHelp() {
return "Generates a n4js client.";
}
public N4jsClientCodegen() {
super();
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.BETA)
.build();
specialCharReplacements.clear();
outputFolder = "generated-code" + File.separator + "n4js";
modelTemplateFiles.put("model.mustache", ".n4jsd");
apiTemplateFiles.put("api.mustache", ".n4js");
embeddedTemplateDir = templateDir = "n4js";
apiPackage = "";
modelPackage = "";
typeMapping = new HashMap();
typeMapping.put("Set", "Set");
typeMapping.put("set", "Set");
typeMapping.put("Array", "Array");
typeMapping.put("array", "Array");
typeMapping.put("boolean", "boolean");
typeMapping.put("string", "string");
typeMapping.put("char", "string");
typeMapping.put("float", "number");
typeMapping.put("long", "int");
typeMapping.put("short", "int");
typeMapping.put("int", "int");
typeMapping.put("integer", "int");
typeMapping.put("number", "number");
typeMapping.put("double", "number");
typeMapping.put("object", "object");
typeMapping.put("Map", "any");
typeMapping.put("map", "any");
typeMapping.put("date", "string");
typeMapping.put("DateTime", "string");
typeMapping.put("binary", "any");
typeMapping.put("File", "any");
typeMapping.put("file", "any");
typeMapping.put("ByteArray", "string");
typeMapping.put("UUID", "string");
typeMapping.put("URI", "string");
typeMapping.put("Error", "Error");
typeMapping.put("AnyType", "any");
importMapping.clear(); // not used
supportsInheritance = true;
supportsMultipleInheritance = false;
reservedWords.addAll(Arrays.asList(
// local variable names used in API methods (endpoints)
"varLocalPath", "queryParameters", "headerParams", "formParams", "useFormData", "varLocalDeferred",
"requestOptions",
// N4JS reserved words
"abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue",
"debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final",
"finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int",
"interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public",
"return", "short", "static", "super", "switch", "synchronized", "this", "throw", "transient", "true",
"try", "typeof", "var", "void", "volatile", "while", "with", "yield"));
languageSpecificPrimitives = new HashSet<>(Arrays.asList("string", "String", "boolean", "number", "int",
"Object", "object", "Array", "any", "any+", "Error"));
defaultIncludes.add("~Object+");
defaultIncludes.add("Object+");
forbiddenChars.add("@");
cliOptions.clear();
cliOptions.add(new CliOption(API_PACKAGE, API_PACKAGE_DESC));
cliOptions.add(new CliOption(MODEL_PACKAGE, MODEL_PACKAGE_DESC));
cliOptions.add(new CliOption(API_NAME_PREFIX, API_NAME_PREFIX_DESC));
cliOptions.add(new CliOption(CHECK_REQUIRED_PARAMS_NOT_NULL,
"Iff true null-checks are performed for required parameters."));
cliOptions.add(new CliOption(CHECK_SUPERFLUOUS_BODY_PROPS,
"Iff true a new copy of the given body object is transmitted. This copy only contains those properties defined in its model specification."));
cliOptions.add(new CliOption(GENERATE_DEFAULT_API_EXECUTER,
"Iff true a default implementation of the api executer interface is generated."));
}
@Override
public void processOpts() {
super.processOpts();
// disable since otherwise Modules/Classes are not generated iff used as
// parameters only
GlobalSettings.setProperty("skipFormModel", "false");
supportingFiles.clear();
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("ApiHelper.mustache", apiPackage, "ApiHelper.n4js"));
checkRequiredBodyPropsNotNull = processBooleanOpt(CHECK_REQUIRED_PARAMS_NOT_NULL,
checkRequiredBodyPropsNotNull);
checkSuperfluousBodyProps = processBooleanOpt(CHECK_SUPERFLUOUS_BODY_PROPS, checkSuperfluousBodyProps);
generateDefaultApiExecuter = processBooleanOpt(GENERATE_DEFAULT_API_EXECUTER, generateDefaultApiExecuter);
if (additionalProperties.get(API_PACKAGE) instanceof String) {
apiPackage = additionalProperties.get(API_PACKAGE).toString();
} else {
additionalProperties.put(API_PACKAGE, apiPackage);
}
if (additionalProperties.get(MODEL_PACKAGE) instanceof String) {
modelPackage = additionalProperties.get(MODEL_PACKAGE).toString();
} else {
additionalProperties.put(MODEL_PACKAGE, modelPackage);
}
if (additionalProperties.get(API_NAME_PREFIX) instanceof String) {
apiNamePrefix = additionalProperties.get(API_NAME_PREFIX).toString();
} else {
additionalProperties.put(API_NAME_PREFIX, apiNamePrefix);
}
}
private boolean processBooleanOpt(String OPT, boolean defaultValue) {
boolean passedValue = defaultValue;
if (additionalProperties.containsKey(OPT)) {
Object value = additionalProperties.get(OPT);
if (value instanceof Boolean) {
passedValue = (Boolean) value;
} else {
try {
passedValue = Boolean.parseBoolean(value.toString());
} catch (Exception e) {
// ignore
}
}
}
additionalProperties.put(OPT, passedValue);
return defaultValue;
}
@Override
public String toModelFilename(String name) {
String modelFilename = super.toModelFilename(name);
if (typeMapping.containsKey(modelFilename) || defaultIncludes.contains(modelFilename)) {
return modelFilename;
}
return modelFilename;
}
public boolean checkRequiredBodyPropsNotNull() {
return checkRequiredBodyPropsNotNull;
}
public boolean checkSuperfluousBodyProps() {
return checkSuperfluousBodyProps;
}
public boolean generateDefaultApiExecuter() {
return generateDefaultApiExecuter;
}
@Override
public boolean getUseInlineModelResolver() {
return false;
}
@Override
public void setOpenAPI(OpenAPI openAPI) {
super.setOpenAPI(openAPI);
typeAliases.put("object", "~Object+");
}
@Override
protected boolean isReservedWord(String word) {
// case sensitive matching
return reservedWords.contains(word);
}
@Override
public String toAnyOfName(List names, Schema composedSchema) {
List types = getTypesFromSchemas(composedSchema.getAnyOf());
return String.join(" | ", types);
}
@Override
public String toOneOfName(List names, Schema composedSchema) {
List types = getTypesFromSchemas(composedSchema.getOneOf());
return String.join(" | ", types);
}
@Override
public String toAllOfName(List names, Schema composedSchema) {
List types = getTypesFromSchemas(composedSchema.getAllOf());
return String.join(" & ", types);
}
/**
* Extracts the list of type names from a list of schemas. Excludes `AnyType` if
* there are other valid types extracted.
*
* @param schemas list of schemas
* @return list of types
*/
@SuppressWarnings("rawtypes")
protected List getTypesFromSchemas(List schemas) {
List filteredSchemas = schemas.size() > 1 ? schemas.stream()
.filter(schema -> !"AnyType".equals(super.getSchemaType(schema))).collect(Collectors.toList())
: schemas;
return filteredSchemas.stream().map(schema -> getTypeDeclaration(schema)).distinct()
.collect(Collectors.toList());
}
@Override
protected void addImports(Set importsToBeAddedTo, IJsonSchemaValidationProperties type) {
Set imports = type.getImports(importContainerType, importBaseType, generatorMetadata.getFeatureSet());
Set mappedImports = new HashSet<>();
for (String imp : imports) {
String mappedImp = imp;
if (typeMapping.containsKey(imp)) {
mappedImp = typeMapping.get(imp);
} else {
mappedImp = imp;
}
mappedImports.add(mappedImp);
}
addImports(importsToBeAddedTo, mappedImports);
}
@Override
protected void addImport(Set importsToBeAddedTo, String type) {
String[] parts = splitComposedType(type);
for (String s : parts) {
super.addImport(importsToBeAddedTo, s);
}
}
private String[] splitComposedType(String name) {
return name.replace(" ", "").split("[|&<>]");
}
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
objs = super.postProcessModels(objs);
for (ModelMap modelMap : objs.getModels()) {
CodegenModel cgModel = modelMap.getModel();
if (cgModel.unescapedDescription != null && !cgModel.unescapedDescription.contains("\n * ")) {
cgModel.description = escapeTextWhileAllowingNewLines(cgModel.unescapedDescription.trim()).replace("\n",
"\n * ");
}
}
postProcessModelsEnum(objs); // enable enums
return objs;
}
@Override
protected void addImportsForPropertyType(CodegenModel model, CodegenProperty property) {
if (model.getIsAnyType()) {
return; // disable (unused) imports created for properties of type aliases
}
super.addImportsForPropertyType(model, property);
}
@Override
public Map postProcessAllModels(Map objs) {
objs = super.postProcessAllModels(objs);
for (String modelName : objs.keySet()) {
ModelsMap modelsMap = objs.get(modelName);
// imports
List> imports = modelsMap.getImports();
ArrayList> n4jsImports = new ArrayList>();
modelsMap.put("n4jsimports", n4jsImports);
String className = modelsMap.get("classname").toString();
for (Map imp : imports) {
Map n4jsImport = toN4jsImports(className, objs, imp);
if (n4jsImport != null) {
n4jsImports.add(n4jsImport);
}
}
// app description -> module documentation
adjustDescriptionWithNewLines(modelsMap);
}
return objs;
}
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap operations, List allModels) {
OperationMap objs = operations.getOperations();
boolean needImportCleanCopyBody = false;
// The api.mustache template requires all of the auth methods for the whole api
// Loop over all the operations and pick out each unique auth method
Map authMethodsMap = new HashMap<>();
for (CodegenOperation op : objs.getOperation()) {
if (op.hasAuthMethods) {
for (CodegenSecurity sec : op.authMethods) {
authMethodsMap.put(sec.name, sec);
}
}
if (op.bodyParam != null && !op.bodyParam.vars.isEmpty()) {
needImportCleanCopyBody = true;
}
if (op.responses != null && op.responses.size() > 0) {
Map responses2xx = new LinkedHashMap<>();
Map responses4xx = new LinkedHashMap<>();
for (CodegenResponse response : op.responses) {
if (response.is2xx) {
responses2xx.put(response.baseType, response);
}
if (response.is4xx) {
responses4xx.put(response.baseType, response);
}
}
op.vendorExtensions.put("responses2xx", new ArrayList<>(responses2xx.values()));
op.vendorExtensions.put("responses4xx", new ArrayList<>(responses4xx.values()));
}
}
operations.put("needImportCleanCopyBody", needImportCleanCopyBody);
// If there were any auth methods specified add them to the operations context
if (!authMethodsMap.isEmpty()) {
operations.put("authMethods", authMethodsMap.values());
operations.put("hasAuthMethods", true);
}
// Add additional filename information for model imports in the apis
Iterator> iter = operations.getImports().iterator();
while (iter.hasNext()) {
Map im = iter.next();
String className = im.get("classname");
className = convertToModelName(className);
String adjClassName = typeMapping.getOrDefault(className, className);
if (needToImport(adjClassName)) {
im.put("classname", className);
im.put("filename", toModelImport(className));
} else {
iter.remove();
}
}
// app description -> module documentation
adjustDescriptionWithNewLines(additionalProperties);
return operations;
}
private String convertToModelName(String modelName) {
if (modelName == null) {
return modelName;
}
Schema schema = ModelUtils.getSchema(openAPI, modelName);
if (schema == null) {
return modelName;
}
if (ModelUtils.isObjectSchema(schema)) {
return toModelFilename(modelName);
}
return modelName;
}
private void adjustDescriptionWithNewLines(Map map) {
if (map.containsKey("appDescriptionWithNewLines")
&& !map.get("appDescriptionWithNewLines").toString().contains("\n * ")) {
String appDescriptionWithNewLines = map.get("appDescriptionWithNewLines").toString();
appDescriptionWithNewLines = appDescriptionWithNewLines.trim().replace("\n", "\n * ");
map.put("appDescriptionWithNewLines", appDescriptionWithNewLines);
}
}
private Map toN4jsImports(String className, Map objs, Map imp) {
String modelImpName = imp.get("import");
if (modelImpName == null) {
return null;
}
String modelName = fromModelImport(modelImpName);
if (!objs.containsKey(modelName)) {
return null;
}
ModelsMap modelsMap = objs.get(modelName);
String impClassName = modelsMap.get("classname").toString();
if (impClassName == null || Objects.equals(impClassName, className)) {
return null;
}
Map n4jsImport = new HashMap<>();
n4jsImport.put("elementname", impClassName);
n4jsImport.put("modulename", modelImpName);
return n4jsImport;
}
@Override
public String toModelImport(String name) {
if ("".equals(modelPackage())) {
return name;
} else {
return modelPackage() + "/" + name;
}
}
protected String fromModelImport(String modelImportName) {
if ("".equals(modelPackage())) {
return modelImportName;
} else if (modelImportName == null) {
return modelImportName;
} else {
if (modelImportName.startsWith(modelPackage() + "/")) {
String nameWithoutModelPackage = modelImportName.substring(1 + modelPackage().length());
if (modelNamePrefix != null && nameWithoutModelPackage.startsWith(modelNamePrefix)) {
return nameWithoutModelPackage.substring(modelNamePrefix.length());
}
return nameWithoutModelPackage;
}
return modelImportName;
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
Schema items = getSchemaItems((ArraySchema) p);
return getTypeDeclaration(unaliasSchema(items)) + "[]";
} else if (ModelUtils.isMapSchema(p)) {
return "~Object+";
} else if (ModelUtils.isStringSchema(p)) {
if (p.getEnum() != null) {
return enumValuesToEnumTypeUnion(p.getEnum(), "string");
}
} else if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p)) {
// Handle integer and double enums
if (p.getEnum() != null) {
return numericEnumValuesToEnumTypeUnion(new ArrayList(p.getEnum()));
}
} else if (ModelUtils.isFileSchema(p)) {
return "File";
} else if (ModelUtils.isObjectSchema(p)
|| ModelUtils.isObjectSchema(ModelUtils.getReferencedSchema(openAPI, p))) {
String result = super.getTypeDeclaration(p);
return toModelFilename(result);
} else if (ModelUtils.isBinarySchema(p)) {
return "ArrayBuffer";
}
return super.getTypeDeclaration(p);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
protected String getParameterDataType(Parameter parameter, Schema p) {
// handle enums of various data types
if (ModelUtils.isArraySchema(p)) {
ArraySchema mp1 = (ArraySchema) p;
Schema inner = mp1.getItems();
return getParameterDataType(parameter, inner) + "[]";
} else if (ModelUtils.isMapSchema(p)) {
return "~Object+";
} else if (ModelUtils.isStringSchema(p)) {
// Handle string enums
if (p.getEnum() != null) {
return enumValuesToEnumTypeUnion(p.getEnum(), "string");
}
} else if (ModelUtils.isObjectSchema(p)
|| ModelUtils.isObjectSchema(ModelUtils.getReferencedSchema(openAPI, p))) {
String result = super.getTypeDeclaration(p);
return toModelFilename(result);
} else if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p)) {
// Handle integer and double enums
if (p.getEnum() != null) {
return numericEnumValuesToEnumTypeUnion(new ArrayList(p.getEnum()));
}
}
return this.getTypeDeclaration(p);
}
@Override
protected String getSingleSchemaType(@SuppressWarnings("rawtypes") Schema schema) {
Schema unaliasSchema = unaliasSchema(schema);
if (StringUtils.isNotBlank(unaliasSchema.get$ref())) {
String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
if (StringUtils.isNotEmpty(schemaName)) {
if (schemaMapping.containsKey(schemaName)) {
return schemaName;
}
}
}
return super.getSingleSchemaType(unaliasSchema);
}
/**
* Converts a list of strings to a literal union for representing enum values as
* a type. Example output: 'available' | 'pending' | 'sold'
*
* @param values list of allowed enum values
* @param dataType either "string" or "number"
* @return a literal union for representing enum values as a type
*/
private String enumValuesToEnumTypeUnion(List values, String dataType) {
StringBuilder b = new StringBuilder();
boolean isFirst = true;
for (String value : values) {
if (!isFirst) {
b.append(" | ");
}
b.append(toEnumValue(value, dataType));
isFirst = false;
}
return b.toString();
}
/**
* Converts a list of numbers to a literal union for representing enum values as
* a type. Example output: 3 | 9 | 55
*
* @param values a list of numbers
* @return a literal union for representing enum values as a type
*/
private String numericEnumValuesToEnumTypeUnion(List values) {
List stringValues = new ArrayList<>();
for (Number value : values) {
stringValues.add(value.toString());
}
return enumValuesToEnumTypeUnion(stringValues, "number");
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
if (property.unescapedDescription != null && property.unescapedDescription.contains("\n")) {
property.description = escapeTextWhileAllowingNewLines(property.unescapedDescription.trim()).replace("\n",
"\n * ");
}
}
@Override
public String escapeText(String input) {
input = escapeTextWhileAllowingNewLines(input);
if (input == null) {
return input;
}
// remove \n, \r
return input.replaceAll("[\\n\\r]", " ");
}
@Override
public String escapeTextWhileAllowingNewLines(String input) {
if (input == null) {
return input;
}
// remove \t
// outer unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
return escapeUnsafeCharacters(
StringEscapeUtils.unescapeEcmaScript(StringEscapeUtils.escapeEcmaScript(input).replace("\\/", "/"))
.replaceAll("[\\t]", " "));
}
@Override
public String escapeReservedWord(String name) {
return "_" + name;
}
@Override
public String toVarName(final String name) {
String name2 = super.toVarName(name);
for (String forbiddenChar : forbiddenChars) {
if (name2.contains(forbiddenChar)) {
return "[\"" + name2 + "\"]";
}
}
return name2;
}
@Override
public String toParamName(String name) {
String name2 = super.toParamName(name);
for (String forbiddenChar : forbiddenChars) {
if (name2.contains(forbiddenChar)) {
return "[\"" + name2 + "\"]";
}
}
return name2;
}
@Override
public String escapeQuotationMark(String input) {
// remove ', " to avoid code injection
return input.replace("\"", "").replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
}