diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultGenerator.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultGenerator.java index 738172cb80d..b02f3fe58f5 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultGenerator.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultGenerator.java @@ -705,6 +705,28 @@ private Map buildSupportFileBundle(List allOperations, L bundle.put("models", allModels); bundle.put("apiFolder", config.apiPackage().replace('.', File.separatorChar)); bundle.put("modelPackage", config.modelPackage()); + if (allOperations != null) { + // For each API group... + for (HashMap apiGroup : (List>) (Object) allOperations) { + HashMap apiGroupOperationsContainer = + (HashMap) apiGroup.get("operations"); + List apiGroupOperations = + (List) (Object) apiGroupOperationsContainer.get("operation"); + // For each operation within the API group... + for (CodegenOperation apiGroupOperation : apiGroupOperations) { + // If this operation has a file response, indicate that the API hasFileOperations... + if (apiGroupOperation.isResponseFile) { + bundle.put("hasFileOperations", true); + break; + } + } + // If we've already found at least one API Group containing an operation that must return + // file responses, we're done... + if (bundle.containsKey("hasFileOperations")) { + break; + } + } + } List authMethods = config.fromSecurity(swagger.getSecurityDefinitions()); if (authMethods != null && !authMethods.isEmpty()) { bundle.put("authMethods", authMethods); diff --git a/modules/swagger-codegen/src/main/resources/typescript-fetch/api.mustache b/modules/swagger-codegen/src/main/resources/typescript-fetch/api.mustache index d149d5ff2ee..ea1fce9f1ee 100644 --- a/modules/swagger-codegen/src/main/resources/typescript-fetch/api.mustache +++ b/modules/swagger-codegen/src/main/resources/typescript-fetch/api.mustache @@ -8,6 +8,41 @@ import { Configuration } from "./configuration"; const BASE_PATH = "{{{basePath}}}".replace(/\/+$/, ""); +{{#hasFileOperations}} +const responseToFile = (fileResponse: Response): Promise => { + let filename = "download"; // Default filename of 'download' + + const contentTypeHeader = fileResponse.headers.get("Content-Type"); + const lastModifiedHeader = fileResponse.headers.get("Last-Modified"); + const contentDispositionHeader = fileResponse.headers.get("Content-Disposition"); + + const type = contentTypeHeader || ""; + const lastModified = lastModifiedHeader ? Date.parse(lastModifiedHeader) : Date.now(); + if (contentDispositionHeader) { + const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; + const filenameMatches = filenameRegex.exec(contentDispositionHeader); + if (filenameMatches && filenameMatches[1]) { + filename = filenameMatches[1].replace(/['"]/g, ""); + } + } + + return fileResponse.blob().then(blob => { + // If the File constructor is available and this is not Edge (which has an unimplemented constructor), use it... + // (https://developer.mozilla.org/en-US/docs/Web/API/File/File) + if (typeof File === "function" && !/Edge/.test(navigator.userAgent)) { + return new File([blob], filename, { type, lastModified }); + } + // Otherwise, extend the Blob with the additional properties and return it as a File... + else { + const file = blob as any; + file.name = filename; + file.lastModified = lastModified; + return file as File; + } + }); +}; + +{{/hasFileOperations}} /** * * @export @@ -260,12 +295,17 @@ export const {{classname}}Fp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: any): (fetch?: FetchAPI, basePath?: string) => Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Response{{/returnType}}> { + {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: any): (fetch?: FetchAPI, basePath?: string) => Promise<{{#returnType}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{#isResponseFile}}File{{/isResponseFile}}{{/returnType}}{{^returnType}}Response{{/returnType}}> { const localVarFetchArgs = {{classname}}FetchParamCreator(configuration).{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => { return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => { if (response.status >= 200 && response.status < 300) { + {{^isResponseFile}} return response{{#returnType}}.json(){{/returnType}}; + {{/isResponseFile}} + {{#isResponseFile}} + return responseToFile(response); + {{/isResponseFile}} } else { throw response; } diff --git a/samples/client/petstore/typescript-fetch/builds/default/api.ts b/samples/client/petstore/typescript-fetch/builds/default/api.ts index 7b89d8a61f5..9d538e2147b 100644 --- a/samples/client/petstore/typescript-fetch/builds/default/api.ts +++ b/samples/client/petstore/typescript-fetch/builds/default/api.ts @@ -78,6 +78,26 @@ export class RequiredError extends Error { } } +/** + * some description + * @export + * @interface Amount + */ +export interface Amount { + /** + * some description + * @type {number} + * @memberof Amount + */ + value: number; + /** + * + * @type {Currency} + * @memberof Amount + */ + currency: Currency; +} + /** * Describes the result of uploading an image resource * @export @@ -124,6 +144,14 @@ export interface Category { name?: string; } +/** + * some description + * @export + * @interface Currency + */ +export interface Currency { +} + /** * An order for a pets from the pet store * @export