fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #1305 from hey-api/fix/experimental-parser-content-file-no-schema

fix: handle file-like content media type without explicit schema

authored by

Lubos and committed by
GitHub
59b84a04 c3d66553

+291 -16
+5
.changeset/many-tomatoes-happen.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix: handle file-like content media type without explicit schema
+70
packages/openapi-ts/src/ir/__tests__/mediaType.test.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { isMediaTypeFileLike } from '../mediaType'; 4 + 5 + describe('isMediaTypeFileLike', () => { 6 + const scenarios: Array<{ 7 + fileLike: ReturnType<typeof isMediaTypeFileLike>; 8 + mediaType: Parameters<typeof isMediaTypeFileLike>[0]['mediaType']; 9 + }> = [ 10 + { 11 + fileLike: false, 12 + mediaType: 'application/json', 13 + }, 14 + { 15 + fileLike: true, 16 + mediaType: 'application/json+download', 17 + }, 18 + { 19 + fileLike: false, 20 + mediaType: 'application/json; charset=ascii', 21 + }, 22 + { 23 + fileLike: true, 24 + mediaType: 'application/octet-stream', 25 + }, 26 + { 27 + fileLike: true, 28 + mediaType: 'application/pdf', 29 + }, 30 + { 31 + fileLike: true, 32 + mediaType: 'application/xml; charset=utf-8', 33 + }, 34 + { 35 + fileLike: true, 36 + mediaType: 'application/zip', 37 + }, 38 + { 39 + fileLike: false, 40 + mediaType: 'image/jpeg', 41 + }, 42 + { 43 + fileLike: false, 44 + mediaType: 'image/jpeg; charset=utf-8', 45 + }, 46 + { 47 + fileLike: false, 48 + mediaType: 'text/html; charset=utf-8', 49 + }, 50 + { 51 + fileLike: true, 52 + mediaType: 'text/javascript; charset=ISO-8859-1', 53 + }, 54 + { 55 + fileLike: true, 56 + mediaType: 'text/plain; charset=utf-8', 57 + }, 58 + { 59 + fileLike: true, 60 + mediaType: 'video/mp4', 61 + }, 62 + ]; 63 + 64 + it.each(scenarios)( 65 + 'detects $mediaType as file-like? $fileLike', 66 + async ({ mediaType, fileLike }) => { 67 + expect(isMediaTypeFileLike({ mediaType })).toEqual(fileLike); 68 + }, 69 + ); 70 + });
+1
packages/openapi-ts/src/ir/ir.d.ts
··· 106 106 107 107 export interface IRResponseObject { 108 108 // TODO: parser - handle headers, links, and possibly other media types? 109 + mediaType?: string; 109 110 schema: IRSchemaObject; 110 111 } 111 112
+11
packages/openapi-ts/src/ir/mediaType.ts
··· 1 + const fileLikeRegExp = 2 + /^(application\/(pdf|rtf|msword|vnd\.(ms-|openxmlformats-officedocument\.)|zip|x-(7z|tar|rar|zip|iso)|octet-stream|gzip|x-msdownload|json\+download|xml|x-yaml|x-7z-compressed|x-tar)|text\/(plain|yaml|css|javascript)|audio\/(mpeg|wav)|video\/(mp4|x-matroska)|image\/(vnd\.adobe\.photoshop|svg\+xml))(; ?charset=[^;]+)?$/i; 1 3 const jsonMimeRegExp = /^application\/(.*\+)?json(;.*)?$/i; 2 4 const multipartFormDataMimeRegExp = /^multipart\/form-data(;.*)?$/i; 3 5 const xWwwFormUrlEncodedMimeRegExp = 4 6 /^application\/x-www-form-urlencoded(;.*)?$/i; 5 7 6 8 export type IRMediaType = 'form-data' | 'json' | 'url-search-params'; 9 + 10 + export const isMediaTypeFileLike = ({ 11 + mediaType, 12 + }: { 13 + mediaType: string; 14 + }): boolean => { 15 + fileLikeRegExp.lastIndex = 0; 16 + return fileLikeRegExp.test(mediaType); 17 + }; 7 18 8 19 export const mediaTypeToIrMediaType = ({ 9 20 mediaType,
+41 -1
packages/openapi-ts/src/openApi/3.0.x/parser/mediaType.ts
··· 1 1 import type { IRMediaType } from '../../../ir/mediaType'; 2 - import { mediaTypeToIrMediaType } from '../../../ir/mediaType'; 2 + import { 3 + isMediaTypeFileLike, 4 + mediaTypeToIrMediaType, 5 + } from '../../../ir/mediaType'; 3 6 import type { 4 7 MediaTypeObject, 5 8 ReferenceObject, ··· 11 14 schema: SchemaObject | ReferenceObject | undefined; 12 15 type: IRMediaType | undefined; 13 16 } 17 + 18 + export const contentToSchema = ({ 19 + content, 20 + }: { 21 + content: Content; 22 + }): SchemaObject | undefined => { 23 + const { mediaType, schema } = content; 24 + 25 + if (schema && '$ref' in schema) { 26 + return { 27 + allOf: [{ ...schema }], 28 + }; 29 + } 30 + 31 + if (!schema) { 32 + if (isMediaTypeFileLike({ mediaType })) { 33 + return { 34 + format: 'binary', 35 + type: 'string', 36 + }; 37 + } 38 + return; 39 + } 40 + 41 + if ( 42 + schema.type === 'string' && 43 + !schema.format && 44 + isMediaTypeFileLike({ mediaType }) 45 + ) { 46 + return { 47 + ...schema, 48 + format: 'binary', 49 + }; 50 + } 51 + 52 + return schema; 53 + }; 14 54 15 55 export const mediaTypeObject = ({ 16 56 content,
+6 -11
packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts
··· 8 8 ResponseObject, 9 9 SchemaObject, 10 10 } from '../types/spec'; 11 - import { mediaTypeObject } from './mediaType'; 11 + import { contentToSchema, mediaTypeObject } from './mediaType'; 12 12 import { paginationField } from './pagination'; 13 13 import { schemaToIrSchema } from './schema'; 14 14 ··· 134 134 135 135 if (content) { 136 136 irOperation.responses[name] = { 137 + mediaType: content.mediaType, 137 138 schema: schemaToIrSchema({ 138 139 context, 139 - schema: 140 - content.schema && '$ref' in content.schema 141 - ? { 142 - allOf: [{ ...content.schema }], 143 - description: responseObject.description, 144 - } 145 - : { 146 - description: responseObject.description, 147 - ...content.schema, 148 - }, 140 + schema: { 141 + description: responseObject.description, 142 + ...contentToSchema({ content }), 143 + }, 149 144 }), 150 145 }; 151 146 } else {
+35 -1
packages/openapi-ts/src/openApi/3.1.x/parser/mediaType.ts
··· 1 1 import type { IRMediaType } from '../../../ir/mediaType'; 2 - import { mediaTypeToIrMediaType } from '../../../ir/mediaType'; 2 + import { 3 + isMediaTypeFileLike, 4 + mediaTypeToIrMediaType, 5 + } from '../../../ir/mediaType'; 3 6 import type { MediaTypeObject, SchemaObject } from '../types/spec'; 4 7 5 8 interface Content { ··· 7 10 schema: SchemaObject | undefined; 8 11 type: IRMediaType | undefined; 9 12 } 13 + 14 + export const contentToSchema = ({ 15 + content, 16 + }: { 17 + content: Content; 18 + }): SchemaObject | undefined => { 19 + const { mediaType, schema } = content; 20 + 21 + if (!schema) { 22 + if (isMediaTypeFileLike({ mediaType })) { 23 + return { 24 + format: 'binary', 25 + type: 'string', 26 + }; 27 + } 28 + return; 29 + } 30 + 31 + if ( 32 + schema.type === 'string' && 33 + !schema.format && 34 + isMediaTypeFileLike({ mediaType }) 35 + ) { 36 + return { 37 + ...schema, 38 + format: 'binary', 39 + }; 40 + } 41 + 42 + return schema; 43 + }; 10 44 11 45 export const mediaTypeObject = ({ 12 46 content,
+3 -2
packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts
··· 8 8 ResponseObject, 9 9 SchemaObject, 10 10 } from '../types/spec'; 11 - import { mediaTypeObject } from './mediaType'; 11 + import { contentToSchema, mediaTypeObject } from './mediaType'; 12 12 import { paginationField } from './pagination'; 13 13 import { schemaToIrSchema } from './schema'; 14 14 ··· 128 128 129 129 if (content) { 130 130 irOperation.responses[name] = { 131 + mediaType: content.mediaType, 131 132 schema: schemaToIrSchema({ 132 133 context, 133 134 schema: { 134 135 description: responseObject.description, 135 - ...content.schema, 136 + ...contentToSchema({ content }), 136 137 }, 137 138 }), 138 139 };
+7
packages/openapi-ts/test/3.0.x.spec.ts
··· 58 58 }, 59 59 { 60 60 config: createConfig({ 61 + input: 'content-binary.json', 62 + output: 'content-binary', 63 + }), 64 + description: 'handles binary content', 65 + }, 66 + { 67 + config: createConfig({ 61 68 input: 'discriminator-all-of.yaml', 62 69 output: 'discriminator-all-of', 63 70 }),
+7
packages/openapi-ts/test/3.1.x.spec.ts
··· 58 58 }, 59 59 { 60 60 config: createConfig({ 61 + input: 'content-binary.json', 62 + output: 'content-binary', 63 + }), 64 + description: 'handles binary content', 65 + }, 66 + { 67 + config: createConfig({ 61 68 input: 'discriminator-all-of.yaml', 62 69 output: 'discriminator-all-of', 63 70 }),
+2
packages/openapi-ts/test/__snapshots__/3.0.x/content-binary/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen';
+13
packages/openapi-ts/test/__snapshots__/3.0.x/content-binary/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type GetFooResponses = { 4 + 200: string; 5 + }; 6 + 7 + export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 8 + 9 + export type GetBarResponses = { 10 + 200: Blob | File; 11 + }; 12 + 13 + export type GetBarResponse = GetBarResponses[keyof GetBarResponses];
+2
packages/openapi-ts/test/__snapshots__/3.1.x/content-binary/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen';
+13
packages/openapi-ts/test/__snapshots__/3.1.x/content-binary/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type GetFooResponses = { 4 + 200: string; 5 + }; 6 + 7 + export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 8 + 9 + export type GetBarResponses = { 10 + 200: Blob | File; 11 + }; 12 + 13 + export type GetBarResponse = GetBarResponses[keyof GetBarResponses];
+1 -1
packages/openapi-ts/test/sample.cjs
··· 13 13 input: { 14 14 // include: 15 15 // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', 16 - path: './test/spec/3.0.x/full.json', 16 + path: './test/spec/3.0.x/content-binary.json', 17 17 // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 18 18 }, 19 19 // name: 'foo',
+37
packages/openapi-ts/test/spec/3.0.x/content-binary.json
··· 1 + { 2 + "openapi": "3.0.0", 3 + "info": { 4 + "title": "OpenAPI 3.0.0 content binary example", 5 + "version": "1" 6 + }, 7 + "paths": { 8 + "/foo": { 9 + "get": { 10 + "responses": { 11 + "200": { 12 + "content": { 13 + "image/png": { 14 + "schema": { 15 + "type": "string", 16 + "contentMediaType": "image/png", 17 + "contentEncoding": "base64" 18 + } 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }, 25 + "/bar": { 26 + "get": { 27 + "responses": { 28 + "200": { 29 + "content": { 30 + "application/zip": {} 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+37
packages/openapi-ts/test/spec/3.1.x/content-binary.json
··· 1 + { 2 + "openapi": "3.1.0", 3 + "info": { 4 + "title": "OpenAPI 3.1.0 content binary example", 5 + "version": "1" 6 + }, 7 + "paths": { 8 + "/foo": { 9 + "get": { 10 + "responses": { 11 + "200": { 12 + "content": { 13 + "image/png": { 14 + "schema": { 15 + "type": "string", 16 + "contentMediaType": "image/png", 17 + "contentEncoding": "base64" 18 + } 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }, 25 + "/bar": { 26 + "get": { 27 + "responses": { 28 + "200": { 29 + "content": { 30 + "application/zip": {} 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }