From c57ca4e386c7146b7105a95e03fa2087851fa6ba Mon Sep 17 00:00:00 2001 From: NekoPaw <152394158+sb123sb123@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:55:47 +0800 Subject: [PATCH] fix: support extensible enum values Signed-off-by: NekoPaw <152394158+sb123sb123@users.noreply.github.com> --- src/services/__tests__/models/Schema.test.ts | 52 ++++++++++++++++++++ src/services/models/Schema.ts | 16 +++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index 514599d97f..9d16764d06 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -311,6 +311,58 @@ describe('Models', () => { `); }); describe('enum values', () => { + function createSchemaModel(schema: Record) { + const parser = new OpenAPIParser( + { + openapi: '3.0.0', + info: { title: 'Enum extension test', version: '1.0.0' }, + paths: {}, + } as any, + undefined, + opts, + ); + return new SchemaModel(parser, schema as any, '#/components/schemas/Test', opts); + } + + test('uses x-extensible-enum values as display enums', () => { + const model = createSchemaModel({ + type: 'string', + 'x-extensible-enum': ['open', 'closed'], + }); + + expect(model.enum).toEqual(['open', 'closed']); + expect(printSchema(model, enumDetailsPrinter)).toContain('enum: [open,closed]'); + }); + + test('uses other x-*-enum extension values', () => { + const model = createSchemaModel({ + type: 'string', + 'x-acme-status-enum': ['ready', 'closed'], + }); + + expect(model.enum).toEqual(['ready', 'closed']); + expect(printSchema(model, enumDetailsPrinter)).toContain('enum: [ready,closed]'); + }); + + test('uses enum extensions declared on array items', () => { + const model = createSchemaModel({ + type: 'array', + items: { type: 'string', 'x-extensible-enum': ['fast', 'slow'] }, + }); + + expect(model.enum).toEqual(['fast', 'slow']); + expect(printSchema(model, enumDetailsPrinter)).toContain('enum: [fast,slow]'); + }); + + test('prefers the standard enum when both forms are present', () => { + const model = createSchemaModel({ + type: 'string', + enum: ['stable'], + 'x-extensible-enum': ['preview'], + }); + + expect(model.enum).toEqual(['stable']); + }); test('should get correct fields enum fields without duplication', () => { const spec = parseYaml(outdent` openapi: 3.0.0 diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index a07e071949..3bfc18e7c4 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -122,7 +122,7 @@ export class SchemaModel { this.description = schema.description || ''; this.type = schema.type || detectType(schema); this.format = schema.format; - this.enum = schema.enum || []; + this.enum = getSchemaEnum(schema); this['x-enumDescriptions'] = schema['x-enumDescriptions']; this.example = schema.example; this.examples = schema.examples; @@ -599,6 +599,20 @@ function buildAdditionalItems({ return []; } +function getSchemaEnum(schema: OpenAPISchema): any[] { + if (schema.enum !== undefined) { + return schema.enum; + } + + const enumExtensions = Object.entries(schema).filter( + ([name, value]) => name.startsWith('x-') && name.endsWith('-enum') && Array.isArray(value), + ); + const extensibleEnum = enumExtensions.find(([name]) => name === 'x-extensible-enum')?.[1]; + const values = extensibleEnum ?? enumExtensions[0]?.[1]; + + return Array.isArray(values) ? values : []; +} + function getDiscriminator(schema: OpenAPISchema): OpenAPISchema['discriminator'] { return schema.discriminator || schema['x-discriminator']; }