Skip to content

Commit

Permalink
fix: handle multiple form-data parameters in Swagger 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Sep 30, 2024
1 parent a5c57e0 commit 7677924
Show file tree
Hide file tree
Showing 26 changed files with 267 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-windows-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: handle multiple form-data parameters in Swagger 2.0
2 changes: 0 additions & 2 deletions examples/openapi-ts-tanstack-react-query/openapi-ts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ export default defineConfig({
client: '@hey-api/client-fetch',
input:
'https://github.com/raw/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml',
// 'https://github.com/raw/Redocly/museum-openapi-example/main/openapi.yaml',
// '../../packages/openapi-ts/test/spec/v3.json',
output: {
format: 'prettier',
lint: 'eslint',
Expand Down
6 changes: 5 additions & 1 deletion packages/openapi-ts/src/generate/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,11 @@ const toRequestOptions = (
(parameter) => parameter.in === 'body' || parameter.in === 'formData',
);
const contents = bodyParameters
.map((parameter) => parameter.mediaType)
.map(
(parameter) =>
parameter.mediaType ||
(parameter.in === 'formData' ? 'multipart/form-data' : undefined),
)
.filter(Boolean)
.filter(unique);
if (contents.length === 1) {
Expand Down
43 changes: 32 additions & 11 deletions packages/openapi-ts/src/generate/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,23 +335,44 @@ const processServiceTypes = ({
}

if (operation.parameters.length > 0) {
let bodyParameter = operation.parameters.find(
(parameter) => parameter.in === 'body',
);
if (!bodyParameter) {
bodyParameter = operation.parameters.find(
(parameter) => parameter.in === 'formData',
);
}
const bodyParameters: OperationParameter = {
let bodyParameters: OperationParameter = {
mediaType: null,
...emptyModel,
...bodyParameter,
in: 'body',
isRequired: bodyParameter ? bodyParameter.isRequired : false,
name: 'body',
prop: 'body',
};
let bodyParameter = operation.parameters.filter(
(parameter) => parameter.in === 'body',
);
if (!bodyParameter.length) {
bodyParameter = operation.parameters.filter(
(parameter) => parameter.in === 'formData',
);
}

if (bodyParameter.length === 1) {
bodyParameters = {
...emptyModel,
...bodyParameter[0],
in: 'body',
isRequired: bodyParameter[0].isRequired,
name: 'body',
prop: 'body',
};
// assume we have multiple formData parameters from Swagger 2.0
} else if (bodyParameter.length > 1) {
bodyParameters = {
...emptyModel,
in: 'body',
isRequired: bodyParameter.some((parameter) => parameter.isRequired),
mediaType: 'multipart/form-data',
name: 'body',
prop: 'body',
properties: bodyParameter,
};
}

Check warning on line 374 in packages/openapi-ts/src/generate/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/types.ts#L365-L374

Added lines #L365 - L374 were not covered by tests

const headerParameters: OperationParameter = {
...emptyModel,
in: 'header',
Expand Down
3 changes: 2 additions & 1 deletion packages/openapi-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,5 +426,6 @@ export default {
defineConfig,
};

export type { OpenApiV3_1 } from './openApi/3.1/types/spec';
export type { OpenApiV3_0_3 } from './openApi/3.0.3';
export type { OpenApiV3_1 } from './openApi/3.1';
export type { UserConfig } from './types/config';
2 changes: 2 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { parseV3_0_3 } from './parser';
export type { OpenApiV3_0_3 } from './types/spec';
6 changes: 6 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.3/parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { OpenApiV3_0_3 } from '../types/spec';

export const parseV3_0_3 = (spec: OpenApiV3_0_3) => {
// TODO
console.log(spec);
};

Check warning on line 6 in packages/openapi-ts/src/openApi/3.0.3/parser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/3.0.3/parser/index.ts#L4-L6

Added lines #L4 - L6 were not covered by tests
7 changes: 7 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.3/types/spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface OpenApiV3_0_3 {
/**
* **REQUIRED**. This string MUST be the {@link https://semver.org/spec/v2.0.0.html semantic version number} of the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#versions OpenAPI Specification version} that the OpenAPI document uses. The `openapi` field SHOULD be used by tooling specifications and clients to interpret the OpenAPI document. This is _not_ related to the API {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#infoVersion `info.version`} string.
*/
openapi: '3.0.3';
// TODO
}
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/openApi/3.1/types/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface OpenApiV3_1 {
*/
jsonSchemaDialect?: string;
/**
* **REQUIRED**. This string MUST be the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#versions version number} of the OpenAPI Specification that the OpenAPI document uses. The `openapi` field SHOULD be used by tooling to interpret the OpenAPI document. This is _not_ related to the API {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#infoVersion info.version} string.
* **REQUIRED**. This string MUST be the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#versions version number} of the OpenAPI Specification that the OpenAPI document uses. The `openapi` field SHOULD be used by tooling to interpret the OpenAPI document. This is _not_ related to the API {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#infoVersion `info.version`} string.
*/
openapi: '3.1.0';
/**
Expand Down
5 changes: 4 additions & 1 deletion packages/openapi-ts/src/openApi/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type OpenApiV3_0_3, parseV3_0_3 } from './3.0.3';
import { type OpenApiV3_1, parseV3_1 } from './3.1';
import type { Client } from './common/interfaces/client';
import type { Config } from './common/interfaces/config';
Expand Down Expand Up @@ -56,9 +57,11 @@ export function parse({
}

export const parseExperimental = ({ spec }: { spec: unknown }) => {
const s = spec as OpenApiV3_1;
const s = spec as OpenApiV3_0_3 | OpenApiV3_1;

switch (s.openapi) {
case '3.0.3':
return parseV3_0_3(s);

Check warning on line 64 in packages/openapi-ts/src/openApi/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/index.ts#L64

Added line #L64 was not covered by tests
case '3.1.0':
return parseV3_1(s);
default:
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/plugins/@hey-api/schemas/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface PluginConfig extends PluginDefinition {
output?: string;
}

export interface UserConfig extends Pick<PluginConfig, 'output'> {}
export interface UserConfig extends Pick<PluginConfig, 'name'> {}
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/plugins/@hey-api/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface PluginConfig extends PluginDefinition {
output?: string;
}

export interface UserConfig extends Pick<PluginConfig, 'output'> {}
export interface UserConfig extends Pick<PluginConfig, 'name'> {}
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/plugins/@hey-api/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface PluginConfig extends PluginDefinition {
output?: string;
}

export interface UserConfig extends Pick<PluginConfig, 'output'> {}
export interface UserConfig extends Pick<PluginConfig, 'name'> {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ export interface PluginConfig extends PluginDefinition {
export interface UserConfig
extends Pick<
PluginConfig,
'infiniteQueryOptions' | 'mutationOptions' | 'output' | 'queryOptions'
'infiniteQueryOptions' | 'mutationOptions' | 'name' | 'queryOptions'
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ export interface PluginConfig extends PluginDefinition {
export interface UserConfig
extends Pick<
PluginConfig,
'infiniteQueryOptions' | 'mutationOptions' | 'output' | 'queryOptions'
'infiniteQueryOptions' | 'mutationOptions' | 'name' | 'queryOptions'
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ export interface PluginConfig extends PluginDefinition {
export interface UserConfig
extends Pick<
PluginConfig,
'infiniteQueryOptions' | 'mutationOptions' | 'output' | 'queryOptions'
'infiniteQueryOptions' | 'mutationOptions' | 'name' | 'queryOptions'
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ export interface PluginConfig extends PluginDefinition {
export interface UserConfig
extends Pick<
PluginConfig,
'infiniteQueryOptions' | 'mutationOptions' | 'output' | 'queryOptions'
'infiniteQueryOptions' | 'mutationOptions' | 'name' | 'queryOptions'
> {}
37 changes: 25 additions & 12 deletions packages/openapi-ts/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,56 @@
import {
defaultConfig as heyApiSchemasDefaultConfig,
type PluginConfig as HeyApiSchemas,
type PluginConfig as PluginHeyApiSchemas,
} from './@hey-api/schemas';
import {
defaultConfig as heyApiServicesDefaultConfig,
type PluginConfig as HeyApiServices,
type PluginConfig as PluginHeyApiServices,
} from './@hey-api/services';
import {
defaultConfig as heyApiTypesDefaultConfig,
type PluginConfig as HeyApiTypes,
type PluginConfig as PluginHeyApiTypes,
} from './@hey-api/types';
import {
defaultConfig as tanStackReactQueryDefaultConfig,
type PluginConfig as TanStackReactQuery,
type PluginConfig as PluginTanStackReactQuery,
type UserConfig as TanStackReactQuery,
} from './@tanstack/react-query';
import {
defaultConfig as tanStackSolidQueryDefaultConfig,
type PluginConfig as TanStackSolidQuery,
type PluginConfig as PluginTanStackSolidQuery,
type UserConfig as TanStackSolidQuery,
} from './@tanstack/solid-query';
import {
defaultConfig as tanStackSvelteQueryDefaultConfig,
type PluginConfig as TanStackSvelteQuery,
type PluginConfig as PluginTanStackSvelteQuery,
type UserConfig as TanStackSvelteQuery,
} from './@tanstack/svelte-query';
import {
defaultConfig as tanStackVueQueryDefaultConfig,
type PluginConfig as TanStackVueQuery,
type PluginConfig as PluginTanStackVueQuery,
type UserConfig as TanStackVueQuery,
} from './@tanstack/vue-query';
import type { DefaultPluginConfigsMap } from './types';

export type Plugins =
| HeyApiSchemas
| HeyApiServices
| HeyApiTypes
/**
* User-facing plugin types.
*/
export type UserPlugins =
| TanStackReactQuery
| TanStackSolidQuery
| TanStackSvelteQuery
| TanStackVueQuery;

export const defaultPluginConfigs: DefaultPluginConfigsMap<Plugins> = {
export type ClientPlugins =
| PluginHeyApiSchemas
| PluginHeyApiServices
| PluginHeyApiTypes
| PluginTanStackReactQuery
| PluginTanStackSolidQuery
| PluginTanStackSvelteQuery
| PluginTanStackVueQuery;

export const defaultPluginConfigs: DefaultPluginConfigsMap<ClientPlugins> = {
'@hey-api/schemas': heyApiSchemasDefaultConfig,
'@hey-api/services': heyApiServicesDefaultConfig,
'@hey-api/types': heyApiTypesDefaultConfig,
Expand Down
6 changes: 3 additions & 3 deletions packages/openapi-ts/src/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { OpenApiV2Schema, OpenApiV3Schema } from '../openApi';
import type { Plugins } from '../plugins/';
import type { ClientPlugins, UserPlugins } from '../plugins/';
import type { Operation } from '../types/client';
import type { ExtractArrayOfObjects } from './utils';

Expand Down Expand Up @@ -101,7 +101,7 @@ export interface ClientConfig {
/**
* Plugins are used to generate additional output files from provided input.
*/
plugins?: ReadonlyArray<Plugins['name'] | Plugins>;
plugins?: ReadonlyArray<UserPlugins['name'] | UserPlugins>;
/**
* Path to custom request file
* @deprecated
Expand Down Expand Up @@ -262,7 +262,7 @@ export type Config = Omit<
client: Extract<Required<ClientConfig>['client'], object>;
output: Extract<Required<ClientConfig>['output'], object>;
plugins: ExtractArrayOfObjects<
Required<ClientConfig>['plugins'],
ReadonlyArray<ClientPlugins>,
{ name: string }
>;
schemas: Extract<Required<ClientConfig>['schemas'], object>;
Expand Down
64 changes: 64 additions & 0 deletions packages/openapi-ts/test/2.0.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';

import { createClient } from '../';
import type { UserConfig } from '../src/types/config';
import { getFilePaths } from './utils';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const VERSION = '2.0';

describe(`OpenAPI ${VERSION}`, () => {
const createConfig = (userConfig: UserConfig): UserConfig => ({
client: '@hey-api/client-fetch',
schemas: false,
...userConfig,
input: path.join(
__dirname,
'spec',
VERSION,
typeof userConfig.input === 'string' ? userConfig.input : '',
),
output: path.join(
__dirname,
'generated',
VERSION,
typeof userConfig.output === 'string' ? userConfig.output : '',
),
});

const scenarios = [
{
config: createConfig({
input: 'form-data.json',
output: 'form-data',
}),
description: 'handles form data',
},
];

it.each(scenarios)('$description', async ({ config }) => {
// @ts-ignore
await createClient(config);

const outputPath = typeof config.output === 'string' ? config.output : '';
const filePaths = getFilePaths(outputPath);

filePaths.forEach((filePath) => {
const fileContent = readFileSync(filePath, 'utf-8');
expect(fileContent).toMatchFileSnapshot(
path.join(
__dirname,
'__snapshots__',
VERSION,
filePath.slice(outputPath.length + 1),
),
);
});
});
});
10 changes: 6 additions & 4 deletions packages/openapi-ts/test/3.1.0.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ import { getFilePaths } from './utils';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

describe('OpenAPI 3.1.0', () => {
const VERSION = '3.1.0';

describe(`OpenAPI ${VERSION}`, () => {
const createConfig = (userConfig: UserConfig): UserConfig => ({
client: '@hey-api/client-fetch',
schemas: false,
...userConfig,
input: path.join(
__dirname,
'spec',
'3.1.0',
VERSION,
typeof userConfig.input === 'string' ? userConfig.input : '',
),
output: path.join(
__dirname,
'generated',
'3.1.0',
VERSION,
typeof userConfig.output === 'string' ? userConfig.output : '',
),
});
Expand Down Expand Up @@ -56,7 +58,7 @@ describe('OpenAPI 3.1.0', () => {
path.join(
__dirname,
'__snapshots__',
'3.1.0',
VERSION,
filePath.slice(outputPath.length + 1),
),
);
Expand Down
3 changes: 3 additions & 0 deletions packages/openapi-ts/test/__snapshots__/2.0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './services.gen';
export * from './types.gen';
Loading

0 comments on commit 7677924

Please sign in to comment.