org.openapitools.codegen.languages.AbstractFSharpCodegen 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 com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache.Lambda;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
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.templating.mustache.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public abstract class AbstractFSharpCodegen extends DefaultCodegen implements CodegenConfig {
protected boolean optionalAssemblyInfoFlag = true;
protected boolean optionalProjectFileFlag = true;
protected boolean useDateTimeOffsetFlag = false;
protected boolean useCollection = false;
protected boolean returnICollection = false;
protected boolean netCoreProjectFileFlag = false;
protected String modelPropertyNaming = CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.PascalCase.name();
protected String licenseUrl = "http://localhost";
protected String licenseName = "NoLicense";
protected String packageVersion = "1.0.0";
protected String packageName = "OpenAPI";
protected String packageTitle = "OpenAPI Library";
protected String packageProductName = "OpenAPILibrary";
protected String packageDescription = "A library generated from a OpenAPI doc";
protected String packageCompany = "OpenAPI";
protected String packageCopyright = "No Copyright";
protected String packageAuthors = "OpenAPI";
protected String interfacePrefix = "I";
protected String projectFolder = packageName;
protected String sourceFolder = projectFolder + File.separator + "src";
protected String testFolder = projectFolder + ".Tests";
protected Set collectionTypes;
protected Set mapTypes;
// true if nullable types will be supported (as option)
protected boolean supportNullable = Boolean.TRUE;
protected Set nullableType = new HashSet<>();
private final Logger LOGGER = LoggerFactory.getLogger(AbstractFSharpCodegen.class);
public AbstractFSharpCodegen() {
super();
supportsInheritance = true;
importMapping.clear();
importMapping.put("IDictionary", "System.Collections.Generic");
outputFolder = this.getName();
embeddedTemplateDir = templateDir = this.getName();
collectionTypes = new HashSet<>(Arrays.asList("list", "seq"));
mapTypes = new HashSet<>(
Arrays.asList("IDictionary")
);
reservedWords.addAll(
Arrays.asList(
// local variable names in API methods (endpoints)
"localVarPath", "localVarPathParams", "localVarQueryParams", "localVarHeaderParams",
"localVarFormParams", "localVarFileParams", "localVarStatusCode", "localVarResponse",
"localVarPostBody", "localVarHttpHeaderAccepts", "localVarHttpHeaderAccept",
"localVarHttpContentTypes", "localVarHttpContentType",
"localVarStatusCode",
// F# reserved words
"abstract", "and", "as", "async", "await", "assert", "base", "begin", "bool", "break", "byte", "case", "catch", "char", "checked",
"class", "const", "continue", "decimal", "default", "delegate", "do", "done", "double", "downcast", "downto", "dynamic",
"elif", "else", "end", "enum", "event", "exception", "explicit", "extern", "false", "finally", "fixed", "float", "for",
"foreach", "fun", "function", "if", "in", "inherit", "inline", "int", "interface", "internal", "is", "lazy", "let", "let!", "lock",
"match", "match!", "member", "module", "mutable", "namespace", "new", "not", "null", "of", "open", "option", "or", "override", "params",
"private", "public", "raise", "rec", "return", "return!", "sealed", "select", "static", "string", "struct", "then", "to",
"true", "try", "type", "upcast", "use", "use!", "val", "void", "volatile", "when", "while", "with", "yield", "yield!")
);
// TODO - these are based on C# generator, do we need to add any more?
languageSpecificPrimitives = new HashSet<>(
Arrays.asList(
"String",
"string",
"bool",
"char",
"decimal",
"int",
"int16",
"int64",
"nativeint",
"unativeint",
"uint16",
"uint32",
"uint64",
"float",
"byte[]",
"ICollection",
"Collection",
"list",
"dict",
"seq",
"Dictionary",
"List",
"DateTime",
"DataTimeOffset",
"Double",
"Int32",
"Int64",
"float",
"float32",
"single",
"double",
"System.IO.Stream", // not really a primitive, we include it to avoid model import
"obj")
);
instantiationTypes.put("array", "list");
instantiationTypes.put("list", "list");
instantiationTypes.put("map", "IDictionary");
typeMapping = new HashMap<>();
typeMapping.put("string", "string");
typeMapping.put("binary", "byte[]");
typeMapping.put("ByteArray", "byte[]");
typeMapping.put("boolean", "bool");
typeMapping.put("integer", "int");
typeMapping.put("float", "float");
typeMapping.put("long", "int64");
typeMapping.put("double", "double");
typeMapping.put("number", "decimal");
typeMapping.put("DateTime", "DateTime");
typeMapping.put("date", "DateTime");
typeMapping.put("file", "System.IO.Stream");
typeMapping.put("array", "list");
typeMapping.put("list", "list");
typeMapping.put("map", "IDictionary");
typeMapping.put("object", "obj");
typeMapping.put("UUID", "Guid");
typeMapping.put("URI", "string");
// nullable type
nullableType = new HashSet<>(
Arrays.asList("decimal", "bool", "int", "float", "long", "double", "string", "Guid", "apiKey")
);
}
public void setReturnICollection(boolean returnICollection) {
this.returnICollection = returnICollection;
}
public void setUseCollection(boolean useCollection) {
this.useCollection = useCollection;
if (useCollection) {
typeMapping.put("array", "seq");
typeMapping.put("list", "seq");
instantiationTypes.put("array", "seq");
instantiationTypes.put("list", "seq");
}
}
public void setNetCoreProjectFileFlag(boolean flag) {
this.netCoreProjectFileFlag = flag;
}
public void useDateTimeOffset(boolean flag) {
this.useDateTimeOffsetFlag = flag;
if (flag) {
typeMapping.put("DateTime", "DateTimeOffset?");
} else {
typeMapping.put("DateTime", "DateTime?");
}
}
@Override
public void processOpts() {
super.processOpts();
// License info
if (additionalProperties.containsKey(CodegenConstants.LICENSE_URL)) {
setLicenseUrl((String) additionalProperties.get(CodegenConstants.LICENSE_URL));
} else {
additionalProperties.put(CodegenConstants.LICENSE_URL, this.licenseUrl);
}
if (additionalProperties.containsKey(CodegenConstants.LICENSE_NAME)) {
setLicenseName((String) additionalProperties.get(CodegenConstants.LICENSE_NAME));
} else {
additionalProperties.put(CodegenConstants.LICENSE_NAME, this.licenseName);
}
// {{packageVersion}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
}
// {{sourceFolder}}
if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
} else {
additionalProperties.put(CodegenConstants.SOURCE_FOLDER, this.sourceFolder);
}
// {{packageName}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
}
// {{packageTitle}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_TITLE)) {
setPackageTitle((String) additionalProperties.get(CodegenConstants.PACKAGE_TITLE));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_TITLE, packageTitle);
}
// {{packageProductName}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_PRODUCTNAME)) {
setPackageProductName((String) additionalProperties.get(CodegenConstants.PACKAGE_PRODUCTNAME));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_PRODUCTNAME, packageProductName);
}
// {{packageDescription}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_DESCRIPTION)) {
setPackageDescription((String) additionalProperties.get(CodegenConstants.PACKAGE_DESCRIPTION));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_DESCRIPTION, packageDescription);
}
// {{packageCompany}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COMPANY)) {
setPackageCompany((String) additionalProperties.get(CodegenConstants.PACKAGE_COMPANY));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_COMPANY, packageCompany);
}
// {{packageCopyright}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COPYRIGHT)) {
setPackageCopyright((String) additionalProperties.get(CodegenConstants.PACKAGE_COPYRIGHT));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_COPYRIGHT, packageCopyright);
}
// {{packageAuthors}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_AUTHORS)) {
setPackageAuthors((String) additionalProperties.get(CodegenConstants.PACKAGE_AUTHORS));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_AUTHORS, packageAuthors);
}
// {{useDateTimeOffset}}
if (additionalProperties.containsKey(CodegenConstants.USE_DATETIME_OFFSET)) {
useDateTimeOffset(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DATETIME_OFFSET));
} else {
additionalProperties.put(CodegenConstants.USE_DATETIME_OFFSET, useDateTimeOffsetFlag);
}
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
// This either updates additionalProperties with the above fixes, or sets the default if the option was not specified.
additionalProperties.put(CodegenConstants.INTERFACE_PREFIX, interfacePrefix);
}
@Override
protected ImmutableMap.Builder addMustacheLambdas() {
return super.addMustacheLambdas()
.put("camelcase_param", new CamelCaseAndSanitizeLambda().generator(this).escapeAsParamName(true));
}
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
super.postProcessModels(objs);
for (ModelMap mo : objs.getModels()) {
CodegenModel cm = mo.getModel();
for (CodegenProperty var : cm.vars) {
// check to see if model name is same as the property name
// which will result in compilation error
// if found, prepend with _ to workaround the limitation
if (var.name.equalsIgnoreCase(cm.name)) {
var.name = "_" + var.name;
}
}
}
// process enum in models
return postProcessModelsEnum(objs);
}
/**
* Invoked by {@link DefaultGenerator} after all models have been post-processed, allowing for a last pass of codegen-specific model cleanup.
*
* @param objs Current state of codegen object model.
* @return (ew) modified state of the codegen object model.
*/
@Override
public Map postProcessAllModels(Map objs) {
final Map processed = super.postProcessAllModels(objs);
postProcessEnumRefs(processed);
return postProcessDependencyOrders(processed);
}
/*
* F# does not allow forward declarations, so files must be imported in the correct order.
* Output of CodeGen models must therefore be in dependency order (rather than alphabetical order, which seems to be the default).
* This could probably be made more efficient if absolutely needed.
*/
public Map postProcessDependencyOrders(final Map objs) {
Map> dependencies = new HashMap<>();
List classNames = new ArrayList<>();
for (String k : objs.keySet()) {
CodegenModel model = ModelUtils.getModelByName(k, objs);
if (model == null || model.classname == null) {
throw new RuntimeException("Null model encountered");
}
dependencies.put(model.classname, model.imports);
classNames.add(model.classname);
}
Object[] sortedKeys = classNames.toArray();
for (int i1 = 0; i1 < sortedKeys.length; i1++) {
String k1 = sortedKeys[i1].toString();
for (int i2 = i1 + 1; i2 < sortedKeys.length; i2++) {
String k2 = sortedKeys[i2].toString();
if (dependencies.get(k2).contains(k1)) {
sortedKeys[i2] = k1;
sortedKeys[i1] = k2;
i1 = -1;
break;
}
}
}
Map sorted = new LinkedHashMap<>();
for (int i = sortedKeys.length - 1; i >= 0; i--) {
Object k = sortedKeys[i];
sorted.put(k.toString(), objs.get(k));
}
return sorted;
}
/**
* F# differs from other languages in that Enums are not _true_ objects; enums are compiled to integral types.
* So, in F#, an enum is considers more like a user-defined primitive.
*
* When working with enums, we can't always assume a RefModel is a nullable type (where default(YourType) == null),
* so this post processing runs through all models to find RefModel'd enums. Then, it runs through all vars and modifies
* those vars referencing RefModel'd enums to work the same as inlined enums rather than as objects.
*
* @param models processed models to be further processed for enum references
*/
@SuppressWarnings("unchecked")
private void postProcessEnumRefs(final Map models) {
Map enumRefs = new HashMap<>();
for (String key : models.keySet()) {
CodegenModel model = ModelUtils.getModelByName(key, models);
if (model.isEnum) {
enumRefs.put(key, model);
}
}
for (String openAPIName : models.keySet()) {
CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
if (model != null) {
for (CodegenProperty var : model.allVars) {
if (enumRefs.containsKey(var.dataType)) {
// Handle any enum properties referred to by $ref.
// This is different in F# than most other generators, because enums in C# are compiled to integral types,
// while enums in many other languages are true objects.
CodegenModel refModel = enumRefs.get(var.dataType);
var.allowableValues = refModel.allowableValues;
var.isEnum = true;
// We do these after updateCodegenPropertyEnum to avoid generalities that don't mesh with C#.
var.isPrimitiveType = true;
}
}
// We're looping all models here.
if (model.isEnum) {
// We now need to make allowableValues.enumVars look like the context of CodegenProperty
Boolean isString = false;
Boolean isInteger = false;
Boolean isLong = false;
Boolean isByte = false;
if (model.dataType.startsWith("byte")) {
// F# Actually supports byte and short enums, swagger spec only supports byte.
isByte = true;
model.vendorExtensions.put("x-enum-byte", true);
} else if (model.dataType.startsWith("int32")) {
isInteger = true;
model.vendorExtensions.put("x-enum-integer", true);
} else if (model.dataType.startsWith("int64")) {
isLong = true;
model.vendorExtensions.put("x-enum-long", true);
} else {
// F# doesn't support non-integral enums, so we need to treat everything else as strings (e.g. to not lose precision or data integrity)
isString = true;
model.vendorExtensions.put("x-enum-string", true);
}
// Since we iterate enumVars for modelInnerEnum and enumClass templates, and CodegenModel is missing some of CodegenProperty's properties,
// we can take advantage of Mustache's contextual lookup to add the same "properties" to the model's enumVars scope rather than CodegenProperty's scope.
List