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

Restructure TypeScript Node generation into separate files (PHNX-1041) #363

Merged
merged 5 commits into from
Jul 3, 2018
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 @@ -19,15 +19,15 @@

import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.*;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.*;

public class TypeScriptNodeClientCodegen extends AbstractTypeScriptClientCodegen {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeScriptNodeClientCodegen.class);
Expand All @@ -41,18 +41,24 @@ public class TypeScriptNodeClientCodegen extends AbstractTypeScriptClientCodegen
protected String npmName = null;
protected String npmVersion = "1.0.0";
protected String npmRepository = null;
protected String apiSuffix = "Api";

public TypeScriptNodeClientCodegen() {
super();

typeMapping.put("file", "Buffer");
languageSpecificPrimitives.add("Buffer");

// clear import mapping (from default generator) as TS does not use it
// at the moment
importMapping.clear();

outputFolder = "generated-code/typescript-node";
embeddedTemplateDir = templateDir = "typescript-node";
modelTemplateFiles.put("model.mustache", ".ts");
apiTemplateFiles.put("api-single.mustache", ".ts");
modelPackage = "model";
apiPackage = "api";

this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package"));
this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package"));
Expand Down Expand Up @@ -86,6 +92,101 @@ public String getTypeDeclaration(Schema p) {
}
return super.getTypeDeclaration(p);
}

@Override
public String toApiName(String name) {
if (name.length() == 0) {
return "Default" + apiSuffix;
}
return initialCaps(name) + apiSuffix;
}

@Override
public String toApiFilename(String name) {
if (name.length() == 0) {
return "default" + apiSuffix;
}
return camelize(name, true) + apiSuffix;
}

@Override
public String toApiImport(String name) {
return apiPackage() + "/" + toApiFilename(name);
}

@Override
public String toModelFilename(String name) {
return camelize(toModelName(name), true);
}

@Override
public String toModelImport(String name) {
return modelPackage() + "/" + toModelFilename(name);
}

@Override
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
Map<String, Object> result = super.postProcessAllModels(objs);

for (Map.Entry<String, Object> entry : result.entrySet()) {
Map<String, Object> inner = (Map<String, Object>) entry.getValue();
List<Map<String, Object>> models = (List<Map<String, Object>>) inner.get("models");
for (Map<String, Object> mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");

// Add additional filename information for imports
mo.put("tsImports", toTsImports(cm, cm.imports));
}
}
return result;
}

private List<Map<String, String>> toTsImports(CodegenModel cm, Set<String> imports) {
List<Map<String, String>> tsImports = new ArrayList<>();
for (String im : imports) {
if (!im.equals(cm.classname)) {
HashMap<String, String> tsImport = new HashMap<>();
tsImport.put("classname", im);
tsImport.put("filename", toModelFilename(im));
tsImports.add(tsImport);
}
}
return tsImports;
}

@Override
public Map<String, Object> postProcessOperations(Map<String, Object> operations) {
Map<String, Object> objs = (Map<String, Object>) operations.get("operations");

// The api.mustache template requires all of the auth methods for the whole api
// Loop over all the operations and pick out each unique auth method
Map<String, CodegenSecurity> authMethodsMap = new HashMap<>();
for (CodegenOperation op : (List<CodegenOperation>) objs.get("operation")) {
if(op.hasAuthMethods){
for(CodegenSecurity sec : op.authMethods){
authMethodsMap.put(sec.name, sec);
}
}
}

// If there wer any auth methods specified add them to the operations context
if (!authMethodsMap.isEmpty()) {
operations.put("authMethods", authMethodsMap.values());
operations.put("hasAuthMethods", true);
}

// Add filename information for api imports
objs.put("apiFilename", getApiFilenameFromClassname(objs.get("classname").toString()));

// Add additional filename information for model imports in the apis
List<Map<String, Object>> imports = (List<Map<String, Object>>) operations.get("imports");
for (Map<String, Object> im : imports) {
im.put("filename", im.get("import"));
im.put("classname", getModelnameFromModelFilename(im.get("filename").toString()));
}

return operations;
}

public void setNpmName(String npmName) {
this.npmName = npmName;
Expand All @@ -110,10 +211,12 @@ public void setNpmRepository(String npmRepository) {
@Override
public void processOpts() {
super.processOpts();
supportingFiles.add(new SupportingFile("api.mustache", null, "api.ts"));
supportingFiles.add(new SupportingFile("models.mustache", modelPackage().replace('.', File.separatorChar), "models.ts"));
supportingFiles.add(new SupportingFile("api-all.mustache", apiPackage().replace('.', File.separatorChar), "apis.ts"));
supportingFiles.add(new SupportingFile("api.mustache", getIndexDirectory(), "api.ts"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore"));

if (additionalProperties.containsKey(NPM_NAME)) {
addNpmPackageGeneration();
}
Expand Down Expand Up @@ -141,9 +244,57 @@ private void addNpmPackageGeneration() {
supportingFiles.add(new SupportingFile("package.mustache", getPackageRootDirectory(), "package.json"));
supportingFiles.add(new SupportingFile("tsconfig.mustache", getPackageRootDirectory(), "tsconfig.json"));
}

private String getIndexDirectory() {
String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.')));
return indexPackage.replace('.', File.separatorChar);
}

// The purpose of this override and associated methods is to allow for automatic conversion
// from 'file' type to the built in node 'Buffer' type
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
if (isLanguagePrimitive(openAPIType) || isLanguageGenericType(openAPIType)) {
return openAPIType;
}
applyLocalTypeMapping(openAPIType);
return openAPIType;
}

private String applyLocalTypeMapping(String type) {
if (typeMapping.containsKey(type)) {
type = typeMapping.get(type);
}
return type;
}

private boolean isLanguagePrimitive(String type) {
return languageSpecificPrimitives.contains(type);
}

// Determines if the given type is a generic/templated type (ie. ArrayList<String>)
private boolean isLanguageGenericType(String type) {
for (String genericType : languageGenericTypes) {
if (type.startsWith(genericType + "<")) {
return true;
}
}
return false;
}

private String getPackageRootDirectory() {
String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.')));
return indexPackage.replace('.', File.separatorChar);
}

private String getApiFilenameFromClassname(String classname) {
String name = classname.substring(0, classname.length() - apiSuffix.length());
return toApiFilename(name);
}

private String getModelnameFromModelFilename(String filename) {
String name = filename.substring((modelPackage() + File.separator).length());
return camelize(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{#apiInfo}}
{{#apis}}
{{#operations}}
export * from './{{ classFilename }}';
import { {{ classname }} } from './{{ classFilename }}';
{{/operations}}
{{#withInterfaces}}
export * from './{{ classFilename }}Interface'
{{/withInterfaces}}
{{/apis}}
export const APIS = [{{#apis}}{{#operations}}{{ classname }}{{/operations}}{{^-last}}, {{/-last}}{{/apis}}];
{{/apiInfo}}
Loading