From 458ebdb427e2b8b059df69dbbc223ce8e575a1d5 Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:06:19 +0100 Subject: [PATCH 1/3] fix(core): only generate one operation if it has multiple tags --- .../pages/reference/configuration/output.md | 30 ++++ packages/core/src/types.ts | 2 + packages/core/src/writers/target-tags.ts | 134 +++++++++--------- tests/configs/default.config.ts | 15 ++ tests/specifications/multiple-tags.yaml | 84 +++++++++++ 5 files changed, 198 insertions(+), 67 deletions(-) create mode 100644 tests/specifications/multiple-tags.yaml diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index e30b8d8c1..6cddb6e31 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -1625,6 +1625,36 @@ Type: `Object`. Exactly the same as the `override.operations` but this time you can do it by tags +#### multiTagResolver + +Type: `Function`. + +```ts +// type signature +(tags: string[]) => string; +``` + +When using mode `tags` or `tags-split` and an operation have multiple tags, only one of them will be used when generating. +This setting allows you to decide which tag to use. Default is the first tag. + +Example: + +```js +module.exports = { + petstore: { + output: { + override: { + multiTagResolver: (tags) => { + const indexOfFavoriteTag = tags.include('myFavoriteTag'); + if (indexOfFavoriteTag >= 0) return tags[indexOfFavoriteTag]; + return tags[0]; + }, + }, + }, + }, +}; +``` + #### operationName Type: `Function`. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4792e49bb..69ee226a0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -77,6 +77,7 @@ export type NormalizedOverrideOutput = { mutator?: NormalizedMutator; operations: { [key: string]: NormalizedOperationOptions }; tags: { [key: string]: NormalizedOperationOptions }; + multiTagResolver?: (tags: string[]) => string; mock?: OverrideMockOptions; contentType?: OverrideOutputContentType; header: false | ((info: InfoObject) => string[] | string); @@ -360,6 +361,7 @@ export type OverrideOutput = { mutator?: Mutator; operations?: { [key: string]: OperationOptions }; tags?: { [key: string]: OperationOptions }; + multiTagResolver?: (tags: string[]) => string; mock?: OverrideMockOptions; contentType?: OverrideOutputContentType; header?: boolean | ((info: InfoObject) => string[] | string); diff --git a/packages/core/src/writers/target-tags.ts b/packages/core/src/writers/target-tags.ts index 62f10afff..ee650af8e 100644 --- a/packages/core/src/writers/target-tags.ts +++ b/packages/core/src/writers/target-tags.ts @@ -16,79 +16,79 @@ const addDefaultTagIfEmpty = (operation: GeneratorOperation) => ({ const generateTargetTags = ( currentAcc: { [key: string]: GeneratorTargetFull }, operation: GeneratorOperation, + options: NormalizedOutputOptions, ): { [key: string]: GeneratorTargetFull; } => { - return operation.tags.map(kebab).reduce((acc, tag) => { - const currentOperation = acc[tag]; - - if (!currentOperation) { - acc[tag] = { - imports: operation.imports, - importsMock: operation.importsMock, - mutators: operation.mutator ? [operation.mutator] : [], - clientMutators: operation.clientMutators ?? [], - formData: operation.formData ? [operation.formData] : [], - formUrlEncoded: operation.formUrlEncoded - ? [operation.formUrlEncoded] - : [], - paramsSerializer: operation.paramsSerializer - ? [operation.paramsSerializer] - : [], - implementation: operation.implementation, - implementationMock: { - function: operation.implementationMock.function, - handler: operation.implementationMock.handler, - handlerName: ' ' + operation.implementationMock.handlerName + '()', - }, - }; - - return acc; - } - - acc[tag] = { - implementation: - currentOperation.implementation + operation.implementation, - imports: [...currentOperation.imports, ...operation.imports], - importsMock: [...currentOperation.importsMock, ...operation.importsMock], - implementationMock: { - function: - currentOperation.implementationMock.function + - operation.implementationMock.function, - handler: - currentOperation.implementationMock.handler + - operation.implementationMock.handler, - handlerName: - currentOperation.implementationMock.handlerName + - ',\n ' + - operation.implementationMock.handlerName + - '()', - }, - mutators: operation.mutator - ? [...(currentOperation.mutators ?? []), operation.mutator] - : currentOperation.mutators, - clientMutators: operation.clientMutators - ? [ - ...(currentOperation.clientMutators ?? []), - ...operation.clientMutators, - ] - : currentOperation.clientMutators, - formData: operation.formData - ? [...(currentOperation.formData ?? []), operation.formData] - : currentOperation.formData, + const tag = options.override.multiTagResolver + ? options.override.multiTagResolver(operation.tags) + : operation.tags[0]; + const currentOperation = currentAcc[tag]; + + if (!currentOperation) { + currentAcc[tag] = { + imports: operation.imports, + importsMock: operation.importsMock, + mutators: operation.mutator ? [operation.mutator] : [], + clientMutators: operation.clientMutators ?? [], + formData: operation.formData ? [operation.formData] : [], formUrlEncoded: operation.formUrlEncoded - ? [...(currentOperation.formUrlEncoded ?? []), operation.formUrlEncoded] - : currentOperation.formUrlEncoded, + ? [operation.formUrlEncoded] + : [], paramsSerializer: operation.paramsSerializer - ? [ - ...(currentOperation.paramsSerializer ?? []), - operation.paramsSerializer, - ] - : currentOperation.paramsSerializer, + ? [operation.paramsSerializer] + : [], + implementation: operation.implementation, + implementationMock: { + function: operation.implementationMock.function, + handler: operation.implementationMock.handler, + handlerName: ' ' + operation.implementationMock.handlerName + '()', + }, }; - return acc; - }, currentAcc); + return currentAcc; + } + + currentAcc[tag] = { + implementation: currentOperation.implementation + operation.implementation, + imports: [...currentOperation.imports, ...operation.imports], + importsMock: [...currentOperation.importsMock, ...operation.importsMock], + implementationMock: { + function: + currentOperation.implementationMock.function + + operation.implementationMock.function, + handler: + currentOperation.implementationMock.handler + + operation.implementationMock.handler, + handlerName: + currentOperation.implementationMock.handlerName + + ',\n ' + + operation.implementationMock.handlerName + + '()', + }, + mutators: operation.mutator + ? [...(currentOperation.mutators ?? []), operation.mutator] + : currentOperation.mutators, + clientMutators: operation.clientMutators + ? [ + ...(currentOperation.clientMutators ?? []), + ...operation.clientMutators, + ] + : currentOperation.clientMutators, + formData: operation.formData + ? [...(currentOperation.formData ?? []), operation.formData] + : currentOperation.formData, + formUrlEncoded: operation.formUrlEncoded + ? [...(currentOperation.formUrlEncoded ?? []), operation.formUrlEncoded] + : currentOperation.formUrlEncoded, + paramsSerializer: operation.paramsSerializer + ? [ + ...(currentOperation.paramsSerializer ?? []), + operation.paramsSerializer, + ] + : currentOperation.paramsSerializer, + }; + return currentAcc; }; export const generateTargetForTags = ( @@ -101,7 +101,7 @@ export const generateTargetForTags = ( .map(addDefaultTagIfEmpty) .reduce( (acc, operation, index, arr) => { - const targetTags = generateTargetTags(acc, operation); + const targetTags = generateTargetTags(acc, operation, options); if (index === arr.length - 1) { return Object.entries(targetTags).reduce< diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index d233eda9f..1dae76adc 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -209,4 +209,19 @@ export default defineConfig({ target: '../specifications/petstore.yaml', }, }, + multipleTags: { + output: { + target: '../generated/default/multiple-tags/endpoints.ts', + schemas: '../generated/default/multiple-tags/model', + mode: 'tags', + override: { + multiTagResolver: (tags) => { + return tags[tags.length - 1]; + }, + }, + }, + input: { + target: '../specifications/multiple-tags.yaml', + }, + }, }); diff --git a/tests/specifications/multiple-tags.yaml b/tests/specifications/multiple-tags.yaml new file mode 100644 index 000000000..4fdf607f8 --- /dev/null +++ b/tests/specifications/multiple-tags.yaml @@ -0,0 +1,84 @@ +openapi: '3.0.0' +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - dogs + - cats + responses: + '200': + description: A paged array of pets + content: + application/hal+json: + schema: + $ref: '#/components/schemas/Pets' + /pets/dog/{dogId}: + get: + summary: Info for a specific dog + operationId: showDogById + tags: + - dog + parameters: + - name: dogId + in: path + required: true + description: The id of the dog to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Dog' + /pets/cat/{catId}: + get: + summary: Info for a specific cat + operationId: showCatById + tags: + - cat + parameters: + - name: catId + in: path + required: true + description: The id of the cat to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Cat' +components: + schemas: + Pet: + type: object + oneOf: + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Cat' + Dog: + type: object + properties: + barksPerMinute: + type: integer + Cat: + type: object + properties: + petsRequested: + type: integer + Pets: + type: array + items: + $ref: '#/components/schemas/Pet' From 4ca87ec07d4314de17792ea922540592cf948401 Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:24:41 +0100 Subject: [PATCH 2/3] Update tests/specifications/multiple-tags.yaml Co-authored-by: Shodai Suzuki --- tests/specifications/multiple-tags.yaml | 40 ------------------------- 1 file changed, 40 deletions(-) diff --git a/tests/specifications/multiple-tags.yaml b/tests/specifications/multiple-tags.yaml index 4fdf607f8..f38f8da69 100644 --- a/tests/specifications/multiple-tags.yaml +++ b/tests/specifications/multiple-tags.yaml @@ -21,46 +21,6 @@ paths: application/hal+json: schema: $ref: '#/components/schemas/Pets' - /pets/dog/{dogId}: - get: - summary: Info for a specific dog - operationId: showDogById - tags: - - dog - parameters: - - name: dogId - in: path - required: true - description: The id of the dog to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: '#/components/schemas/Dog' - /pets/cat/{catId}: - get: - summary: Info for a specific cat - operationId: showCatById - tags: - - cat - parameters: - - name: catId - in: path - required: true - description: The id of the cat to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: '#/components/schemas/Cat' components: schemas: Pet: From 976deaa3e7b29e2cd17be2fc223e4f36bb127526 Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:19:58 +0100 Subject: [PATCH 3/3] fix: kebab name and remove setting. trim spec --- .../pages/reference/configuration/output.md | 30 ------------------- packages/core/src/types.ts | 2 -- packages/core/src/writers/target-tags.ts | 4 +-- tests/configs/default.config.ts | 5 ---- tests/specifications/multiple-tags.yaml | 19 ++---------- 5 files changed, 3 insertions(+), 57 deletions(-) diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index 6cddb6e31..e30b8d8c1 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -1625,36 +1625,6 @@ Type: `Object`. Exactly the same as the `override.operations` but this time you can do it by tags -#### multiTagResolver - -Type: `Function`. - -```ts -// type signature -(tags: string[]) => string; -``` - -When using mode `tags` or `tags-split` and an operation have multiple tags, only one of them will be used when generating. -This setting allows you to decide which tag to use. Default is the first tag. - -Example: - -```js -module.exports = { - petstore: { - output: { - override: { - multiTagResolver: (tags) => { - const indexOfFavoriteTag = tags.include('myFavoriteTag'); - if (indexOfFavoriteTag >= 0) return tags[indexOfFavoriteTag]; - return tags[0]; - }, - }, - }, - }, -}; -``` - #### operationName Type: `Function`. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 69ee226a0..4792e49bb 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -77,7 +77,6 @@ export type NormalizedOverrideOutput = { mutator?: NormalizedMutator; operations: { [key: string]: NormalizedOperationOptions }; tags: { [key: string]: NormalizedOperationOptions }; - multiTagResolver?: (tags: string[]) => string; mock?: OverrideMockOptions; contentType?: OverrideOutputContentType; header: false | ((info: InfoObject) => string[] | string); @@ -361,7 +360,6 @@ export type OverrideOutput = { mutator?: Mutator; operations?: { [key: string]: OperationOptions }; tags?: { [key: string]: OperationOptions }; - multiTagResolver?: (tags: string[]) => string; mock?: OverrideMockOptions; contentType?: OverrideOutputContentType; header?: boolean | ((info: InfoObject) => string[] | string); diff --git a/packages/core/src/writers/target-tags.ts b/packages/core/src/writers/target-tags.ts index ee650af8e..a7ce5852e 100644 --- a/packages/core/src/writers/target-tags.ts +++ b/packages/core/src/writers/target-tags.ts @@ -20,9 +20,7 @@ const generateTargetTags = ( ): { [key: string]: GeneratorTargetFull; } => { - const tag = options.override.multiTagResolver - ? options.override.multiTagResolver(operation.tags) - : operation.tags[0]; + const tag = kebab(operation.tags[0]); const currentOperation = currentAcc[tag]; if (!currentOperation) { diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index 1dae76adc..921feb362 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -214,11 +214,6 @@ export default defineConfig({ target: '../generated/default/multiple-tags/endpoints.ts', schemas: '../generated/default/multiple-tags/model', mode: 'tags', - override: { - multiTagResolver: (tags) => { - return tags[tags.length - 1]; - }, - }, }, input: { target: '../specifications/multiple-tags.yaml', diff --git a/tests/specifications/multiple-tags.yaml b/tests/specifications/multiple-tags.yaml index f38f8da69..0968fc92a 100644 --- a/tests/specifications/multiple-tags.yaml +++ b/tests/specifications/multiple-tags.yaml @@ -12,8 +12,8 @@ paths: summary: List all pets operationId: listPets tags: - - dogs - cats + - dogs responses: '200': description: A paged array of pets @@ -23,22 +23,7 @@ paths: $ref: '#/components/schemas/Pets' components: schemas: - Pet: - type: object - oneOf: - - $ref: '#/components/schemas/Dog' - - $ref: '#/components/schemas/Cat' - Dog: - type: object - properties: - barksPerMinute: - type: integer - Cat: - type: object - properties: - petsRequested: - type: integer Pets: type: array items: - $ref: '#/components/schemas/Pet' + type: object