org.openapitools.codegen.languages.CLibcurlClientCodegen 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
*
* http://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 io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.io.FilenameUtils;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class CLibcurlClientCodegen extends DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(CLibcurlClientCodegen.class);
protected String moduleName;
protected String specFolder = "spec";
protected String libFolder = "lib";
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected static int emptyMethodNameCounter = 0;
public CLibcurlClientCodegen() {
super();
modelPackage = "models";
apiPackage = "api";
outputFolder = "generated-code" + File.separator + "C-libcurl";
modelTemplateFiles.put("model-header.mustache", ".h");
modelTemplateFiles.put("model-body.mustache", ".c");
apiTemplateFiles.put("api-header.mustache", ".h");
apiTemplateFiles.put("api-body.mustache", ".c");
//modelDocTemplateFiles.put("model_doc.mustache", ".md");
//apiDocTemplateFiles.put("api_doc.mustache", ".md");
embeddedTemplateDir = templateDir = "C-libcurl";
// TODO add auto-generated test files
//modelTestTemplateFiles.put("model_test.mustache", ".c");
//apiTestTemplateFiles.put("api_test.mustache", ".c");
// default HIDE_GENERATION_TIMESTAMP to true
hideGenerationTimestamp = Boolean.TRUE;
setReservedWordsLowerCase(
Arrays.asList(
// local variable names used in API methods (endpoints)
// c reserved keywords
// ref: https://en.cppreference.com/w/c/keyword
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"inline",
"int",
"long",
"register",
"restrict",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"_Alignas",
"_Alignof",
"_Atomic",
"_Bool",
"_Complex",
"_Generic",
"_Imaginary",
"_Noreturn",
"_Static_assert",
"_Thread_local")
);
instantiationTypes.clear();
typeMapping.clear();
importMapping.clear();
languageSpecificPrimitives.clear();
// primitives in C lang
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("short");
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("long");
languageSpecificPrimitives.add("float");
languageSpecificPrimitives.add("double");
languageSpecificPrimitives.add("char");
languageSpecificPrimitives.add("FILE");
languageSpecificPrimitives.add("Object");
languageSpecificPrimitives.add("list_t*");
typeMapping.put("string", "char");
typeMapping.put("char", "char");
typeMapping.put("integer", "int");
typeMapping.put("long", "long");
typeMapping.put("float", "double");
typeMapping.put("double", "float");
typeMapping.put("number", "float");
typeMapping.put("date", "char");
typeMapping.put("DateTime", "char");
typeMapping.put("boolean", "int");
typeMapping.put("file", "FILE");
typeMapping.put("binary", "char");
typeMapping.put("ByteArray", "char");
typeMapping.put("UUID", "char");
typeMapping.put("array", "list");
typeMapping.put("map", "list_t*");
typeMapping.put("date-time", "char");
// remove modelPackage and apiPackage added by default
Iterator itr = cliOptions.iterator();
while (itr.hasNext()) {
CliOption opt = itr.next();
if (CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) ||
CodegenConstants.API_PACKAGE.equals(opt.getOpt())) {
itr.remove();
}
}
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).
defaultValue(Boolean.TRUE.toString()));
}
@Override
public void processOpts() {
super.processOpts();
if (StringUtils.isEmpty(System.getenv("C_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable C_POST_PROCESS_FILE not defined so the C code may not be properly formatted by uncrustify (0.66 or later) or other code formatter. To define it, try `export C_POST_PROCESS_FILE=\"/usr/local/bin/uncrustify --no-backup\" && export UNCRUSTIFY_CONFIG=/path/to/uncrustify-rules.cfg` (Linux/Mac). Note: replace /path/to with the location of uncrustify-rules.cfg");
}
// make api and model doc path available in mustache template
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);
// use constant model/api package (folder path)
setModelPackage("models");
setApiPackage("api");
// root folder
supportingFiles.add(new SupportingFile("CMakeLists.txt.mustache", "", "CMakeLists.txt"));
supportingFiles.add(new SupportingFile("libcurl.licence.mustache", "", "libcurl.licence"));
supportingFiles.add(new SupportingFile("uncrustify-rules.cfg.mustache", "", "uncrustify-rules.cfg"));
supportingFiles.add(new SupportingFile("README.md.mustache", "", "README.md"));
// src folder
supportingFiles.add(new SupportingFile("apiClient.c.mustache", "src", "apiClient.c"));
supportingFiles.add(new SupportingFile("apiKey.c.mustache", "src", "apiKey.c"));
supportingFiles.add(new SupportingFile("list.c.mustache", "src", "list.c"));
// include folder
supportingFiles.add(new SupportingFile("apiClient.h.mustache", "include", "apiClient.h"));
supportingFiles.add(new SupportingFile("keyValuePair.h.mustache", "include", "keyValuePair.h"));
supportingFiles.add(new SupportingFile("list.h.mustache", "include", "list.h"));
// external folder
supportingFiles.add(new SupportingFile("cJSON.licence.mustache", "external", "cJSON.licence"));
supportingFiles.add(new SupportingFile("cJSON.c.mustache", "external" + File.separator + "src", "cJSON.c"));
supportingFiles.add(new SupportingFile("cJSON.h.mustache", "external" + File.separator + "include", "cJSON.h"));
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public String getName() {
return "c";
}
@Override
public String getHelp() {
return "Generates a C (libcurl) client library (beta).";
}
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name;
}
@Override
public String apiFileFolder() {
return outputFolder + File.separator + "api";
}
@Override
public String modelFileFolder() {
return outputFolder + File.separator + "model";
}
@Override
public String apiTestFileFolder() {
return outputFolder + File.separator + "unit-test";
}
@Override
public String modelTestFileFolder() {
return outputFolder + File.separator + "unit-test";
}
@Override
public String apiDocFileFolder() {
return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
}
@Override
public String modelDocFileFolder() {
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
}
@Override
public String getTypeDeclaration(Schema schema) {
/* comment out below as we'll do it in the template instead
if (ModelUtils.isArraySchema(schema)) {
Schema inner = ((ArraySchema) schema).getItems();
return getSchemaType(schema) + "<" + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isMapSchema(schema)) {
Schema inner = (Schema) schema.getAdditionalProperties();
return getSchemaType(schema) + "";
}
*/
return super.getTypeDeclaration(schema);
}
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
return "'" + escapeText((String) p.getDefault()) + "'";
}
}
return null;
}
@Override
public String getSchemaType(Schema schema) {
String openAPIType = super.getSchemaType(schema);
String type = null;
if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type)) {
return type;
}
} else {
type = openAPIType;
}
if (type == null) {
return null;
}
return toModelName(type);
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// if it's all uppper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}
name = underscore(name);
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toParamName(String name) {
// should be the same as variable name
name = name.replaceAll("-","_");
return name;
}
@Override
public String toModelName(String name) {
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
if (!StringUtils.isEmpty(modelNamePrefix)) {
name = modelNamePrefix + "_" + name;
}
if (!StringUtils.isEmpty(modelNameSuffix)) {
name = name + "_" + modelNameSuffix;
}
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(name)) {
String modelName = camelize("Model" + name);
LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName);
return modelName;
}
// model name starts with number
if (name.matches("^\\d.*")) {
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name));
name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
}
// camelize the model name
// phone_number => PhoneNumber
return underscore(name);
}
@Override
public String toModelFilename(String name) {
return underscore(toModelName(name));
}
@Override
public String toModelDocFilename(String name) {
return toModelName(name);
}
@Override
public String toApiFilename(String name) {
// replace - with _ e.g. created-at => created_at
name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// e.g. PhoneNumberApi.rb => phone_number_api.rb
return camelize(name) + "API";
}
@Override
public String toApiDocFilename(String name) {
return toApiName(name);
}
@Override
public String toApiTestFilename(String name) {
return ("test_" + toApiFilename(name)).replaceAll("_", "-");
}
@Override
public String toModelTestFilename(String name) {
return ("test_" + toModelFilename(name)).replaceAll("_", "-");
}
@Override
public String toApiName(String name) {
if (name.length() == 0) {
return "DefaultApi";
}
// e.g. phone_number_api => PhoneNumberApi
return camelize(name) + "API";
}
@Override
public String toEnumValue(String value, String datatype) {
if ("Integer".equals(datatype) || "Float".equals(datatype)) {
return value;
} else {
if (value.matches("\\d.*")) { // starts with number
return "N" + escapeText(value);
} else {
return escapeText(value);
}
}
}
@Override
public String toEnumVarName(String name, String datatype) {
if (name.length() == 0) {
return "EMPTY";
}
// number
if ("Integer".equals(datatype) || "Float".equals(datatype)) {
String varName = name;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}
// string
String enumName = sanitizeName(camelize(name).toUpperCase(Locale.ROOT));
enumName = enumName.replaceFirst("^_", "");
enumName = enumName.replaceFirst("_$", "");
if (enumName.matches("\\d.*")) { // starts with number
return "N" + enumName;
} else {
return enumName;
}
}
@Override
public String toEnumName(CodegenProperty property) {
String enumName = camelize(toModelName(property.name)).toUpperCase(Locale.ROOT);
enumName = enumName.replaceFirst("^_", "");
enumName = enumName.replaceFirst("_$", "");
if (enumName.matches("\\d.*")) { // starts with number
return "N" + enumName;
} else {
return enumName;
}
}
@Override
public Map postProcessModels(Map objs) {
// process enum in models
return postProcessModelsEnum(objs);
}
@Override
public String toOperationId(String operationId) {
// rename to empty_method_name_1 (e.g.) if method name is empty
if (StringUtils.isEmpty(operationId)) {
operationId = camelize("empty_method_name_" + emptyMethodNameCounter++);
LOGGER.warn("Empty method name (operationId) found. Renamed to " + operationId);
return operationId;
}
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
String newOperationId = camelize(sanitizeName("call_" + operationId), true);
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
return newOperationId;
}
// operationId starts with a number
if (operationId.matches("^\\d.*")) {
String newOperationId = camelize(sanitizeName("call_" + operationId), true);
LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + newOperationId);
return newOperationId;
}
return camelize(sanitizeName(operationId), true);
}
@Override
public String toApiImport(String name) {
return apiPackage() + "/" + toApiFilename(name);
}
@Override
public String toModelImport(String name) {
return "#include \"" + name + ".h\"";
}
@Override
public void setParameterExampleValue(CodegenParameter p) {
String example;
if (p.defaultValue == null) {
example = p.example;
} else {
p.example = p.defaultValue;
return;
}
String type = p.baseType;
if (type == null) {
type = p.dataType;
}
if ("String".equals(type)) {
if (example == null) {
example = p.paramName + "_example";
}
example = "'" + escapeText(example) + "'";
} else if ("Integer".equals(type)) {
if (example == null) {
example = "56";
}
} else if ("Float".equals(type)) {
if (example == null) {
example = "3.4";
}
} else if ("BOOLEAN".equals(type)) {
if (example == null) {
example = "true";
}
} else if ("File".equals(type)) {
if (example == null) {
example = "/path/to/file";
}
example = "File.new('" + escapeText(example) + "')";
} else if ("Date".equals(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "Date.parse('" + escapeText(example) + "')";
} else if ("DateTime".equals(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "DateTime.parse('" + escapeText(example) + "')";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = moduleName + "::" + type + ".new";
}
if (example == null) {
example = "nil";
} else if (Boolean.TRUE.equals(p.isListContainer)) {
example = "[" + example + "]";
} else if (Boolean.TRUE.equals(p.isMapContainer)) {
example = "{'key' => " + example + "}";
}
p.example = example;
}
@Override
public boolean shouldOverwrite(String filename) {
// skip spec file as the file might have been updated with new test cases
return !(skipOverwrite && new File(filename).exists());
//
//return super.shouldOverwrite(filename) && !filename.endsWith("_spec.rb");
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("=end", "=_end").replace("=begin", "=_begin");
}
@Override
public void postProcessFile(File file, String fileType) {
if (file == null) {
return;
}
String cPostProcessFile = System.getenv("C_POST_PROCESS_FILE");
if (StringUtils.isEmpty(cPostProcessFile)) {
return; // skip if C_POST_PROCESS_FILE env variable is not defined
}
// only procees the following type (or we can simply rely on the file extension to check if it's a .c or .h file)
Set supportedFileType = new HashSet(
Arrays.asList(
"supporting-mustache",
"model-test",
"model",
"api-test",
"api"));
if (!supportedFileType.contains(fileType)) {
return;
}
// only process files with .c or .h extension
if ("c".equals(FilenameUtils.getExtension(file.toString())) ||
"h".equals(FilenameUtils.getExtension(file.toString()))) {
String command = cPostProcessFile + " " + file.toString();
try {
Process p = Runtime.getRuntime().exec(command);
int exitValue = p.waitFor();
if (exitValue != 0) {
LOGGER.error("Error running the command ({}). Exit code: {}", command, exitValue);
} else {
LOGGER.info("Successfully executed: " + command);
}
} catch (Exception e) {
LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy