Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix javascript-apollo generator/template #13191

Merged
merged 17 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
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 org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -49,8 +50,11 @@ public class JavascriptApolloClientCodegen extends DefaultCodegen implements Cod
public static final String MODULE_NAME = "moduleName";
public static final String PROJECT_DESCRIPTION = "projectDescription";
public static final String PROJECT_VERSION = "projectVersion";
public static final String USE_PROMISES = "usePromises";
public static final String USE_INHERITANCE = "useInheritance";
public static final String EMIT_MODEL_METHODS = "emitModelMethods";
public static final String EMIT_JS_DOC = "emitJSDoc";
public static final String USE_ES6 = "useES6";
public static final String NPM_REPOSITORY = "npmRepository";

final String[][] JAVASCRIPT_SUPPORTING_FILES = {
Expand All @@ -60,7 +64,8 @@ public class JavascriptApolloClientCodegen extends DefaultCodegen implements Cod
new String[]{"git_push.sh.mustache", "git_push.sh"},
new String[]{"README.mustache", "README.md"},
new String[]{"mocha.opts", "mocha.opts"},
new String[]{"travis.yml", ".travis.yml"}
new String[]{"travis.yml", ".travis.yml"},
new String[]{"gitignore.mustache", ".gitignore"}
};

final String[][] JAVASCRIPT_ES6_SUPPORTING_FILES = {
Expand All @@ -71,7 +76,8 @@ public class JavascriptApolloClientCodegen extends DefaultCodegen implements Cod
new String[]{"README.mustache", "README.md"},
new String[]{"mocha.opts", "mocha.opts"},
new String[]{"travis.yml", ".travis.yml"},
new String[]{".babelrc.mustache", ".babelrc"}
new String[]{".babelrc.mustache", ".babelrc"},
new String[]{"gitignore.mustache", ".gitignore"}
};

protected String projectName;
Expand All @@ -82,11 +88,14 @@ public class JavascriptApolloClientCodegen extends DefaultCodegen implements Cod

protected String invokerPackage;
protected String sourceFolder = "src";
protected boolean usePromises;
protected boolean emitModelMethods;
protected boolean emitJSDoc = true;
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected String apiTestPath = "api/";
protected String modelTestPath = "model/";
protected boolean useES6 = true; // default is ES6
protected String npmRepository = null;
private String modelPropertyNaming = "camelCase";

Expand All @@ -95,10 +104,6 @@ public JavascriptApolloClientCodegen() {

modifyFeatureSet(features -> features.includeDocumentationFeatures(DocumentationFeature.Readme));

generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.BETA)
.build();

outputFolder = "generated-code/js";
modelTemplateFiles.put("model.mustache", ".js");
modelTestTemplateFiles.put("model_test.mustache", ".js");
Expand All @@ -115,7 +120,7 @@ public JavascriptApolloClientCodegen() {
hideGenerationTimestamp = Boolean.TRUE;

// reference: http://www.w3schools.com/js/js_reserved.asp
setReservedWordsLowerCase(
reservedWords = new HashSet<>(
Arrays.asList(
"abstract", "arguments", "boolean", "break", "byte",
"case", "catch", "char", "class", "const",
Expand All @@ -142,18 +147,20 @@ public JavascriptApolloClientCodegen() {
defaultIncludes = new HashSet<>(languageSpecificPrimitives);

instantiationTypes.put("array", "Array");
instantiationTypes.put("set", "Array");
instantiationTypes.put("list", "Array");
instantiationTypes.put("map", "Object");
typeMapping.clear();
typeMapping.put("array", "Array");
typeMapping.put("set", "Array");
typeMapping.put("map", "Object");
typeMapping.put("List", "Array");
typeMapping.put("boolean", "Boolean");
typeMapping.put("string", "String");
typeMapping.put("int", "Number");
typeMapping.put("float", "Number");
typeMapping.put("number", "Number");
typeMapping.put("BigDecimal", "Number");
typeMapping.put("decimal", "Number");
typeMapping.put("DateTime", "Date");
typeMapping.put("date", "Date");
typeMapping.put("long", "Number");
Expand All @@ -167,6 +174,7 @@ public JavascriptApolloClientCodegen() {
typeMapping.put("file", "File");
typeMapping.put("UUID", "String");
typeMapping.put("URI", "String");
typeMapping.put("AnyType", "Object");

importMapping.clear();

Expand All @@ -184,6 +192,12 @@ public JavascriptApolloClientCodegen() {
"version of the project (Default: using info.version or \"1.0.0\")"));
cliOptions.add(new CliOption(CodegenConstants.LICENSE_NAME,
"name of the license the project uses (Default: using info.license.name)"));
cliOptions.add(new CliOption(USE_PROMISES,
"use Promises as return values from the client API, instead of superagent callbacks")
.defaultValue(Boolean.FALSE.toString()));
cliOptions.add(new CliOption(EMIT_MODEL_METHODS,
"generate getters and setters for model properties")
.defaultValue(Boolean.FALSE.toString()));
cliOptions.add(new CliOption(EMIT_JS_DOC,
"generate JSDoc comments")
.defaultValue(Boolean.TRUE.toString()));
Expand Down Expand Up @@ -241,12 +255,18 @@ public void processOpts() {
if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE));
}
if (additionalProperties.containsKey(USE_PROMISES)) {
setUsePromises(convertPropertyToBooleanAndWriteBack(USE_PROMISES));
}
if (additionalProperties.containsKey(USE_INHERITANCE)) {
setUseInheritance(convertPropertyToBooleanAndWriteBack(USE_INHERITANCE));
} else {
supportsInheritance = true;
supportsMixins = true;
}
if (additionalProperties.containsKey(EMIT_MODEL_METHODS)) {
setEmitModelMethods(convertPropertyToBooleanAndWriteBack(EMIT_MODEL_METHODS));
}
if (additionalProperties.containsKey(EMIT_JS_DOC)) {
setEmitJSDoc(convertPropertyToBooleanAndWriteBack(EMIT_JS_DOC));
}
Expand Down Expand Up @@ -277,7 +297,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
if (StringUtils.isEmpty(info.getDescription())) {
projectDescription = "JS API client generated by OpenAPI Generator";
} else {
projectDescription = sanitizeName(info.getDescription());
projectDescription = info.getDescription();
}
}

Expand All @@ -293,7 +313,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
projectName = "openapi-js-client";
}
if (StringUtils.isBlank(moduleName)) {
moduleName = camelize(underscore(projectName));
moduleName = camelize(underscore(sanitizeName(projectName)));
}
if (StringUtils.isBlank(projectVersion)) {
projectVersion = "1.0.0";
Expand All @@ -314,15 +334,21 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage);
additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder);
additionalProperties.put(USE_PROMISES, usePromises);
additionalProperties.put(USE_INHERITANCE, supportsInheritance);
additionalProperties.put(EMIT_MODEL_METHODS, emitModelMethods);
additionalProperties.put(EMIT_JS_DOC, emitJSDoc);
additionalProperties.put(USE_ES6, useES6);
additionalProperties.put(NPM_REPOSITORY, npmRepository);

// make api and model doc path available in mustache template
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);

String[][] supportingTemplateFiles = JAVASCRIPT_SUPPORTING_FILES;
if (useES6) {
supportingTemplateFiles = JAVASCRIPT_ES6_SUPPORTING_FILES;
}

for (String[] supportingTemplateFile : supportingTemplateFiles) {
supportingFiles.add(new SupportingFile(supportingTemplateFile[0], "", supportingTemplateFile[1]));
Expand Down Expand Up @@ -417,6 +443,10 @@ public void setLicenseName(String licenseName) {
this.licenseName = licenseName;
}

public void setUsePromises(boolean usePromises) {
this.usePromises = usePromises;
}

public void setNpmRepository(String npmRepository) {
this.npmRepository = npmRepository;
}
Expand All @@ -426,6 +456,10 @@ public void setUseInheritance(boolean useInheritance) {
this.supportsMixins = useInheritance;
}

public void setEmitModelMethods(boolean emitModelMethods) {
this.emitModelMethods = emitModelMethods;
}

public void setEmitJSDoc(boolean emitJSDoc) {
this.emitJSDoc = emitJSDoc;
}
Expand Down Expand Up @@ -507,6 +541,11 @@ public String toVarName(String name) {
return name;
}

@Override
protected boolean isReservedWord(String word) {
return word != null && reservedWords.contains(word);
}

@Override
public String toParamName(String name) {
// should be the same as variable name
Expand Down Expand Up @@ -867,22 +906,36 @@ private String trimBrackets(String s) {
return null;
}

private String getModelledType(String dataType) {
return "module:" + (StringUtils.isEmpty(invokerPackage) ? "" : (invokerPackage + "/"))
+ (StringUtils.isEmpty(modelPackage) ? "" : (modelPackage + "/")) + dataType;
}

private String getJSDocType(CodegenModel cm, CodegenProperty cp) {
if (Boolean.TRUE.equals(cp.isContainer)) {
if (cp.containerType.equals("array"))
return "Array.<" + cp.items + ">";
if (cp.containerType.equals("array") || cp.containerType.equals("set"))
return "Array.<" + getJSDocType(cm, cp.items) + ">";
else if (cp.containerType.equals("map"))
return "Object.<String, " + cp.items + ">";
return "Object.<String, " + getJSDocType(cm, cp.items) + ">";
}
String dataType = trimBrackets(cp.datatypeWithEnum);
if (cp.isEnum) {
dataType = cm.classname + '.' + dataType;
}
if (isModelledType(cp))
dataType = getModelledType(dataType);
return dataType;
}

private boolean isModelledType(CodegenProperty cp) {
// N.B. enums count as modelled types, file is not modelled (SuperAgent uses some 3rd party library).
return cp.isEnum || !languageSpecificPrimitives.contains(cp.baseType == null ? cp.dataType : cp.baseType);
}

private String getJSDocType(CodegenParameter cp) {
String dataType = trimBrackets(cp.dataType);
if (isModelledType(cp))
dataType = getModelledType(dataType);
if (Boolean.TRUE.equals(cp.isArray)) {
return "Array.<" + dataType + ">";
} else if (Boolean.TRUE.equals(cp.isMap)) {
Expand All @@ -891,9 +944,16 @@ private String getJSDocType(CodegenParameter cp) {
return dataType;
}

private boolean isModelledType(CodegenParameter cp) {
// N.B. enums count as modelled types, file is not modelled (SuperAgent uses some 3rd party library).
return cp.isEnum || !languageSpecificPrimitives.contains(cp.baseType == null ? cp.dataType : cp.baseType);
}

private String getJSDocType(CodegenOperation co) {
String returnType = trimBrackets(co.returnType);
if (returnType != null) {
if (isModelledType(co))
returnType = getModelledType(returnType);
if (Boolean.TRUE.equals(co.isArray)) {
return "Array.<" + returnType + ">";
} else if (Boolean.TRUE.equals(co.isMap)) {
Expand All @@ -903,12 +963,16 @@ private String getJSDocType(CodegenOperation co) {
return returnType;
}

private boolean isModelledType(CodegenOperation co) {
// This seems to be the only way to tell whether an operation return type is modelled.
return !Boolean.TRUE.equals(co.returnTypeIsPrimitive);
}

private boolean isPrimitiveType(String type) {
final String[] primitives = {"number", "integer", "string", "boolean", "null"};
return Arrays.asList(primitives).contains(type);
}

@SuppressWarnings("unchecked")
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
// Generate and store argument list string of each operation into
Expand Down Expand Up @@ -937,6 +1001,9 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
argList.add("opts");
}

// add the 'requestInit' parameter
argList.add("requestInit");

String joinedArgList = StringUtils.join(argList, ", ");
operation.vendorExtensions.put("x-codegen-arg-list", joinedArgList);
operation.vendorExtensions.put("x-codegen-has-optional-params", hasOptionalParams);
Expand Down Expand Up @@ -1145,6 +1212,24 @@ public void postProcessFile(File file, String fileType) {
}
}

@Override
protected String getCollectionFormat(CodegenParameter codegenParameter) {
// This method will return `passthrough` when the parameter data format is binary and an array.
// `passthrough` is not part of the OAS spec. However, this will act like a flag that we should
// not do any processing on the collection type (i.e. convert to tsv, csv, etc..). This is
// critical to support multi file uploads correctly.
if (codegenParameter.isArray && Objects.equals(codegenParameter.dataFormat, "binary")) {
return "passthrough";
}
return super.getCollectionFormat(codegenParameter);
}

@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.JAVASCRIPT; }

@Override
protected void addImport(ComposedSchema composed, Schema childSchema, CodegenModel model, String modelName ) {
// import everything (including child schema of a composed schema)
addImport(model, modelName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{>licenseInfo}}

import RESTDataSource from 'apollo-datasource-rest';
import { RESTDataSource } from 'apollo-datasource-rest';

{{#emitJSDoc}}/**
* @module {{#invokerPackage}}{{.}}/{{/invokerPackage}}ApiClient
Expand All @@ -14,9 +14,16 @@ import RESTDataSource from 'apollo-datasource-rest';
* @class
*/{{/emitJSDoc}}
export default class ApiClient extends RESTDataSource {
constructor() {
constructor(baseURL = '{{{basePath}}}') {
super()

{{#emitJSDoc}}/**
* The base URL against which to resolve every API call's (relative) path.
* @type {String}
* @default {{{basePath}}}
*/{{/emitJSDoc}}
this.baseURL = baseURL.replace(/\/+$/, '');

{{#emitJSDoc}}/**
* The authentication methods to be included for all API calls.
* @type {Array.<String>}
Expand Down Expand Up @@ -53,7 +60,7 @@ export default class ApiClient extends RESTDataSource {
}

parametrizePath(path, pathParams) {
return url.replace(/\{([\w-]+)\}/g, (fullMatch, key) => {
joaomlneto marked this conversation as resolved.
Show resolved Hide resolved
return path.replace(/\{([\w-]+)\}/g, (fullMatch, key) => {
var value;
if (pathParams.hasOwnProperty(key)) {
value = this.paramToString(pathParams[key]);
Expand Down Expand Up @@ -176,7 +183,7 @@ export default class ApiClient extends RESTDataSource {

async callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, authNames,
returnType) {
contentTypes, accepts, returnType, requestInit) {
joaomlneto marked this conversation as resolved.
Show resolved Hide resolved

var parameterizedPath = this.parametrizePath(path, pathParams);
var fetchOptions = {
Expand All @@ -203,9 +210,9 @@ export default class ApiClient extends RESTDataSource {
var httpMethodFn = httpMethod.toLowerCase();

if (httpMethodFn == 'get' || httpMethodFn == 'delete') {
response = await this[httpMethodFn](parameterizedPath, fetchOptions);
response = await this[httpMethodFn](parameterizedPath, [], requestInit);
joaomlneto marked this conversation as resolved.
Show resolved Hide resolved
} else {
response = await this[httpMethodFn](parameterizedPath, body, fetchOptions)
response = await this[httpMethodFn](parameterizedPath, body, requestInit)
}

var convertedResponse = ApiClient.convertToType(response, returnType);
Expand Down Expand Up @@ -234,7 +241,7 @@ export default class ApiClient extends RESTDataSource {
case 'Blob':
return data;
default:
if (type === Object) {
joaomlneto marked this conversation as resolved.
Show resolved Hide resolved
if (typeof type === "object") {
// generic object, return directly
return data;
} else if (typeof type.constructFromObject === 'function') {
Expand Down
Loading