[TypeScript][Aurelia] Create Aurelia code generator #5987 (#5991)

* [TypeScript][Aurelia] Create Aurelia code generator #5987

* Implement authentication methods for Aurelia #5987

* Support form data in Aurelia #5987

* Generate an index.ts file for Aurelia #5987

* Add return type to Aurelia model imports #5987

* Add Aurelia client options test #5987
This commit is contained in:
Konstantin Simon Maria Möllers 2017-07-23 09:38:21 +02:00 committed by wing328
parent e298964489
commit 21619c5320
16 changed files with 731 additions and 0 deletions

View File

@ -0,0 +1,131 @@
package io.swagger.codegen.languages;
import io.swagger.codegen.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
public class TypeScriptAureliaClientCodegen extends AbstractTypeScriptClientCodegen {
public static final String NPM_NAME = "npmName";
public static final String NPM_VERSION = "npmVersion";
protected String npmName = null;
protected String npmVersion = "1.0.0";
public TypeScriptAureliaClientCodegen() {
super();
apiTemplateFiles.put("api.mustache", ".ts");
// clear import mapping (from default generator) as TS does not use it
// at the moment
importMapping.clear();
outputFolder = "generated-code/typescript-aurelia";
embeddedTemplateDir = templateDir = "typescript-aurelia";
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"));
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(NPM_NAME)) {
this.setNpmName(additionalProperties.get(NPM_NAME).toString());
}
if (additionalProperties.containsKey(NPM_VERSION)) {
this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString());
}
// Set supporting files
supportingFiles.add(new SupportingFile("models.mustache", "", "models.ts"));
supportingFiles.add(new SupportingFile("index.ts.mustache", "", "index.ts"));
supportingFiles.add(new SupportingFile("Api.ts.mustache", "", "Api.ts"));
supportingFiles.add(new SupportingFile("AuthStorage.ts.mustache", "", "AuthStorage.ts"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("README.md", "", "README.md"));
supportingFiles.add(new SupportingFile("package.json.mustache", "", "package.json"));
supportingFiles.add(new SupportingFile("tsconfig.json.mustache", "", "tsconfig.json"));
supportingFiles.add(new SupportingFile("tslint.json.mustache", "", "tslint.json"));
supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore"));
}
@Override
public String getName() {
return "typescript-aurelia";
}
@Override
public String getHelp() {
return "Generates a TypeScript client library for the Aurelia framework (beta).";
}
public String getNpmName() {
return npmName;
}
public void setNpmName(String npmName) {
this.npmName = npmName;
}
public String getNpmVersion() {
return npmVersion;
}
public void setNpmVersion(String npmVersion) {
this.npmVersion = npmVersion;
}
@Override
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
objs = super.postProcessOperations(objs);
HashSet<String> modelImports = new HashSet<>();
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation op : operationList) {
// Aurelia uses "asGet", "asPost", ... methods; change the method format
op.httpMethod = initialCaps(op.httpMethod.toLowerCase());
// Collect models to be imported
for (CodegenParameter param : op.allParams) {
if (!param.isPrimitiveType) {
modelImports.add(param.dataType);
}
}
if (op.returnBaseType != null && !op.returnTypeIsPrimitive) {
modelImports.add(op.returnBaseType);
}
}
objs.put("modelImports", modelImports);
return objs;
}
@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
// process enum in models
List<Object> models = (List<Object>) postProcessModelsEnum(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) {
// name enum with model name, e.g. StatuEnum => PetStatusEnum
if (Boolean.TRUE.equals(var.isEnum)) {
var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + var.enumName);
var.enumName = cm.classname + var.enumName;
}
}
}
return objs;
}
}

View File

@ -69,6 +69,7 @@ io.swagger.codegen.languages.SwiftCodegen
io.swagger.codegen.languages.TizenClientCodegen io.swagger.codegen.languages.TizenClientCodegen
io.swagger.codegen.languages.TypeScriptAngular2ClientCodegen io.swagger.codegen.languages.TypeScriptAngular2ClientCodegen
io.swagger.codegen.languages.TypeScriptAngularClientCodegen io.swagger.codegen.languages.TypeScriptAngularClientCodegen
io.swagger.codegen.languages.TypeScriptAureliaClientCodegen
io.swagger.codegen.languages.TypeScriptFetchClientCodegen io.swagger.codegen.languages.TypeScriptFetchClientCodegen
io.swagger.codegen.languages.TypeScriptJqueryClientCodegen io.swagger.codegen.languages.TypeScriptJqueryClientCodegen
io.swagger.codegen.languages.TypeScriptNodeClientCodegen io.swagger.codegen.languages.TypeScriptNodeClientCodegen

View File

@ -0,0 +1,65 @@
{{>licenseInfo}}
import { HttpClient } from 'aurelia-http-client';
import { AuthStorage } from './AuthStorage';
const BASE_PATH = '{{{basePath}}}'.replace(/\/+$/, '');
export class Api {
basePath: string;
httpClient: HttpClient;
authStorage: AuthStorage;
constructor(httpClient: HttpClient, authStorage: AuthStorage, basePath: string = BASE_PATH) {
this.basePath = basePath;
this.httpClient = httpClient;
this.authStorage = authStorage;
}
/**
* Encodes a query string.
*
* @param params The params to encode.
* @return An encoded query string.
*/
protected queryString(params: { [key: string]: any }): string {
const queries = [];
for (let key in params) {
const value = this.toString(params[key]);
if (value != null) {
queries.push(`${key}=${encodeURIComponent(value)}`);
}
}
return queries.join('&');
}
/**
* Converts a value to string.
*
* @param value The value to convert.
*/
protected toString(value: any): string | null {
if (value === null) {
return null;
}
switch (typeof value) {
case 'undefined': return null;
case 'boolean': return value ? 'true' : 'false';
case 'string': return value;
default: return '' + value;
}
}
/**
* Ensures that a given parameter is set.
*
* @param context A name for the callee's context.
* @param params The parameters being set.
* @param paramName The required parameter to check.
*/
protected ensureParamIsSet<T>(context: string, params: T, paramName: keyof T): void {
if (null === params[paramName]) {
throw new Error(`Missing required parameter ${paramName} when calling ${context}`);
}
}
}

View File

@ -0,0 +1,38 @@
{{>licenseInfo}}
/**
* Class to storage authentication data
*/
export class AuthStorage {
private storage: Map<string, string>;
constructor() {
this.storage = new Map();
}
{{#authMethods}}
/**
* Sets the {{name}} auth method value.
*
* @param value The new value to set for {{name}}.
*/
set{{name}}(value: string): this {
this.storage.set('{{name}}', value);
return this;
}
/**
* Removes the {{name}} auth method value.
*/
remove{{name}}(): this {
this.storage.delete('{{name}}');
return this;
}
/**
* Gets the {{name}} auth method value.
*/
get{{name}}(): null | string {
return this.storage.get('{{name}}') || null;
}
{{/authMethods}}
}

View File

@ -0,0 +1,55 @@
# TypeScript-Aurelia
This generator creates TypeScript/JavaScript client that is injectable by [Aurelia](http://aurelia.io/).
The generated Node module can be used in the following environments:
Environment
* Node.js
* Webpack
* Browserify
Language level
* ES5 - you must have a Promises/A+ library installed
* ES6
Module system
* CommonJS
* ES6 module system
It can be used in both TypeScript and JavaScript. In TypeScript, the definition should be automatically resolved via `package.json`. ([Reference](http://www.typescriptlang.org/docs/handbook/typings-for-npm-packages.html))
### Installation ###
`swagger-codegen` does not generate JavaScript directly. The generated Node module comes with `package.json` that bundles `typescript` and `typings` so it can self-compile during `prepublish` stage. The should be run automatically during `npm install` or `npm publish`.
CAVEAT: Due to [privilege implications](https://docs.npmjs.com/misc/scripts#user), `npm` would skip all scripts if the user is `root`. You would need to manually run it with `npm run prepublish` or run `npm install --unsafe-perm`.
#### NPM ####
You may publish the module to NPM. In this case, you would be able to install the module as any other NPM module. It maybe useful to use [scoped packages](https://docs.npmjs.com/misc/scope).
You can also use `npm link` to link the module. However, this would not modify `package.json` of the installing project, as such you would need to relink every time you deploy that project.
You can also directly install the module using `npm install file_path`. If you do `npm install file_path --save`, NPM will save relative path to `package.json`. In this case, `npm install` and `npm shrinkwrap` may misbehave. You would need to manually edit `package.json` and replace it with absolute path.
Regardless of which method you deployed your NPM module, the ES6 module syntaxes are as follows:
```
import * as localName from 'npmName';
import {operationId} from 'npmName';
```
The CommonJS syntax is as follows:
```
import localName = require('npmName');
```
#### Direct copy/symlink ####
You may also simply copy or symlink the generated module into a directory under your project. The syntax of this is as follows:
With ES6 module syntax, the following syntaxes are supported:
```
import * as localName from './symlinkDir';
import {operationId} from './symlinkDir';
```
The CommonJS syntax is as follows:
```
import localName = require('./symlinkDir')';
```

View File

@ -0,0 +1,107 @@
{{>licenseInfo}}
import { autoinject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-http-client';
import { Api } from './Api';
import { AuthStorage } from './AuthStorage';
import {
{{#modelImports}} {{this}},
{{/modelImports}}
} from './models';
{{#operations}}
{{#operation}}
/**
* {{operationId}} - parameters interface
*/
export interface I{{operationIdCamelCase}}Params {
{{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}};
{{/allParams}}
}
{{/operation}}
/**
* {{classname}} - API class{{#description}}
* {{&description}}{{/description}}
*/
@autoinject()
export class {{classname}} extends Api {
/**
* Creates a new {{classname}} class.
*
* @param httpClient The Aurelia HTTP client to be injected.
*/
constructor(httpClient: HttpClient, authStorage: AuthStorage) {
super(httpClient, authStorage);
}
{{#operation}}
/**{{#summary}}
* {{summary}}
*{{/summary}}{{#notes}}
* {{notes}}{{/notes}}{{#allParams}}
* @param params.{{paramName}} {{description}}{{/allParams}}
*/
async {{nickname}}({{#hasParams}}params: I{{operationIdCamelCase}}Params{{/hasParams}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}any{{/returnType}}> {
// Verify required parameters are set
{{#allParams}}
{{#required}}
this.ensureParamIsSet('{{nickname}}', params, '{{paramName}}');
{{/required}}
{{/allParams}}
// Create URL to call
const url = `${this.basePath}{{{path}}}`{{#pathParams}}
.replace(`{${'{{baseName}}'}}`, `${params['{{paramName}}']}`){{/pathParams}};
const response = await this.httpClient.createRequest(url)
// Set HTTP method
.as{{httpMethod}}()
{{#hasQueryParams}}
// Set query parameters
.withParams({ {{#queryParams}}
'{{baseName}}': params['{{paramName}}'],{{/queryParams}}
})
{{/hasQueryParams}}
{{#hasFormParams}}
// Encode form parameters
.withHeader('content-type', 'application/x-www-form-urlencoded')
.withContent(this.queryString({ {{#formParams}}
'{{baseName}}': params['{{paramName}}'],{{/formParams}}
}))
{{/hasFormParams}}
{{#hasBodyParam}}
{{#bodyParam}}
// Encode body parameter
.withHeader('content-type', 'application/json')
.withContent(JSON.stringify(params['{{paramName}}'] || {}))
{{/bodyParam}}
{{/hasBodyParam}}
{{#headerParams}}
.withHeader('{{baseName}}', params['{{paramName}}']){{/headerParams}}
{{#authMethods}}
// Authentication '{{name}}' required
{{#isApiKey}}
{{#isKeyInHeader}}
.withHeader('{{keyParamName}}', this.authStorage.get{{name}}())
{{/isKeyInHeader}}
{{#isKeyInQuery}}
.withParams({ {{keyParamName}}: this.authStorage.get{{name}}() })
{{/isKeyInQuery}}
{{/isApiKey}}
{{/authMethods}}
// Send the request
.send();
if (response.statusCode < 200 || response.statusCode >= 300) {
throw new Error(response.content);
}
// Extract the content
return response.content;
}
{{/operation}}
}
{{/operations}}

View File

@ -0,0 +1,52 @@
#!/bin/sh
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
#
# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update"
git_user_id=$1
git_repo_id=$2
release_note=$3
if [ "$git_user_id" = "" ]; then
git_user_id="{{{gitUserId}}}"
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
fi
if [ "$git_repo_id" = "" ]; then
git_repo_id="{{{gitRepoId}}}"
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
fi
if [ "$release_note" = "" ]; then
release_note="{{{releaseNote}}}"
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
fi
# Initialize the local directory as a Git repository
git init
# Adds the files in the local repository and stages them for commit.
git add .
# Commits the tracked changes and prepares them to be pushed to a remote repository.
git commit -m "$release_note"
# Sets the new remote
git_remote=`git remote`
if [ "$git_remote" = "" ]; then # git remote not defined
if [ "$GIT_TOKEN" = "" ]; then
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git crediential in your environment."
git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git
else
git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git
fi
fi
git pull origin master
# Pushes (Forces) the changes in the local repository up to the remote repository
echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git"
git push origin master 2>&1 | grep -v 'To https'

View File

@ -0,0 +1,3 @@
wwwroot/*.js
node_modules
typings

View File

@ -0,0 +1,16 @@
{{>licenseInfo}}
export { Api } from './Api';
export { AuthStorage } from './AuthStorage';
{{#apiInfo}}
{{#apis}}
{{#operations}}
export { {{classname}} } from './{{classname}}';
{{/operations}}
{{/apis}}
{{/apiInfo}}
export {
{{#models}}
{{#model}} {{classname}},
{{/model}}
{{/models}}
} from './models';

View File

@ -0,0 +1,11 @@
/**
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/

View File

@ -0,0 +1,39 @@
{{>licenseInfo}}
{{#models}}
{{#model}}
{{#description}}
/**
* {{{description}}}
*/
{{/description}}
{{^isEnum}}
export interface {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{
{{/isEnum}}
{{#isEnum}}
export type {{{classname}}} = {{#allowableValues}}{{#values}}'{{{.}}}'{{^-last}} | {{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{#vars}}
{{#description}}
/**
* {{{description}}}
*/
{{/description}}
{{name}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}};
{{/vars}}
{{^isEnum}}
}
{{/isEnum}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
/**
* Enum for the {{name}} property.
*/
export type {{{enumName}}} = {{#allowableValues}}{{#values}}'{{{.}}}'{{^-last}} | {{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,21 @@
{
"name": "{{#npmName}}{{{npmName}}}{{/npmName}}{{^npmName}}typescript-fetch-api{{/npmName}}",
"version": "{{#npmVersion}}{{{npmVersion}}}{{/npmVersion}}{{^npmVersion}}0.0.0{{/npmVersion}}",
"license": "Unlicense",
"main": "./dist/api.js",
"browser": "./dist/api.js",
"typings": "./dist/api.d.ts",
"dependencies": {
{{^supportsES6}}"core-js": "^2.4.0",
{{/supportsES6}}"isomorphic-fetch": "^2.2.1"
},
"scripts" : {
"prepublish" : "typings install && tsc",
"test": "tslint api.ts"
},
"devDependencies": {
"tslint": "^3.15.1",
"typescript": "^1.8.10",
"typings": "^1.0.4"
}
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"declaration": true,
"target": "{{#supportsES6}}es6{{/supportsES6}}{{^supportsES6}}es5{{/supportsES6}}",
"module": "commonjs",
"noImplicitAny": true,
"outDir": "dist",
"rootDir": "."
},
"exclude": [
"dist",
"node_modules"
]
}

View File

@ -0,0 +1,101 @@
{
"jsRules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"spaces"
],
"no-duplicate-variable": true,
"no-eval": true,
"no-trailing-whitespace": true,
"no-unsafe-finally": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"double"
],
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
},
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"spaces"
],
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"double"
],
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@ -0,0 +1,41 @@
package io.swagger.codegen.options;
import com.google.common.collect.ImmutableMap;
import io.swagger.codegen.CodegenConstants;
import io.swagger.codegen.languages.TypeScriptAureliaClientCodegen;
import java.util.Map;
public class TypeScriptAureliaClientOptionsProvider implements OptionsProvider {
public static final String SORT_PARAMS_VALUE = "false";
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
public static final Boolean SUPPORTS_ES6_VALUE = false;
public static final String MODEL_PROPERTY_NAMING_VALUE = "camelCase";
private static final String NMP_NAME = "npmName";
private static final String NMP_VERSION = "1.0.0";
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
@Override
public String getLanguage() {
return "typescript-aurelia";
}
@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)
.put(CodegenConstants.MODEL_PROPERTY_NAMING, MODEL_PROPERTY_NAMING_VALUE)
.put(CodegenConstants.SUPPORTS_ES6, String.valueOf(SUPPORTS_ES6_VALUE))
.put(TypeScriptAureliaClientCodegen.NPM_NAME, NMP_NAME)
.put(TypeScriptAureliaClientCodegen.NPM_VERSION, NMP_VERSION)
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
.build();
}
@Override
public boolean isServer() {
return false;
}
}

View File

@ -0,0 +1,36 @@
package io.swagger.codegen.typescript.aurelia;
import io.swagger.codegen.AbstractOptionsTest;
import io.swagger.codegen.CodegenConfig;
import io.swagger.codegen.languages.TypeScriptAureliaClientCodegen;
import io.swagger.codegen.options.TypeScriptAureliaClientOptionsProvider;
import mockit.Expectations;
import mockit.Tested;
public class TypeScriptAureliaClientOptionsTest extends AbstractOptionsTest {
@Tested
private TypeScriptAureliaClientCodegen clientCodegen;
public TypeScriptAureliaClientOptionsTest() {
super(new TypeScriptAureliaClientOptionsProvider());
}
@Override
protected CodegenConfig getCodegenConfig() {
return clientCodegen;
}
@SuppressWarnings("unused")
@Override
protected void setExpectations() {
new Expectations(clientCodegen) {{
clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(TypeScriptAureliaClientOptionsProvider.SORT_PARAMS_VALUE));
times = 1;
clientCodegen.setModelPropertyNaming(TypeScriptAureliaClientOptionsProvider.MODEL_PROPERTY_NAMING_VALUE);
times = 1;
clientCodegen.setSupportsES6(TypeScriptAureliaClientOptionsProvider.SUPPORTS_ES6_VALUE);
times = 1;
}};
}
}