Skip to content

Commit

Permalink
[cpp-qt5] improvements for server and client (OpenAPITools#1284)
Browse files Browse the repository at this point in the history
- Remove pointer usage in generated models
- Use const reference wherever possible
- Reuse same super class for Qt5 client and server
- Support primitive return types for Qt5 client
- Add const reference to API calls
  • Loading branch information
etherealjoy authored Nov 8, 2018
1 parent 045becc commit 624d846
Show file tree
Hide file tree
Showing 45 changed files with 1,780 additions and 3,015 deletions.
2 changes: 1 addition & 1 deletion bin/cpp-qt5-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g cpp-qt5 -o samples/client/petstore/cpp-qt5 $@"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g cpp-qt5-client -o samples/client/petstore/cpp-qt5 $@"

java $JAVA_OPTS -jar $executable $ags
2 changes: 1 addition & 1 deletion bin/openapi3/cpp-qt5-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g cpp-qt5 -o samples/client/petstore/cpp-qt5 $@"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g cpp-qt5-client -o samples/client/petstore/cpp-qt5 $@"

java $JAVA_OPTS -jar $executable $ags
2 changes: 1 addition & 1 deletion bin/security/cpp-qt5-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/2_0/petstore-security-test.yaml -g cpp-qt5 -o samples/client/petstore-security-test/cpp-qt5 $@"
ags="generate -t modules/openapi-generator/src/main/resources/cpp-qt5-client -i modules/openapi-generator/src/test/resources/2_0/petstore-security-test.yaml -g cpp-qt5-client -o samples/client/petstore-security-test/cpp-qt5 $@"

java $JAVA_OPTS -jar $executable $ags
2 changes: 1 addition & 1 deletion bin/windows/cpp-qt5-petstore.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ If Not Exist %executable% (
)

REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g cpp-qt5 -o samples\client\petstore\cpp-qt5
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g cpp-qt5-client -o samples\client\petstore\cpp-qt5

java %JAVA_OPTS% -jar %executable% %ags%
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
package org.openapitools.codegen.languages;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.utils.ModelUtils;

import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.util.SchemaTypeUtil;

public class CppQt5AbstractCodegen extends AbstractCppCodegen implements CodegenConfig {

protected final String PREFIX = "OAI";
protected String apiVersion = "1.0.0";
protected static final String CPP_NAMESPACE = "cppNamespace";
protected static final String CPP_NAMESPACE_DESC = "C++ namespace (convention: name::space::for::api).";
protected Set<String> foundationClasses = new HashSet<String>();
protected String cppNamespace = "OpenAPI";
protected Map<String, String> namespaces = new HashMap<String, String>();
protected Set<String> systemIncludes = new HashSet<String>();

protected Set<String> nonFrameworkPrimitives = new HashSet<String>();

public CppQt5AbstractCodegen() {
super();
// set modelNamePrefix as default for QHttpEngine Server
if (StringUtils.isEmpty(modelNamePrefix)) {
modelNamePrefix = PREFIX;
}
// CLI options
addOption(CPP_NAMESPACE, CPP_NAMESPACE_DESC, this.cppNamespace);

/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("apiVersion", apiVersion);
additionalProperties().put("prefix", PREFIX);

// Write defaults namespace in properties so that it can be accessible in templates.
// At this point command line has not been parsed so if value is given
// in command line it will supersede this content
additionalProperties.put("cppNamespace", cppNamespace);
// CLI options
addOption(CPP_NAMESPACE, CPP_NAMESPACE_DESC, this.cppNamespace);
/*
* Language Specific Primitives. These types will not trigger imports by
* the client generator
*/
languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"bool",
"qint32",
"qint64",
"float",
"double")
);
nonFrameworkPrimitives.addAll(languageSpecificPrimitives);

foundationClasses.addAll(
Arrays.asList(
"QString",
"QDate",
"QDateTime",
"QByteArray")
);
languageSpecificPrimitives.addAll(foundationClasses);
super.typeMapping = new HashMap<String, String>();

typeMapping.put("date", "QDate");
typeMapping.put("DateTime", "QDateTime");
typeMapping.put("string", "QString");
typeMapping.put("integer", "qint32");
typeMapping.put("long", "qint64");
typeMapping.put("boolean", "bool");
typeMapping.put("array", "QList");
typeMapping.put("map", "QMap");
typeMapping.put("object", PREFIX + "Object");
// mapped as "file" type for OAS 3.0
typeMapping.put("ByteArray", "QByteArray");
// UUID support - possible enhancement : use QUuid instead of QString.
// beware though that Serialization/de-serialization of QUuid does not
// come out of the box and will need to be sorted out (at least imply
// modifications on multiple templates)
typeMapping.put("UUID", "QString");
typeMapping.put("file", "QIODevice");
typeMapping.put("binary", "QIODevice");
importMapping = new HashMap<String, String>();
namespaces = new HashMap<String, String>();

systemIncludes.add("QString");
systemIncludes.add("QList");
systemIncludes.add("QMap");
systemIncludes.add("QDate");
systemIncludes.add("QDateTime");
systemIncludes.add("QByteArray");
systemIncludes.add("QIODevice");
}
@Override
public void processOpts() {
super.processOpts();

if (additionalProperties.containsKey("cppNamespace")) {
cppNamespace = (String) additionalProperties.get("cppNamespace");
}

additionalProperties.put("cppNamespaceDeclarations", cppNamespace.split("\\::"));
if (additionalProperties.containsKey("modelNamePrefix")) {
typeMapping.put("object", modelNamePrefix + "Object");
additionalProperties().put("prefix", modelNamePrefix);
}
}

@Override
public String toModelImport(String name) {
if( name.isEmpty() ) {
return null;
}

if (namespaces.containsKey(name)) {
return "using " + namespaces.get(name) + ";";
} else if (systemIncludes.contains(name)) {
return "#include <" + name + ">";
} else if(importMapping.containsKey(name)){
return importMapping.get(name);
}

String folder = modelPackage().replace("::", File.separator);
if (!folder.isEmpty())
folder += File.separator;

return "#include \"" + folder + name + ".h\"";
}

/**
* 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
*/
@Override
@SuppressWarnings("rawtypes")
public String getTypeDeclaration(Schema p) {
String openAPIType = getSchemaType(p);

if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "<QString, " + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isBinarySchema(p)) {
return getSchemaType(p) + "*";
} else if (ModelUtils.isFileSchema(p)) {
return getSchemaType(p) + "*";
}
if (foundationClasses.contains(openAPIType)) {
return openAPIType;
} else if (languageSpecificPrimitives.contains(openAPIType)) {
return toModelName(openAPIType);
} else {
return openAPIType;
}
}

@Override
@SuppressWarnings("rawtypes")
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
return "false";
} else if (ModelUtils.isDateSchema(p)) {
return "NULL";
} else if (ModelUtils.isDateTimeSchema(p)) {
return "NULL";
} else if (ModelUtils.isNumberSchema(p)) {
if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) {
return "0.0f";
}
return "0.0";
} else if (ModelUtils.isIntegerSchema(p)) {
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) {
return "0L";
}
return "0";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return "QMap<QString, " + getTypeDeclaration(inner) + ">()";
} else if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return "QList<" + getTypeDeclaration(inner) + ">()";
} else if (ModelUtils.isStringSchema(p)) {
return "QString(\"\")";
} else if (!StringUtils.isEmpty(p.get$ref())) {
return toModelName(ModelUtils.getSimpleRef(p.get$ref())) + "()";
}
return "NULL";
}

@Override
public String toModelFilename(String name) {
return modelNamePrefix + initialCaps(name);
}

/**
* Optional - OpenAPI type conversion. This is used to map OpenAPI types in a `Schema` into
* either language specific types via `typeMapping` or into complex models if there is not a mapping.
*
* @return a string value of the type or complex model for this property
*/
@Override
@SuppressWarnings("rawtypes")
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);

String type = null;
if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type)) {
return toModelName(type);
}
if (foundationClasses.contains(type)) {
return type;
}
} else {
type = openAPIType;
}
return toModelName(type);
}

@Override
public String toVarName(String name) {
// sanitize name
String varName = name;
varName = sanitizeName(name);

// if it's all uppper case, convert to lower case
if (varName.matches("^[A-Z_]*$")) {
varName = varName.toLowerCase(Locale.ROOT);
}

// camelize (lower first character) the variable name
// petId => pet_id
varName = org.openapitools.codegen.utils.StringUtils.underscore(varName);

// for reserved word or word starting with number, append _
if (isReservedWord(varName) || varName.matches("^\\d.*")) {
varName = escapeReservedWord(varName);
}

return varName;
}

@Override
public String toParamName(String name) {
return toVarName(name);
}

@Override
public String getTypeDeclaration(String str) {
return str;
}

@Override
protected boolean needToImport(String type) {
return StringUtils.isNotBlank(type) && !defaultIncludes.contains(type)
&& !nonFrameworkPrimitives.contains(type);
}


@Override
@SuppressWarnings("unchecked")
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
Map<String, Object> objectMap = (Map<String, Object>) objs.get("operations");
List<CodegenOperation> operations = (List<CodegenOperation>) objectMap.get("operation");

List<Map<String, String>> imports = (List<Map<String, String>>) objs.get("imports");
for (CodegenOperation operation : operations) {
// Check all return parameter baseType if there is a necessity to include, include it if not
// already done
if (operation.returnBaseType != null && needToImport(operation.returnBaseType)) {
if(!isIncluded(operation.returnBaseType, imports)) {
imports.add(createMapping("import", operation.returnBaseType));
}
}
List<CodegenParameter> params = new ArrayList<CodegenParameter>();
if (operation.allParams != null)params.addAll(operation.allParams);

// Check all parameter baseType if there is a necessity to include, include it if not
// already done
for(CodegenParameter param : params) {
if(param.isPrimitiveType && needToImport(param.baseType)) {
if(!isIncluded(param.baseType, imports)) {
imports.add(createMapping("import", param.baseType));
}
}
}
if (operation.pathParams != null) {
// We use QString to pass path params, add it to include
if(!isIncluded("QString", imports)) {
imports.add(createMapping("import", "QString"));
}
}
}
if(isIncluded("QMap", imports)) {
// Maps uses QString as key
if(!isIncluded("QString", imports)) {
imports.add(createMapping("import", "QString"));
}
}
return objs;
}

private Map<String, String> createMapping(String key, String value) {
Map<String, String> customImport = new HashMap<String, String>();
customImport.put(key, toModelImport(value));
return customImport;
}

private boolean isIncluded(String type, List<Map<String, String>> imports) {
boolean included = false;
String inclStr = toModelImport(type);
for (Map<String, String> importItem : imports) {
if(importItem.containsValue(inclStr)) {
included = true;
break;
}
}
return included;
}
}
Loading

0 comments on commit 624d846

Please sign in to comment.