├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── rhyme │ └── codegen │ └── TypeScriptFetchApiGenerator.java └── resources ├── META-INF └── services │ └── io.swagger.codegen.CodegenConfig └── typescript-fetch-api └── api.mustache /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger Codegen for the TypeScript with Fetch API 2 | 3 | ## Overview 4 | 5 | This is a boiler-plate project to generate your own client library with Swagger. Its goal is 6 | to get you started with the basic plumbing so you can put in your own logic. It won't work without 7 | your changes applied. 8 | 9 | ## What's Swagger? 10 | 11 | The goal of Swagger™ is to define a standard, language-agnostic interface to REST APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined via Swagger, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interfaces have done for lower-level programming, Swagger removes the guesswork in calling the service. 12 | 13 | 14 | Check out [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) for additional information about the Swagger project, including additional libraries with support for other languages and more. 15 | 16 | ## How do I use this? 17 | 18 | At this point, you've likely generated a client setup. It will include something along these lines: 19 | 20 | ``` 21 | . 22 | |- README.md // this file 23 | |- pom.xml // build script 24 | |-- src 25 | |--- main 26 | |---- java 27 | |----- com.rhyme.codegen.TypeScriptFetchApiGenerator.java // generator file 28 | |---- resources 29 | |----- typescript-api-fetch // template files 30 | |----- META-INF 31 | |------ services 32 | |------- io.swagger.codegen.CodegenConfig 33 | ``` 34 | 35 | You _will_ need to make changes in at least the following: 36 | 37 | `TypeScriptFetchApiGenerator.java` 38 | 39 | Templates in this folder: 40 | 41 | `src/main/resources/typescript-fetch-api` 42 | 43 | Once modified, you can run this: 44 | 45 | ``` 46 | mvn package 47 | ``` 48 | 49 | In your generator project. A single jar file will be produced in `target`. You can now use that with codegen: 50 | 51 | ``` 52 | java -cp /path/to/swagger-codegen-cli.jar:/path/to/your.jar io.swagger.codegen.Codegen -l typescript-fetch-api -i /path/to/swagger.yaml -o ./test 53 | ``` 54 | 55 | Now your templates are available to the client generator and you can write output values 56 | 57 | ## But how do I modify this? 58 | 59 | The `TypeScriptFetchApiGenerator.java` has comments in it--lots of comments. There is no good substitute 60 | for reading the code more, though. See how the `TypeScriptFetchApiGenerator` implements `CodegenConfig`. 61 | That class has the signature of all values that can be overridden. 62 | 63 | For the templates themselves, you have a number of values available to you for generation. 64 | You can execute the `java` command from above while passing different debug flags to show 65 | the object you have available during client generation: 66 | 67 | ``` 68 | # The following additional debug options are available for all codegen targets: 69 | # -DdebugSwagger prints the OpenAPI Specification as interpreted by the codegen 70 | # -DdebugModels prints models passed to the template engine 71 | # -DdebugOperations prints operations passed to the template engine 72 | # -DdebugSupportingFiles prints additional data passed to the template engine 73 | 74 | java -DdebugOperations -cp /path/to/swagger-codegen-cli.jar:/path/to/your.jar io.swagger.codegen.Codegen -l typescript-fetch-api -i /path/to/swagger.yaml -o ./test 75 | ``` 76 | 77 | Will, for example, output the debug info for operations. You can use this info 78 | in the `api.mustache` file. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | io.swagger 5 | typescript-fetch-api-swagger-codegen 6 | jar 7 | typescript-fetch-api-swagger-codegen 8 | 1.0.4 9 | 10 | 2.2.0 11 | 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-surefire-plugin 17 | 2.12 18 | 19 | 20 | 21 | loggerPath 22 | conf/log4j.properties 23 | 24 | 25 | -Xms512m -Xmx1500m 26 | methods 27 | pertest 28 | 29 | 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-jar-plugin 35 | 2.2 36 | 37 | 38 | 39 | jar 40 | test-jar 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.codehaus.mojo 50 | build-helper-maven-plugin 51 | 52 | 53 | add_sources 54 | generate-sources 55 | 56 | add-source 57 | 58 | 59 | 60 | src/main/java 61 | 62 | 63 | 64 | 65 | add_test_sources 66 | generate-test-sources 67 | 68 | add-test-source 69 | 70 | 71 | 72 | src/test/java 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-compiler-plugin 81 | 2.3.2 82 | 83 | 1.6 84 | 1.6 85 | 86 | 87 | 88 | 89 | 90 | 91 | io.swagger 92 | swagger-codegen 93 | ${swagger-codegen-version} 94 | provided 95 | 96 | 97 | 98 | 2.2.2 99 | 1.0.0 100 | 4.8.1 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/com/rhyme/codegen/TypeScriptFetchApiGenerator.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.codegen; 2 | 3 | import io.swagger.codegen.CodegenModel; 4 | import io.swagger.codegen.CodegenParameter; 5 | import io.swagger.codegen.CodegenProperty; 6 | import io.swagger.codegen.SupportingFile; 7 | import io.swagger.codegen.CodegenConstants.MODEL_PROPERTY_NAMING_TYPE; 8 | import io.swagger.codegen.languages.AbstractTypeScriptClientCodegen; 9 | import io.swagger.models.ModelImpl; 10 | import io.swagger.models.properties.ArrayProperty; 11 | import io.swagger.models.properties.FileProperty; 12 | import io.swagger.models.properties.MapProperty; 13 | import io.swagger.models.properties.ObjectProperty; 14 | import io.swagger.models.properties.Property; 15 | 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.TreeSet; 19 | 20 | public class TypeScriptFetchApiGenerator extends AbstractTypeScriptClientCodegen { 21 | 22 | public TypeScriptFetchApiGenerator() { 23 | super(); 24 | 25 | // clear import mapping (from default generator) as TS does not use it 26 | // at the moment 27 | importMapping.clear(); 28 | 29 | outputFolder = "generated-code/typescript-fetch-api"; 30 | embeddedTemplateDir = templateDir = "typescript-fetch-api"; 31 | } 32 | 33 | @Override 34 | protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, ModelImpl swaggerModel) { 35 | codegenModel.additionalPropertiesType = getSwaggerType(swaggerModel.getAdditionalProperties()); 36 | addImport(codegenModel, codegenModel.additionalPropertiesType); 37 | } 38 | 39 | @Override 40 | public String getModelPropertyNaming() { 41 | return "original"; 42 | } 43 | 44 | @Override 45 | public String getTypeDeclaration(Property p) { 46 | Property inner; 47 | if (p instanceof ArrayProperty) { 48 | ArrayProperty mp1 = (ArrayProperty)p; 49 | inner = mp1.getItems(); 50 | return this.getSwaggerType(p) + "<" + this.getTypeDeclaration(inner) + ">"; 51 | } else if (p instanceof MapProperty) { 52 | MapProperty mp = (MapProperty)p; 53 | inner = mp.getAdditionalProperties(); 54 | return "{ [key: string]: " + this.getTypeDeclaration(inner) + "; }"; 55 | } else if (p instanceof FileProperty || p instanceof ObjectProperty) { 56 | return "any"; 57 | } else { 58 | return super.getTypeDeclaration(p); 59 | } 60 | } 61 | 62 | @Override 63 | public String getSwaggerType(Property p) { 64 | String swaggerType = super.getSwaggerType(p); 65 | if (languageSpecificPrimitives.contains(swaggerType)) { 66 | return swaggerType; 67 | } 68 | return addModelPrefix(swaggerType); 69 | } 70 | 71 | @Override 72 | public void postProcessParameter(CodegenParameter parameter) { 73 | super.postProcessParameter(parameter); 74 | parameter.dataType = addModelPrefix(parameter.dataType); 75 | } 76 | 77 | 78 | private String addModelPrefix(String swaggerType) { 79 | String type = null; 80 | if (typeMapping.containsKey(swaggerType)) { 81 | type = typeMapping.get(swaggerType); 82 | } else { 83 | type = swaggerType; 84 | } 85 | return type; 86 | } 87 | 88 | @Override 89 | public void processOpts() { 90 | super.processOpts(); 91 | supportingFiles.add(new SupportingFile("api.mustache", "", "api.ts")); 92 | } 93 | 94 | @Override 95 | public String getName() { 96 | return "typescript-fetch-api"; 97 | } 98 | 99 | @Override 100 | public String getHelp() { 101 | return "Generates a TypeScript client library using Fetch API (beta)."; 102 | } 103 | 104 | @Override 105 | public Map postProcessModels(Map objs) { 106 | // process enum in models 107 | @SuppressWarnings("unchecked") 108 | List models = (List) postProcessModelsEnum(objs).get("models"); 109 | for (Object _mo : models) { 110 | @SuppressWarnings("unchecked") 111 | Map mo = (Map) _mo; 112 | CodegenModel cm = (CodegenModel) mo.get("model"); 113 | cm.imports = new TreeSet(cm.imports); 114 | for (CodegenProperty var : cm.vars) { 115 | // name enum with model name, e.g. StatuEnum => PetStatusEnum 116 | if (Boolean.TRUE.equals(var.isEnum)) { 117 | var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + var.enumName); 118 | var.enumName = cm.classname + var.enumName; 119 | } 120 | } 121 | } 122 | 123 | return objs; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig: -------------------------------------------------------------------------------- 1 | com.rhyme.codegen.TypeScriptFetchApiGenerator -------------------------------------------------------------------------------- /src/main/resources/typescript-fetch-api/api.mustache: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import * as url from "url"; 3 | 4 | interface Dictionary { [index: string]: T; } 5 | export interface FetchAPI { (url: string, init?: any): Promise; } 6 | 7 | const BasePath = "{{basePath}}"; 8 | const FetchImpl = fetch; 9 | 10 | export interface FetchArgs { 11 | url: string; 12 | options: any; 13 | } 14 | 15 | export class BaseAPI { 16 | basePath: string; 17 | fetch: FetchAPI; 18 | 19 | constructor(fetch: FetchAPI = FetchImpl, basePath: string = BasePath) { 20 | this.basePath = basePath; 21 | this.fetch = fetch; 22 | } 23 | } 24 | 25 | {{#models}} 26 | {{#model}} 27 | {{#description}} 28 | /** 29 | * {{{description}}} 30 | */ 31 | {{/description}} 32 | {{#isEnum}} 33 | export enum {{classname}} { 34 | {{#allowableValues}} 35 | {{#enumVars}} 36 | {{{name}}} = {{{value}}}{{^-last}},{{/-last}} 37 | {{/enumVars}} 38 | {{/allowableValues}} 39 | } 40 | {{/isEnum}} 41 | {{^isEnum}} 42 | export interface {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ 43 | {{#vars}} 44 | {{#description}} 45 | /** 46 | * {{{description}}} 47 | */ 48 | {{/description}} 49 | "{{name}}"{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}; 50 | {{/vars}} 51 | } 52 | 53 | {{/isEnum}} 54 | 55 | {{/model}} 56 | {{/models}} 57 | 58 | {{#apiInfo}} 59 | {{#apis}} 60 | {{#operations}} 61 | 62 | /** 63 | * {{classname}} - fetch parameter creator{{#description}} 64 | * {{&description}}{{/description}} 65 | */ 66 | export const {{classname}}FetchParamCreactor = { 67 | {{#operation}} 68 | /** {{#summary}} 69 | * {{summary}}{{/summary}}{{#notes}} 70 | * {{notes}}{{/notes}}{{#allParams}} 71 | * @param {{paramName}} {{description}}{{/allParams}} 72 | */ 73 | {{nickname}}({{#hasParams}}params: { {{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}};{{/allParams}} }{{/hasParams}}): FetchArgs { 74 | {{#allParams}} 75 | {{#required}} 76 | // verify required parameter "{{paramName}}" is set 77 | if (params["{{paramName}}"] == null) { 78 | throw new Error("Missing required parameter {{paramName}} when calling {{nickname}}"); 79 | } 80 | {{/required}} 81 | {{/allParams}} 82 | const baseUrl = `{{path}}`{{#pathParams}} 83 | .replace(`{${"{{baseName}}"}}`, `${ params.{{paramName}} }`){{/pathParams}}; 84 | let urlObj = url.parse(baseUrl, true); 85 | {{#hasQueryParams}} 86 | urlObj.query = {{#supportsES6}}Object.{{/supportsES6}}assign({}, urlObj.query, { {{#queryParams}} 87 | "{{baseName}}": params.{{paramName}},{{/queryParams}} 88 | }); 89 | {{/hasQueryParams}} 90 | let fetchOptions: RequestInit = { method: "{{httpMethod}}" }; 91 | 92 | let contentTypeHeader: Dictionary = {}; 93 | {{#hasFormParams}} 94 | contentTypeHeader = { "Content-Type": "application/x-www-form-urlencoded" }; 95 | fetchOptions.body = querystring.stringify({ {{#formParams}} 96 | "{{baseName}}": params.{{paramName}},{{/formParams}} 97 | }); 98 | {{/hasFormParams}} 99 | {{#hasBodyParam}} 100 | contentTypeHeader = { "Content-Type": "application/json" };{{#bodyParam}} 101 | if (params["{{paramName}}"]) { 102 | fetchOptions.body = JSON.stringify(params["{{paramName}}"] || {}); 103 | }{{/bodyParam}} 104 | {{/hasBodyParam}} 105 | {{#hasHeaderParam}} 106 | fetchOptions.headers = {{#supportsES6}}Object.{{/supportsES6}}assign({ {{#headerParams}} 107 | "{{baseName}}": params.{{paramName}},{{/headerParams}} 108 | }, contentTypeHeader); 109 | {{/hasHeaderParam}} 110 | {{^hasHeaderParam}} 111 | fetchOptions.headers = contentTypeHeader; 112 | {{/hasHeaderParam}} 113 | return { 114 | url: url.format(urlObj), 115 | options: fetchOptions, 116 | }; 117 | }, 118 | {{/operation}} 119 | } 120 | 121 | /** 122 | * {{classname}} - functional programming interface{{#description}} 123 | * {{&description}}{{/description}} 124 | */ 125 | export const {{classname}}Fp = { 126 | {{#operation}} 127 | /** {{#summary}} 128 | * {{summary}}{{/summary}}{{#notes}} 129 | * {{notes}}{{/notes}}{{#allParams}} 130 | * @param {{paramName}} {{description}}{{/allParams}} 131 | */ 132 | {{nickname}}({{#hasParams}}params: { {{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}; {{/allParams}} }{{/hasParams}}): (fetch?: FetchAPI, basePath?: string) => Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}any{{/returnType}}> { 133 | const fetchArgs = {{classname}}FetchParamCreactor.{{nickname}}({{#hasParams}}params{{/hasParams}}); 134 | return (fetch: FetchAPI = FetchImpl, basePath: string = BasePath) => { 135 | return fetch(basePath + fetchArgs.url, fetchArgs.options).then((response) => { 136 | if (response.status >= 200 && response.status < 300) { 137 | return response{{#returnType}}.json(){{/returnType}}; 138 | } else { 139 | throw response; 140 | } 141 | }); 142 | }; 143 | }, 144 | {{/operation}} 145 | }; 146 | 147 | /** 148 | * {{classname}} - object-oriented interface{{#description}} 149 | * {{&description}}{{/description}} 150 | */ 151 | export class {{classname}} extends BaseAPI { 152 | {{#operation}} 153 | /** {{#summary}} 154 | * {{summary}}{{/summary}}{{#notes}} 155 | * {{notes}}{{/notes}}{{#allParams}} 156 | * @param {{paramName}} {{description}}{{/allParams}} 157 | */ 158 | {{nickname}}({{#hasParams}}params: { {{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}};{{/allParams}} }{{/hasParams}}) { 159 | return {{classname}}Fp.{{nickname}}({{#hasParams}}params{{/hasParams}})(this.fetch, this.basePath); 160 | } 161 | {{/operation}} 162 | }; 163 | 164 | /** 165 | * {{classname}} - factory interface{{#description}} 166 | * {{&description}}{{/description}} 167 | */ 168 | export const {{classname}}Factory = function (fetch: FetchAPI = FetchImpl, basePath: string = BasePath) { 169 | return { 170 | {{#operation}} 171 | /** {{#summary}} 172 | * {{summary}}{{/summary}}{{#notes}} 173 | * {{notes}}{{/notes}}{{#allParams}} 174 | * @param {{paramName}} {{description}}{{/allParams}} 175 | */ 176 | {{nickname}}({{#hasParams}}params: { {{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}};{{/allParams}} }{{/hasParams}}) { 177 | return {{classname}}Fp.{{nickname}}({{#hasParams}}params{{/hasParams}})(fetch, basePath); 178 | }, 179 | {{/operation}} 180 | } 181 | }; 182 | 183 | {{/operations}} 184 | {{/apis}} 185 | {{/apiInfo}} 186 | --------------------------------------------------------------------------------