From e4118479f69209c5dd09a2be0e978834dcd9eb8f Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Mon, 25 Jul 2022 17:02:37 +0300 Subject: [PATCH] fix: enum duplication values when schema uses a specific combination of oneOf and allOf(#2088) --- src/services/OpenAPIParser.ts | 2 +- src/services/__tests__/models/Schema.test.ts | 185 ++++++++++++++++++- src/services/__tests__/models/helpers.ts | 4 + 3 files changed, 189 insertions(+), 2 deletions(-) diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index ef8930e85f..76fbefc925 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -240,7 +240,7 @@ export class OpenAPIParser { if (enumProperty !== undefined) { if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) { - receiver.enum = [...enumProperty, ...receiver.enum]; + receiver.enum = Array.from(new Set([...enumProperty, ...receiver.enum])); } else { receiver.enum = enumProperty; } diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index 85f72a4f7f..9ad5feffa4 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -4,7 +4,7 @@ import { outdent } from 'outdent'; import { SchemaModel } from '../../models/Schema'; import { OpenAPIParser } from '../../OpenAPIParser'; import { RedocNormalizedOptions } from '../../RedocNormalizedOptions'; -import { printSchema } from './helpers'; +import { enumDetailsPrinter, printSchema } from './helpers'; const opts = new RedocNormalizedOptions({}); @@ -283,5 +283,188 @@ describe('Models', () => { allOf: " `); }); + describe('enum values', () => { + test('should get correct fields enum fields without duplication', () => { + const spec = parseYaml(outdent` + openapi: 3.0.0 + components: + schemas: + StringField: { type: string, title: StringField, enum: [A, B, C] } + FieldA: { type: string, title: FieldA, enum: [A1, A2, A3] } + FieldB: { type: string, title: FieldB, enum: [B1, B2, B3] } + FieldC: { type: string, title: FieldC, enum: [C1, C2, C3] } + ObjectWithAllOf: + title: StringFilter + type: object + allOf: + - properties: + type: { type: string, enum: [STRING] } + field: { $ref: '#/components/schemas/StringField' } + required: [type, field, values] + - oneOf: + - properties: + field: { type: string, enum: [A] } + values: { type: array, items: { $ref: '#/components/schemas/FieldA' } } + - properties: + field: { type: string, enum: [B] } + values: { type: array, items: { $ref: '#/components/schemas/FieldB' } } + - properties: + field: { type: string, enum: [C] } + values: { type: array, items: { $ref: '#/components/schemas/FieldC' } } + ObjectWithOneOf: + title: StringFilter + type: object + properties: + type: { type: string, enum: [STRING] } + field: { $ref: '#/components/schemas/StringField' } + required: [type, field, values] + oneOf: + - properties: + field: { type: string, enum: [A] } + values: { type: array, items: { $ref: '#/components/schemas/FieldA' } } + - properties: + field: { type: string, enum: [B] } + values: { type: array, items: { $ref: '#/components/schemas/FieldB' } } + - properties: + field: { type: string, enum: [C] } + values: { type: array, items: { $ref: '#/components/schemas/FieldC' } } + `) as any; + + parser = new OpenAPIParser(spec, undefined, opts); + const schemaWithOneOf = new SchemaModel( + parser, + spec.components.schemas.ObjectWithOneOf, + '#/components/schemas/ObjectWithOneOf', + opts, + ); + expect(printSchema(schemaWithOneOf, enumDetailsPrinter)).toMatchInlineSnapshot(` + "oneOf + StringFilter -> + field*: enum: [A,B,C] + values*: [enum: [A1,A2,A3]] + type*: enum: [STRING] + StringFilter -> + field*: enum: [A,B,C] + values*: [enum: [B1,B2,B3]] + type*: enum: [STRING] + StringFilter -> + field*: enum: [A,B,C] + values*: [enum: [C1,C2,C3]] + type*: enum: [STRING]" + `); + + const schemaWithAllOf = new SchemaModel( + parser, + spec.components.schemas.ObjectWithAllOf, + '#/components/schemas/ObjectWithAllOf', + opts, + ); + expect(printSchema(schemaWithAllOf, enumDetailsPrinter)).toMatchInlineSnapshot(` + "oneOf + object -> + type*: enum: [STRING] + field*: enum: [A,B,C] + values*: [enum: [A1,A2,A3]] + object -> + type*: enum: [STRING] + field*: enum: [B,A,C] + values*: [enum: [B1,B2,B3]] + object -> + type*: enum: [STRING] + field*: enum: [C,A,B] + values*: [enum: [C1,C2,C3]]" + `); + }); + + test('should get correct fields enum limits', () => { + const spec = parseYaml(outdent` + openapi: 3.0.0 + components: + schemas: + StringField: { type: string, title: StringField, enum: [A, B, C] } + FieldA: { type: string, title: FieldA, enum: [A1, A2, A3] } + FieldB: { type: string, title: FieldB, enum: [B1, B2, B3] } + FieldC: { type: string, title: FieldC, enum: [C1, C2, C3] } + ObjectWithAllOf: + title: StringFilter + type: object + allOf: + - properties: + type: { type: string, enum: [STRING] } + required: [type, field, values] + - oneOf: + - properties: + field: { type: string, enum: [A] } + values: { type: array, items: { $ref: '#/components/schemas/FieldA' } } + - properties: + field: { type: string, enum: [B] } + values: { type: array, items: { $ref: '#/components/schemas/FieldB' } } + - properties: + field: { type: string, enum: [C] } + values: { type: array, items: { $ref: '#/components/schemas/FieldC' } } + ObjectWithOneOf: + title: StringFilter + type: object + properties: + type: { type: string, enum: [STRING] } + required: [type, field, values] + oneOf: + - properties: + field: { type: string, enum: [A] } + values: { type: array, items: { $ref: '#/components/schemas/FieldA' } } + - properties: + field: { type: string, enum: [B] } + values: { type: array, items: { $ref: '#/components/schemas/FieldB' } } + - properties: + field: { type: string, enum: [C] } + values: { type: array, items: { $ref: '#/components/schemas/FieldC' } } + `) as any; + + parser = new OpenAPIParser(spec, undefined, opts); + const schemaWithOneOf = new SchemaModel( + parser, + spec.components.schemas.ObjectWithOneOf, + '#/components/schemas/ObjectWithOneOf', + opts, + ); + expect(printSchema(schemaWithOneOf, enumDetailsPrinter)).toMatchInlineSnapshot(` + "oneOf + StringFilter -> + field*: enum: [A] + values*: [enum: [A1,A2,A3]] + type*: enum: [STRING] + StringFilter -> + field*: enum: [B] + values*: [enum: [B1,B2,B3]] + type*: enum: [STRING] + StringFilter -> + field*: enum: [C] + values*: [enum: [C1,C2,C3]] + type*: enum: [STRING]" + `); + + const schemaWithAllOf = new SchemaModel( + parser, + spec.components.schemas.ObjectWithAllOf, + '#/components/schemas/ObjectWithAllOf', + opts, + ); + expect(printSchema(schemaWithAllOf, enumDetailsPrinter)).toMatchInlineSnapshot(` + "oneOf + object -> + type*: enum: [STRING] + field*: enum: [A] + values*: [enum: [A1,A2,A3]] + object -> + type*: enum: [STRING] + field*: enum: [B] + values*: [enum: [B1,B2,B3]] + object -> + type*: enum: [STRING] + field*: enum: [C] + values*: [enum: [C1,C2,C3]]" + `); + }); + }); }); }); diff --git a/src/services/__tests__/models/helpers.ts b/src/services/__tests__/models/helpers.ts index 60455c6145..9ff3de20f6 100644 --- a/src/services/__tests__/models/helpers.ts +++ b/src/services/__tests__/models/helpers.ts @@ -12,6 +12,10 @@ export function circularDetailsPrinter(schema: SchemaModel): string { return schema.isCircular ? ' !circular' : ''; } +export function enumDetailsPrinter(schema: SchemaModel): string { + return schema.enum ? `enum: [${schema.enum.toString()}]` : ''; +} + export function printSchema( schema: SchemaModel, detailsPrinter: (schema: SchemaModel) => string = () => '',