diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/TypeScriptFetchClientCodegen.java new file mode 100644 index 00000000000..6d34adfad8d --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/TypeScriptFetchClientCodegen.java @@ -0,0 +1,38 @@ +package io.swagger.codegen.languages; + +import io.swagger.codegen.SupportingFile; + +import java.io.File; + +public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodegen { + + @Override + public String getName() { + return "typescript-fetch"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript client library using Fetch API."; + } + + @Override + public void processOpts() { + super.processOpts(); + final String defaultFolder = apiPackage().replace('.', File.separatorChar); + + supportingFiles.add(new SupportingFile("api.mustache", null, "api.ts")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("assign.ts", defaultFolder, "assign.ts")); + supportingFiles.add(new SupportingFile("package.json", "", "package.json")); + supportingFiles.add(new SupportingFile("typings.json", "", "typings.json")); + supportingFiles.add(new SupportingFile("tsconfig.json", "", "tsconfig.json")); + } + + public TypeScriptFetchClientCodegen() { + super(); + outputFolder = "generated-code/typescript-fetch"; + embeddedTemplateDir = templateDir = "TypeScript-Fetch"; + } + +} diff --git a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index 22823c95626..39eca08ccdb 100644 --- a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -38,6 +38,7 @@ io.swagger.codegen.languages.TizenClientCodegen io.swagger.codegen.languages.TypeScriptAngular2ClientCodegen io.swagger.codegen.languages.TypeScriptAngularClientCodegen io.swagger.codegen.languages.TypeScriptNodeClientCodegen +io.swagger.codegen.languages.TypeScriptFetchClientCodegen io.swagger.codegen.languages.AkkaScalaClientCodegen io.swagger.codegen.languages.CsharpDotNet2ClientCodegen io.swagger.codegen.languages.ClojureClientCodegen diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/api.mustache b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/api.mustache new file mode 100644 index 00000000000..02e6816ff26 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/api.mustache @@ -0,0 +1,133 @@ +import * as querystring from 'querystring'; +import * as fetch from 'isomorphic-fetch'; +import {assign} from './assign'; + + +{{#models}} +{{#model}} +{{#description}} +/** + * {{{description}}} + */ +{{/description}} +export interface {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ +{{#vars}} +{{#description}} + + /** + * {{{description}}} + */ +{{/description}} + "{{name}}"{{^required}}?{{/required}}: {{#isEnum}}{{classname}}.{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}; +{{/vars}} +} + +{{#hasEnums}} +{{#vars}} +{{#isEnum}} + +export enum {{datatypeWithEnum}} { {{#allowableValues}}{{#values}} + {{.}} = '{{.}}'{{^-last}},{{/-last}}{{/values}}{{/allowableValues}} +} +{{/isEnum}} +{{/vars}} +{{/hasEnums}} +{{/model}} +{{/models}} + +{{#apiInfo}} +{{#apis}} +{{#operations}} +//export namespace {{package}} { + 'use strict'; + +{{#description}} + /** + * {{&description}} + */ +{{/description}} + export class {{classname}} { + protected basePath = '{{basePath}}'; + public defaultHeaders : any = {}; + + constructor(basePath?: string) { + if (basePath) { + this.basePath = basePath; + } + } + +{{#operation}} + /** + * {{summary}} + * {{notes}} + {{#allParams}}* @param {{paramName}} {{description}} + {{/allParams}}*/ + public {{nickname}} (params: { {{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}};{{/allParams}} }, extraQueryParams?: any, extraFetchParams?: any ) : Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}{}{{/returnType}}> { + const localVarPath = this.basePath + '{{path}}'{{#pathParams}} + .replace('{' + '{{baseName}}' + '}', String(params.{{paramName}})){{/pathParams}}; + + let queryParameters: any = assign({}, extraQueryParams); + let headerParams: any = assign({}, this.defaultHeaders); +{{#hasFormParams}} + let formParams: any = {}; + headerParams['Content-Type'] = 'application/x-www-form-urlencoded'; + +{{/hasFormParams}} +{{#hasBodyParam}} + headerParams['Content-Type'] = 'application/json'; + +{{/hasBodyParam}} +{{#allParams}} +{{#required}} + // verify required parameter '{{paramName}}' is set + if (params.{{paramName}} == null) { + throw new Error('Missing required parameter {{paramName}} when calling {{nickname}}'); + } +{{/required}} +{{/allParams}} +{{#queryParams}} + if (params.{{paramName}} !== undefined) { + queryParameters['{{baseName}}'] = params.{{paramName}}; + } + +{{/queryParams}} +{{#headerParams}} + headerParams['{{baseName}}'] = params.{{paramName}}; + +{{/headerParams}} +{{#formParams}} + formParams['{{baseName}}'] = params.{{paramName}}; + +{{/formParams}} + let fetchParams = { + method: '{{httpMethod}}', + headers: headerParams, + {{#bodyParam}}body: JSON.stringify(params.{{paramName}}), + {{/bodyParam}} + {{#hasFormParams}}body: querystring.stringify(formParams), + {{/hasFormParams}} + + }; + + if (extraFetchParams) { + fetchParams = assign(fetchParams, extraFetchParams); + } + + let localVarPathWithQueryParameters = localVarPath + (localVarPath.indexOf('?') !== -1 ? '&' : '?') + querystring.stringify(queryParameters); + + return fetch(localVarPathWithQueryParameters, fetchParams).then((response) => { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } else { + var error = new Error(response.statusText); + error['response'] = response; + throw error; + } + }); + } +{{/operation}} + } +//} +{{/operations}} +{{/apis}} +{{/apiInfo}} diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/assign.ts b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/assign.ts new file mode 100644 index 00000000000..040f87d7d98 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/assign.ts @@ -0,0 +1,18 @@ +export function assign (target, ...args) { + 'use strict'; + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (let source of args) { + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; +}; \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/git_push.sh.mustache b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/git_push.sh.mustache new file mode 100755 index 00000000000..e153ce23ecf --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/git_push.sh.mustache @@ -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' + diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/package.json b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/package.json new file mode 100644 index 00000000000..94206a9f3a1 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "isomorphic-fetch": "^2.2.1" + } +} diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/tsconfig.json b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/tsconfig.json new file mode 100644 index 00000000000..ea2d7b83410 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es5", + "sourceMap": true + }, + "exclude": [ + "node_modules", + "typings/browser", + "typings/main", + "typings/main.d.ts" + ] +} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/typings.json b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/typings.json new file mode 100644 index 00000000000..c22f086f7f0 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/TypeScript-Fetch/typings.json @@ -0,0 +1,9 @@ +{ + "version": false, + "dependencies": {}, + "ambientDependencies": { + "es6-promise": "registry:dt/es6-promise#0.0.0+20160423074304", + "node": "registry:dt/node#4.0.0+20160423143914", + "isomorphic-fetch": "github:leonyu/DefinitelyTyped/isomorphic-fetch/isomorphic-fetch.d.ts#isomorphic-fetch-fix-module" + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/TypeScriptFetchClientOptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/TypeScriptFetchClientOptionsProvider.java new file mode 100644 index 00000000000..6981dd3702c --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/TypeScriptFetchClientOptionsProvider.java @@ -0,0 +1,31 @@ +package io.swagger.codegen.options; + +import com.google.common.collect.ImmutableMap; +import io.swagger.codegen.CodegenConstants; + +import java.util.Map; + +public class TypeScriptFetchClientOptionsProvider implements OptionsProvider { + public static final String SORT_PARAMS_VALUE = "false"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String MODEL_PROPERTY_NAMING_VALUE = "camelCase"; + + @Override + public String getLanguage() { + return "typescript-fetch"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + 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) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchClientOptionsTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchClientOptionsTest.java new file mode 100644 index 00000000000..d6d0849de29 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchClientOptionsTest.java @@ -0,0 +1,34 @@ +package io.swagger.codegen.typescriptfetch; + +import io.swagger.codegen.AbstractOptionsTest; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.languages.TypeScriptFetchClientCodegen; +import io.swagger.codegen.options.TypeScriptFetchClientOptionsProvider; +import mockit.Expectations; +import mockit.Tested; + +public class TypeScriptFetchClientOptionsTest extends AbstractOptionsTest { + + @Tested + private TypeScriptFetchClientCodegen clientCodegen; + + public TypeScriptFetchClientOptionsTest() { + super(new TypeScriptFetchClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(TypeScriptFetchClientOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + clientCodegen.setModelPropertyNaming(TypeScriptFetchClientOptionsProvider.MODEL_PROPERTY_NAMING_VALUE); + times = 1; + }}; + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchModelTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchModelTest.java new file mode 100644 index 00000000000..d2173fdb710 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/typescriptfetch/TypeScriptFetchModelTest.java @@ -0,0 +1,177 @@ +package io.swagger.codegen.typescriptfetch; + +import com.google.common.collect.Sets; +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenProperty; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.languages.TypeScriptFetchClientCodegen; +import io.swagger.models.ArrayModel; +import io.swagger.models.Model; +import io.swagger.models.ModelImpl; +import io.swagger.models.properties.*; +import org.testng.Assert; +import org.testng.annotations.Test; + +@SuppressWarnings("static-method") +public class TypeScriptFetchModelTest { + + @Test(description = "convert a simple TypeScript Angular model") + public void simpleModelTest() { + final Model model = new ModelImpl() + .description("a sample model") + .property("id", new LongProperty()) + .property("name", new StringProperty()) + .property("createdAt", new DateTimeProperty()) + .required("id") + .required("name"); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 3); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "number"); + Assert.assertEquals(property1.name, "id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "number"); + Assert.assertTrue(property1.hasMore); + Assert.assertTrue(property1.required); + Assert.assertTrue(property1.isNotContainer); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.datatype, "string"); + Assert.assertEquals(property2.name, "name"); + Assert.assertEquals(property2.defaultValue, "null"); + Assert.assertEquals(property2.baseType, "string"); + Assert.assertTrue(property2.hasMore); + Assert.assertTrue(property2.required); + Assert.assertTrue(property2.isNotContainer); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.complexType, null); + Assert.assertEquals(property3.datatype, "Date"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertNull(property3.hasMore); + Assert.assertNull(property3.required); + Assert.assertTrue(property3.isNotContainer); + } + + @Test(description = "convert a model with list property") + public void listPropertyTest() { + final Model model = new ModelImpl() + .description("a sample model") + .property("id", new LongProperty()) + .property("urls", new ArrayProperty().items(new StringProperty())) + .required("id"); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 2); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "number"); + Assert.assertEquals(property1.name, "id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "number"); + Assert.assertTrue(property1.hasMore); + Assert.assertTrue(property1.required); + Assert.assertTrue(property1.isNotContainer); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "urls"); + Assert.assertEquals(property2.datatype, "Array"); + Assert.assertEquals(property2.name, "urls"); + Assert.assertEquals(property2.baseType, "Array"); + Assert.assertNull(property2.hasMore); + Assert.assertNull(property2.required); + Assert.assertTrue(property2.isContainer); + } + + @Test(description = "convert a model with complex property") + public void complexPropertyTest() { + final Model model = new ModelImpl() + .description("a sample model") + .property("children", new RefProperty("#/definitions/Children")); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "children"); + Assert.assertEquals(property1.datatype, "Children"); + Assert.assertEquals(property1.name, "children"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "Children"); + Assert.assertNull(property1.required); + Assert.assertTrue(property1.isNotContainer); + } + + @Test(description = "convert a model with complex list property") + public void complexListPropertyTest() { + final Model model = new ModelImpl() + .description("a sample model") + .property("children", new ArrayProperty() + .items(new RefProperty("#/definitions/Children"))); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "children"); + Assert.assertEquals(property1.complexType, "Children"); + Assert.assertEquals(property1.datatype, "Array"); + Assert.assertEquals(property1.name, "children"); + Assert.assertEquals(property1.baseType, "Array"); + Assert.assertNull(property1.required); + Assert.assertTrue(property1.isContainer); + } + + @Test(description = "convert an array model") + public void arrayModelTest() { + final Model model = new ArrayModel() + .description("an array model") + .items(new RefProperty("#/definitions/Children")); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "an array model"); + Assert.assertEquals(cm.vars.size(), 0); + } + + @Test(description = "convert a map model") + public void mapModelTest() { + final Model model = new ModelImpl() + .description("a map model") + .additionalProperties(new RefProperty("#/definitions/Children")); + final DefaultCodegen codegen = new TypeScriptFetchClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a map model"); + Assert.assertEquals(cm.vars.size(), 0); + Assert.assertEquals(cm.imports.size(), 1); + Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1); + } +}