Add a Javascript (Closure) Angular generator.

This commit is contained in:
Andrew Z Allen 2016-01-27 19:08:09 -07:00
parent 564fa04f7c
commit b950271940
5 changed files with 420 additions and 0 deletions

View File

@ -0,0 +1,206 @@
package io.swagger.codegen.languages;
import io.swagger.codegen.CodegenModel;
import io.swagger.codegen.*;
import io.swagger.models.properties.*;
import java.util.TreeSet;
import java.util.*;
import java.io.File;
public class JavascriptClosureAngularClientCodegen extends DefaultCodegen implements CodegenConfig {
public JavascriptClosureAngularClientCodegen() {
super();
supportsInheritance = false;
reservedWords = new HashSet<String>(Arrays.asList("abstract",
"continue", "for", "new", "switch", "assert", "default", "if",
"package", "synchronized", "do", "goto", "private",
"this", "break", "double", "implements", "protected", "throw",
"byte", "else", "import", "public", "throws", "case", "enum",
"instanceof", "return", "transient", "catch", "extends", "int",
"short", "try", "char", "final", "interface", "static", "void",
"class", "finally", "const", "super", "while"));
languageSpecificPrimitives = new HashSet<String>(Arrays.asList(
"string",
"boolean",
"number",
"Object",
"Blob",
"Date"));
instantiationTypes.put("array", "Array");
typeMapping = new HashMap<String, String>();
typeMapping.put("Array", "Array");
typeMapping.put("array", "Array");
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("long", "number");
typeMapping.put("short", "number");
typeMapping.put("char", "string");
typeMapping.put("double", "number");
typeMapping.put("object", "Object");
typeMapping.put("Object", "Object");
typeMapping.put("File", "Blob");
typeMapping.put("file", "Blob");
typeMapping.put("integer", "number");
typeMapping.put("Map", "Object");
typeMapping.put("map", "Object");
typeMapping.put("DateTime", "Date");
outputFolder = "generated-code/javascript-closure-angular";
modelTemplateFiles.put("model.mustache", ".js");
apiTemplateFiles.put("api.mustache", ".js");
embeddedTemplateDir = templateDir = "Javascript-Closure-Angular";
apiPackage = "API.Client";
modelPackage = "API.Client";
}
@Override
public String getName() {
return "javascript-closure-angular";
}
@Override
public String getHelp() {
return "Generates a Javascript AngularJS client library annotated with Google Closure Compiler annotations" +
"(https://developers.google.com/closure/compiler/docs/js-for-compiler?hl=en)";
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public String escapeReservedWord(String name) {
return "_" + name;
}
@Override
public String apiFileFolder() {
return outputFolder + "/" + apiPackage().replace('.', File.separatorChar);
}
public String modelFileFolder() {
return outputFolder + "/" + modelPackage().replace('.', File.separatorChar);
}
@Override
public String toVarName(String name) {
// replace - with _ e.g. created-at => created_at
name = name.replaceAll("-", "_");
// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$"))
return name;
// camelize the variable name
// pet_id => PetId
name = camelize(name, true);
// for reserved word or word starting with number, append _
if (reservedWords.contains(name) || name.matches("^\\d.*"))
name = escapeReservedWord(name);
return name;
}
@Override
public String toParamName(String name) {
// should be the same as variable name
return toVarName(name);
}
@Override
public String toModelName(String name) {
// model name cannot use reserved keyword, e.g. return
if (reservedWords.contains(name))
throw new RuntimeException(name
+ " (reserved word) cannot be used as a model name");
// camelize the model name
// phone_number => PhoneNumber
return camelize(name);
}
@Override
public String toModelFilename(String name) {
// should be the same as the model name
return toModelName(name);
}
@Override
public String getTypeDeclaration(Property p) {
if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
Property inner = ap.getItems();
return getSwaggerType(p) + "<!" + getTypeDeclaration(inner) + ">";
} else if (p instanceof MapProperty) {
MapProperty mp = (MapProperty) p;
Property inner = mp.getAdditionalProperties();
return "Object<!string, "+ getTypeDeclaration(inner) + ">";
} else if (p instanceof FileProperty) {
return "Object";
}
String type = super.getTypeDeclaration(p);
if (type.equals("boolean") ||
type.equals("Date") ||
type.equals("number") ||
type.equals("string")) {
return type;
}
return apiPackage + "." + type;
}
@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
String type = null;
if (typeMapping.containsKey(swaggerType)) {
type = typeMapping.get(swaggerType);
if (languageSpecificPrimitives.contains(type))
return type;
} else
type = swaggerType;
return type;
}
@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
List<Object> models = (List<Object>) objs.get("models");
for (Object _mo : models) {
Map<String, Object> mo = (Map<String, Object>) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
cm.imports = new TreeSet(cm.imports);
for (CodegenProperty var : cm.vars) {
// handle default value for enum, e.g. available => StatusEnum.available
if (var.isEnum && var.defaultValue != null && !"null".equals(var.defaultValue)) {
var.defaultValue = var.datatypeWithEnum + "." + var.defaultValue;
}
}
}
return objs;
}
@Override
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
if (objs.get("imports") instanceof List) {
List<Map<String, String>> imports = (ArrayList<Map<String, String>>)objs.get("imports");
Collections.sort(imports, new Comparator<Map<String, String>>() {
public int compare(Map<String, String> o1, Map<String, String> o2) {
return o1.get("import").compareTo(o2.get("import"));
}
});
objs.put("imports", imports);
}
return objs;
}
}

View File

@ -0,0 +1,143 @@
/**
* @fileoverview AUTOMATICALLY GENERATED service for {{package}}.{{classname}}.
* Do not edit this file by hand or your changes will be lost next time it is
* generated.{{#appDescription}}
*
* {{ appDescription }}{{/appDescription}}{{#version}}
* Version: {{version}}{{/version}}{{#appContact}}
* Contact: {{appContact}}{{/appContact}}
* Generated at: {{generatedDate}}
* Generated by: {{generatorClass}}
*/{{#licenseInfo}}
/**
* @license {{licenseInfo}}{{#licenseUrl}}
* {{licenseUrl}}{{/licenseUrl}}
*/
{{/licenseInfo}}
goog.provide('{{package}}.{{classname}}');
{{#imports}}
goog.require('{{import}}');
{{/imports}}
{{#operations}}
/**
{{#description}}
* {{&description}}
{{/description}}
* @constructor
* @param {!angular.$http} $http
* @param {!angular.$injector} $injector
* @struct
*/
{{package}}.{{classname}} = function($http, $injector) {
/** @private {!string} */
this.basePath_ = $injector.has('{{classname}}BasePath') ?
/** @type {!string} */ ($injector.get('{{classname}}BasePath')) :
'{{basePath}}';
/** @private {!Object<string, string>} */
this.defaultHeaders_ = $injector.has('{{classname}}DefaultHeaders') ?
/** @type {!Object<string, string>} */ (
$injector.get('{{classname}}DefaultHeaders')) :
{};
/** @private {!angular.$http} */
this.http_ = $http;
}
{{package}}.{{classname}}.$inject = ['$http', '$injector'];
{{#operation}}
/**
* {{summary}}
* {{notes}}{{#allParams}}
* @param {!{{{dataType}}}{{^required}}={{/required}}} {{^required}}opt_{{/required}}{{paramName}} {{description}}{{/allParams}}
* @param {!angular.$http.Config=} opt_extraHttpRequestParams Extra HTTP parameters to send.
* @return {!angular.$q.Promise{{#returnType}}<!{{{returnType}}}>{{/returnType}}}
*/
{{package}}.{{classname}}.prototype.{{nickname}} = function({{#allParams}}{{^required}}opt_{{/required}}{{paramName}}, {{/allParams}}opt_extraHttpRequestParams) {
/** @const {!string} */
var path = this.basePath_ + '{{path}}'{{#pathParams}}
.replace('{' + '{{baseName}}' + '}', String({{^required}}opt_{{/required}}{{paramName}})){{/pathParams}};
{{#required}}
// verify required parameter '{{paramName}}' is set
if (!{{paramName}}) {
throw new Error('Missing required parameter {{paramName}} when calling {{nickname}}');
}
{{/required}}
/** @type {!Object<string,string>} */
var queryParameters = {};
{{#queryParams}}
if ({{^required}}opt_{{/required}}{{paramName}} !== undefined) {
queryParameters['{{baseName}}'] = String({{^required}}opt_{{/required}}{{paramName}});
}
{{/queryParams}}
/** @type {!Object<string,string>} */
var headerParams = angular.copy(this.defaultHeaders_);
{{#headerParams}}
if ({{^required}}opt_{{/required}}{{paramName}} !== undefined) {
headerParams['{{baseName}}'] = {{^required}}opt_{{/required}}{{paramName}};
}
{{/headerParams}}
{{#hasFormParams}}
/** @type {!FormData} */
var formParams = new FormData();
{{/hasFormParams}}
{{#formParams}}
if ({{^required}}opt_{{/required}}{{paramName}} !== undefined) {
var {{paramName}}_ = /** @type {?} */ ({{^required}}opt_{{/required}}{{paramName}});
if ({{paramName}}_ instanceof Blob) {
formParams.append('{{baseName}}', {{paramName}}_);
} else if (typeof {{paramName}}_ === 'string') {
formParams.append('{{baseName}}', {{paramName}}_);
} else {
throw new Error('Forms parameter {{^required}}opt_{{/required}}{{paramName}} is required to be a string or a Blob (https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob)');
}
}
{{/formParams}}
{{#allParams}}
{{/allParams}}
/** @type {!angular.$http.Config} */
var httpRequestConfig = /** @type {!angular.$http.Config} */ ({
url: path,
json: {{#hasFormParams}}false{{/hasFormParams}}{{^hasFormParams}}true{{/hasFormParams}},{{#bodyParam}}
data: {{paramName}},{{/bodyParam}}{{#hasFormParams}}
data: formParams,{{/hasFormParams}}
params: queryParameters,
headers: headerParams
});
if (opt_extraHttpRequestParams) {
// If an opt_extraHttpRequestParams object is passed in, override values
// set the generated config with the passed in values.
httpRequestConfig = angular.merge(httpRequestConfig, opt_extraHttpRequestParams);
}
// This whole block is to work around a limitation in closure compiler. It
// would be better to call the $http service directly as a function, but that
// isn't permitted since it has methods attached to it. Manually confirmed to
// compile down to just a single method even with only SIMPLE optimization on.
// https://github.com/google/closure-compiler/blob/90769b826df65eabfb0211517b0d6d85c0c1c60b/contrib/externs/angular-1.4.js#L1393
switch ('{{httpMethod}}') {
case 'GET':
return this.http_.get(path, httpRequestConfig);
case 'HEAD':
return this.http_.head(path, httpRequestConfig);
case 'POST':
return this.http_.post(path, {}, httpRequestConfig);
case 'PUT':
return this.http_.put(path, {}, httpRequestConfig);
case 'DELETE':
return this.http_.delete(path, httpRequestConfig);
case 'PATCH':
return this.http_.patch(path, {}, httpRequestConfig);
}
}
{{/operation}}
{{/operations}}

View File

@ -0,0 +1,40 @@
{{#models}}
{{#model}}
goog.provide('{{package}}.{{name}}');
{{/model}}
{{/models}}
{{#models}}
{{#model}}
/**
{{#description}}
* {{{description}}}
{{/description}}
* @record
*/
{{package}}.{{classname}} = function() {}
{{#vars}}
/**
{{#description}}
* {{{description}}}
{{/description}}
{{! Explicitly force types to be non-nullable using !. This is redundant but valid }}
* @type {!{{{datatype}}}}
* @export
*/
{{package}}.{{classname}}.prototype.{{name}};
{{/vars}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
/** @enum {string} */
{{package}}.{{classname}}.{{datatypeWithEnum}} = { {{#allowableValues}}{{#values}}
{{.}}: '{{.}}',{{/values}}{{/allowableValues}}
}
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
{{/model}}
{{/models}}

View File

@ -10,6 +10,7 @@ io.swagger.codegen.languages.JavaJerseyServerCodegen
io.swagger.codegen.languages.JavaCXFServerCodegen io.swagger.codegen.languages.JavaCXFServerCodegen
io.swagger.codegen.languages.JavaInflectorServerCodegen io.swagger.codegen.languages.JavaInflectorServerCodegen
io.swagger.codegen.languages.JavascriptClientCodegen io.swagger.codegen.languages.JavascriptClientCodegen
io.swagger.codegen.languages.JavascriptClosureAngularClientCodegen
io.swagger.codegen.languages.JMeterCodegen io.swagger.codegen.languages.JMeterCodegen
io.swagger.codegen.languages.NodeJSServerCodegen io.swagger.codegen.languages.NodeJSServerCodegen
io.swagger.codegen.languages.ObjcClientCodegen io.swagger.codegen.languages.ObjcClientCodegen

View File

@ -0,0 +1,30 @@
package io.swagger.codegen.options;
import io.swagger.codegen.CodegenConstants;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
public class JavascriptClosureAnularClientOptionsProvider implements OptionsProvider {
public static final String SORT_PARAMS_VALUE = "false";
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
@Override
public String getLanguage() {
return "javascript-closure-angular";
}
@Override
public Map<String, String> createOptions() {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<String, String>();
return builder.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE)
.put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE)
.build();
}
@Override
public boolean isServer() {
return false;
}
}