org.openapitools.codegen.languages.AbstractAdaCodegen Maven / Gradle / Ivy
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
*
* 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.samskivert.mustache.Escapers;
import com.samskivert.mustache.Mustache;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.ClientModificationFeature;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.GlobalFeature;
import org.openapitools.codegen.meta.features.SchemaSupportFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
import org.openapitools.codegen.meta.features.WireFormatFeature;
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 java.io.File;
import java.util.*;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.StringUtils.camelize;
abstract public class AbstractAdaCodegen extends DefaultCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(AbstractAdaCodegen.class);
public static final String HTTP_SUPPORT_OPTION = "httpSupport";
public static final String OPENAPI_PACKAGE_NAME_OPTION = "openApiName";
// Common media types.
private static final String APPLICATION_XML = "application/xml";
private static final String TEXT_XML = "text/xml";
private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
private static final String TEXT_PLAIN = "text/plain";
private static final String APPLICATION_JSON = "application/json";
private static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
// RFC 7807 Support
private static final String APPLICATION_PROBLEM_JSON = "application/problem+json";
private static final String APPLICATION_PROBLEM_XML = "application/problem+xml";
// RFC 7386 support
private static final String APPLICATION_MERGE_PATCH_JSON = "application/merge-patch+json";
// Extension attributes used by the Ada code generator
// "x-ada-type-name" allows to override the name generated for a type/object.
// It can be a full qualified Ada type name and the type can be defined in an external package.
// In that case, we don't generate the Ada type but use its external definition. Only the
// Serialize/Deserialize are generated in the model. If there is an inconsistency, this will
// be detected at compilation time.
// "x-ada-no-vector" instructs to not instantiate the Vectors package for the given model type.
// "x-ada-serialize-op" allows to control the name of the serialize operation for the field.
private static final String X_ADA_TYPE_NAME = "x-ada-type-name";
private static final String X_ADA_VECTOR_TYPE_NAME = "x-ada-vector-type-name";
private static final String X_ADA_NO_VECTOR = "x-ada-no-vector";
private static final String X_ADA_SERIALIZE_OP = "x-ada-serialize-op";
protected String packageName = "defaultPackage";
protected String projectName = "defaultProject";
protected List orderedModels;
protected final Map> modelDepends;
protected final Map nullableTypeMapping;
protected final Map operationsScopes;
protected final List> mediaGroups;
protected final List> mediaLists;
protected final Map mediaToVariableName;
protected final List mediaVariables;
protected final List adaImports;
protected final Set adaImportSet = new TreeSet<>();
protected int scopeIndex = 0;
protected String httpClientPackageName = "Curl";
protected String openApiPackageName = "Swagger";
private static final String bytesType = "swagger::ByteArray";
static class NameBinding {
public int position;
public String name;
public String value;
NameBinding(int pos, String name, String value) {
this.position = pos;
this.name = name;
this.value = value;
}
}
public AbstractAdaCodegen() {
super();
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML))
.securityFeatures(EnumSet.noneOf(
SecurityFeature.class
))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.includeClientModificationFeatures(
ClientModificationFeature.BasePath
)
);
/*
* Reserved words. Override this with reserved words specific to your language
*/
setReservedWordsLowerCase(
Arrays.asList(
"abort",
"abs",
"abstract",
"accept",
"access",
"aliased",
"all",
"and",
"array",
"at",
"begin",
"body",
"case",
"constant",
"declare",
"delay",
"digits",
"do",
"else",
"elsif",
"end",
"entry",
"exception",
"exit",
"for",
"function",
"generic",
"goto",
"if",
"in",
"interface",
"is",
"limited",
"loop",
"mod",
"new",
"not",
"null",
"of",
"or",
"others",
"out",
"overriding",
"package",
"pragma",
"private",
"procedure",
"protected",
"raise",
"range",
"record",
"rem",
"renames",
"requeue",
"return",
"reverse",
"select",
"separate",
"some",
"subtype",
"synchronized",
"tagged",
"task",
"terminate",
"then",
"type",
"until",
"use",
"when",
"while",
"with",
"xor")
);
typeMapping = new HashMap<>();
typeMapping.put("integer", "Integer");
typeMapping.put("boolean", "Boolean");
typeMapping.put("binary", bytesType);
typeMapping.put("ByteArray", bytesType);
// Mapping to convert an Ada required type to an optional type (nullable).
nullableTypeMapping = new HashMap<>();
modelDepends = new HashMap<>();
orderedModels = new ArrayList<>();
operationsScopes = new HashMap<>();
mediaGroups = new ArrayList<>();
mediaLists = new ArrayList<>();
mediaToVariableName = new HashMap<>();
mediaVariables = new ArrayList<>();
adaImports = new ArrayList<>();
super.importMapping = new HashMap<>();
// CLI options
addOption(CodegenConstants.PROJECT_NAME, "GNAT project name",
this.projectName);
cliOptions.add(CliOption.newString(HTTP_SUPPORT_OPTION, "The name of the HTTP support library. Possible values include 'curl' or 'aws'."));
cliOptions.add(CliOption.newString(OPENAPI_PACKAGE_NAME_OPTION, "The name of the Ada package which provides support for OpenAPI for the generated client and server code. The default is 'Swagger'."));
modelNameSuffix = "Type";
embeddedTemplateDir = templateDir = "Ada";
languageSpecificPrimitives = new HashSet<>(
Arrays.asList("integer", "boolean", "number", "long", "float",
"double", "object", "string", "date", "DateTime", "binary"));
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(HTTP_SUPPORT_OPTION)) {
String httpSupport = additionalProperties.get(HTTP_SUPPORT_OPTION).toString().toLowerCase(Locale.ROOT);
if ("aws".equals(httpSupport)) {
this.httpClientPackageName = "Aws";
} else if ("curl".equals(httpSupport)) {
this.httpClientPackageName = "Curl";
} else {
LOGGER.error("invalid http support option `{}`", httpSupport);
}
}
if (additionalProperties.containsKey(OPENAPI_PACKAGE_NAME_OPTION)) {
this.openApiPackageName = additionalProperties.get(OPENAPI_PACKAGE_NAME_OPTION).toString();
}
typeMapping.put("date", openApiPackageName + ".Date");
typeMapping.put("DateTime", openApiPackageName + ".Datetime");
typeMapping.put("string", openApiPackageName + ".UString");
typeMapping.put("long", openApiPackageName + ".Long");
typeMapping.put("array", openApiPackageName + ".Vector");
typeMapping.put("map", openApiPackageName + ".Map");
typeMapping.put("object", openApiPackageName + ".Object");
typeMapping.put("number", openApiPackageName + ".Number");
typeMapping.put("UUID", openApiPackageName + ".UString");
typeMapping.put("URI", openApiPackageName + ".UString");
typeMapping.put("file", openApiPackageName + ".Blob_Ref");
typeMapping.put("binary", openApiPackageName + ".Blob_Ref");
typeMapping.put("float", openApiPackageName + ".Number");
typeMapping.put("double", openApiPackageName + ".Number");
importMapping.put("File", openApiPackageName + ".File");
// Mapping to convert an Ada required type to an optional type (nullable).
nullableTypeMapping.put(openApiPackageName + ".Date", openApiPackageName + ".Nullable_Date");
nullableTypeMapping.put(openApiPackageName + ".Datetime", openApiPackageName + ".Nullable_Date");
nullableTypeMapping.put(openApiPackageName + ".UString", openApiPackageName + ".Nullable_UString");
nullableTypeMapping.put("Integer", openApiPackageName + ".Nullable_Integer");
nullableTypeMapping.put(openApiPackageName + ".Long", openApiPackageName + ".Nullable_Long");
nullableTypeMapping.put("Boolean", openApiPackageName + ".Nullable_Boolean");
nullableTypeMapping.put(openApiPackageName + ".Object", openApiPackageName + ".Object");
mediaToVariableName.put(TEXT_PLAIN, openApiPackageName + ".Mime_Text");
mediaToVariableName.put(APPLICATION_JSON, openApiPackageName + ".Mime_Json");
mediaToVariableName.put(APPLICATION_XML, openApiPackageName + ".Mime_Xml");
mediaToVariableName.put(APPLICATION_X_WWW_FORM_URLENCODED, openApiPackageName + ".Mime_Form");
}
public String toFilename(String name) {
return name.replace(".", "-").toLowerCase(Locale.ROOT);
}
/**
* Turn a parameter name, operation name into an Ada identifier.
*
* Ada programming standard avoid the camelcase syntax and prefer the underscore
* notation. We also have to make sure the identifier is not a reserved keyword.
* When this happens, we add the configurable prefix. The function translates:
*
* body - P_Body
* petId - Pet_Id
* updatePetWithForm - Update_Pet_With_Form
*
* @param name the parameter name.
* @param prefix the optional prefix in case the parameter name is a reserved keyword.
* @return the Ada identifier to be used.
*/
protected String toAdaIdentifier(String name, String prefix) {
// We cannot use reserved keywords for identifiers
if (isReservedWord(name)) {
LOGGER.warn("Identifier '{}' is a reserved word, renamed to {}{}", name, prefix, name);
name = prefix + name;
}
StringBuilder result = new StringBuilder();
boolean needUpperCase = true;
boolean prevUpperCase = false;
if (name.isEmpty() || Character.isDigit(name.charAt(0)) || name.charAt(0) == '_') {
result.append(prefix);
}
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
boolean isUpperOrDigit = Character.isUpperCase(c) || Character.isDigit(c);
if (needUpperCase) {
needUpperCase = false;
prevUpperCase = isUpperOrDigit;
result.append(Character.toUpperCase(c));
} else if (isUpperOrDigit) {
if (!prevUpperCase) {
result.append('_');
}
result.append(c);
needUpperCase = false;
prevUpperCase = true;
} else {
result.append(c);
prevUpperCase = isUpperOrDigit;
if (c == '_') {
needUpperCase = true;
}
}
}
return result.toString();
}
@Override
public String toOperationId(String operationId) {
return toAdaIdentifier(sanitizeName(operationId), "Call_");
}
@Override
public String toVarName(String name) {
// obtain the name from nameMapping directly if provided
if (nameMapping.containsKey(name)) {
return nameMapping.get(name);
}
return toAdaIdentifier(sanitizeName(name), "P_");
}
@Override
public String toParamName(String name) {
// obtain the name from parameterNameMapping directly if provided
if (parameterNameMapping.containsKey(name)) {
return parameterNameMapping.get(name);
}
return toAdaIdentifier(super.toParamName(name), "P_");
}
/**
* Output the proper model name (capitalized).
* In case the name belongs to the TypeSystem it won't be renamed.
*
* @param name the name of the model
* @return capitalized model name
*/
@Override
public String toModelName(final String name) {
String result = camelize(sanitizeName(name));
if (!StringUtils.isEmpty(modelNamePrefix)) {
result = modelNamePrefix + "_" + result;
}
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(name)) {
String modelName = "Model_" + result;
LOGGER.warn("{} (reserved word) cannot be used as model name. Renamed to {}", name, modelName);
return modelName;
}
// model name starts with number
if (result.matches("^\\d.*")) {
String modelName = "Model_" + result; // e.g. 200Response => Model_200Response (after camelize)
LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name,
modelName);
return modelName;
}
if (languageSpecificPrimitives.contains(result)) {
String modelName = "Model_" + result;
LOGGER.warn("{} (model name matches existing language type) cannot be used as a model name. Renamed to {}",
name, modelName);
return modelName;
}
if (!StringUtils.isEmpty(modelNameSuffix)) {
result = result + "_" + modelNameSuffix;
}
return result;
}
@Override
public String toEnumVarName(String value, String datatype) {
if (enumNameMapping.containsKey(value)) {
return enumNameMapping.get(value);
}
String var;
if (value.isEmpty()) {
var = "EMPTY";
}
// for symbol, e.g. $, #
else if (getSymbolName(value) != null) {
var = getSymbolName(value).toUpperCase(Locale.ROOT);
}
// number
else if ("Integer".equals(datatype) || "Long".equals(datatype) ||
"Float".equals(datatype) || "Double".equals(datatype)) {
String varName = "NUMBER_" + value;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
var = varName;
}
// string
else {
var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT);
if (var.matches("\\d.*")) {
var = "_" + var;
} else {
var = sanitizeName(var);
}
}
return var;
}
@SuppressWarnings("rawtypes")
@Override
public CodegenProperty fromProperty(String name, Schema p, boolean required) {
CodegenProperty property = super.fromProperty(name, p, required);
if (property != null) {
String nameInPascalCase = property.nameInPascalCase;
nameInPascalCase = sanitizeName(nameInPascalCase);
property.nameInPascalCase = nameInPascalCase;
}
return property;
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle
* escaping those terms here. This logic is only called if a variable
* matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
return "p_" + name; // add an underscore to the name
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*").replace("-", "_");
}
/**
* Override the Mustache compiler configuration.
*
* We don't want to have special characters escaped
*
* @param compiler the compiler.
* @return the compiler to use.
*/
@Override
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
compiler = super.processCompiler(compiler).emptyStringIsFalse(true);
return compiler.withEscaper(Escapers.NONE);
}
/**
* Optional - type declaration. This is a String which is used by the
* templates to instantiate your types. There is typically special handling
* for different property types
*
* @return a string value used as the `dataType` field for model templates,
* `returnType` for api templates
*/
@SuppressWarnings("rawtypes")
@Override
public String getTypeDeclaration(Schema p) {
String schemaType = getSchemaType(p);
if (schemaType != null) {
schemaType = schemaType.replace("-", "_");
}
if (ModelUtils.isArraySchema(p)) {
Schema inner = ModelUtils.getSchemaItems(p);
String itemType = getTypeDeclaration(inner);
if (itemType.startsWith("OpenAPI.")) {
return itemType + "_Vector";
} else {
return itemType + "_Vectors.Vector";
}
}
if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
String name = getTypeDeclaration(inner) + "_Map";
if (name.startsWith(openApiPackageName)) {
return name;
} else {
return openApiPackageName + "." + name;
}
}
if (typeMapping.containsKey(schemaType)) {
return typeMapping.get(schemaType);
}
// LOGGER.info("OpenAPI type " + schemaType);
if (languageSpecificPrimitives.contains(schemaType)) {
return schemaType;
}
String modelType = toModelName(schemaType).replace("-", "_");
if (ModelUtils.isStringSchema(p) || ModelUtils.isFileSchema(p)
|| languageSpecificPrimitives.contains(modelType)) {
return modelType;
}
return modelPackage + ".Models." + modelType;
}
/**
* Overrides postProcessParameter to add a vendor extension "x-is-model-type".
* This boolean indicates that the parameter comes from the model package.
*
* @param parameter CodegenParameter object to be processed.
*/
@Override
public void postProcessParameter(CodegenParameter parameter) {
// Give the base class a chance to process
super.postProcessParameter(parameter);
if (parameter.dataType == null) {
return;
}
parameter.vendorExtensions.put("x-is-model-type", isModelType(parameter));
parameter.vendorExtensions.put("x-is-stream-type", isStreamType(parameter));
}
@Override
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
// Add a vendor extension attribute that provides a map of auth methods and the scopes
// which are expected by the operation. This map is then used by postProcessOperationsWithModels
// to build another vendor extension that provides a subset of the auth methods with only
// the scopes required by the operation.
final List securities = operation.getSecurity();
if (securities != null && securities.size() > 0) {
final Map securitySchemes = this.openAPI.getComponents() != null ? this.openAPI.getComponents().getSecuritySchemes() : null;
final List globalSecurities = this.openAPI.getSecurity();
Map> scopes = getAuthScopes(securities, securitySchemes);
if (scopes.isEmpty() && globalSecurities != null) {
scopes = getAuthScopes(globalSecurities, securitySchemes);
}
op.vendorExtensions.put("x-scopes", scopes);
}
// Determine the types that this operation produces. `getProducesInfo`
// simply lists all the types, and then we collect the list of media types
// for the operation and keep the global index assigned to that list in x-produces-media-index.
Set produces = getProducesInfo(openAPI, operation);
boolean producesPlainText = false;
if (produces != null && !produces.isEmpty()) {
List mediaList = new ArrayList<>();
List