diff --git a/.changeset/weak-llamas-promise.md b/.changeset/weak-llamas-promise.md new file mode 100644 index 00000000..36a2731a --- /dev/null +++ b/.changeset/weak-llamas-promise.md @@ -0,0 +1,5 @@ +--- +"swagger-typescript-api": patch +--- + +fix: preserve OpenAPI spec property order when sortTypes is false (#151) diff --git a/src/schema-components-map.ts b/src/schema-components-map.ts index e27573ed..a38bdbd1 100644 --- a/src/schema-components-map.ts +++ b/src/schema-components-map.ts @@ -69,21 +69,42 @@ export class SchemaComponentsMap { return this._data.find((c) => c.$ref === $ref) || null; }; - // Ensure enums are at the top of components list + /** + * Partition-based stable reorder that moves items matching `predicate` to the + * front of `_data` while preserving the relative order of all other items. + * + * Unlike `Array.prototype.sort`, this never changes the relative order of + * components that don't match the predicate, so the spec-defined order of + * non-promoted types is retained (fixes issue #151). + */ + private _stablePromoteToFront( + predicate: (component: SchemaComponent) => boolean, + ) { + const promoted: SchemaComponent[] = []; + const rest: SchemaComponent[] = []; + for (const component of this._data) { + if (predicate(component)) { + promoted.push(component); + } else { + rest.push(component); + } + } + this._data = [...promoted, ...rest]; + } + + // Ensure enums are at the top of components list while preserving the + // relative order of all remaining components (stable reorder, not a sort). enumsFirst() { - this._data.sort((a, b) => { - if (Object.keys(a.rawTypeData || {}).includes("enum")) return -1; - if (Object.keys(b.rawTypeData || {}).includes("enum")) return 1; - return 0; - }); + this._stablePromoteToFront((c) => + Object.keys(c.rawTypeData || {}).includes("enum"), + ); } - // Ensure discriminators are at the top of components list + // Ensure discriminators are at the top of components list while preserving + // the relative order of all remaining components (stable reorder, not a sort). discriminatorsFirst() { - this._data.sort((a, b) => { - if (Object.keys(a.rawTypeData || {}).includes("discriminator")) return -1; - if (Object.keys(b.rawTypeData || {}).includes("discriminator")) return 1; - return 0; - }); + this._stablePromoteToFront((c) => + Object.keys(c.rawTypeData || {}).includes("discriminator"), + ); } } diff --git a/tests/spec/discriminator/__snapshots__/basic.test.ts.snap b/tests/spec/discriminator/__snapshots__/basic.test.ts.snap index def7b89e..1de3040d 100644 --- a/tests/spec/discriminator/__snapshots__/basic.test.ts.snap +++ b/tests/spec/discriminator/__snapshots__/basic.test.ts.snap @@ -13,17 +13,63 @@ exports[`basic > discriminator 1`] = ` * --------------------------------------------------------------- */ +export enum BlockDTOEnum { + Csv = "csv", + File = "file", + Kek = "kek", +} + export enum PetEnum { Dog = "dog", Lizard = "lizard", Cat = "cat", } -export enum BlockDTOEnum { - Csv = "csv", - File = "file", - Kek = "kek", -} +export type SimpleDiscriminator = SimpleObject | ComplexObject; + +export type BlockDTOWithEnum = BaseBlockDtoWithEnum & + ( + | BaseBlockDtoWithEnumTypeMapping + | BaseBlockDtoWithEnumTypeMapping + ); + +export type BlockDTO = BaseBlockDto & + ( + | BaseBlockDtoTypeMapping<"csv", CsvBlockDTO> + | BaseBlockDtoTypeMapping<"file", FileBlockDTO> + ); + +export type Pet = BasePet & + ( + | BasePetPetTypeMapping<"dog", Dog> + | BasePetPetTypeMapping<"cat", Cat> + | BasePetPetTypeMapping<"lizard", Lizard> + ); + +export type PetOnlyDiscriminator = + | ({ + pet_type: "dog"; + } & Dog) + | ({ + pet_type: "cat"; + } & Cat) + | ({ + pet_type: "lizard"; + } & Lizard); + +export type PetWithEnum = BasePetWithEnum & + ( + | BasePetWithEnumPetTypeMapping + | BasePetWithEnumPetTypeMapping + | BasePetWithEnumPetTypeMapping + ); + +export type InvalidDiscriminatorPropertyName = + BaseInvalidDiscriminatorPropertyName & + ( + | BaseInvalidDiscriminatorPropertyNameTypeMapping<"num", number> + | BaseInvalidDiscriminatorPropertyNameTypeMapping<"str", string> + ); /** kek pek */ export type Variant = @@ -49,52 +95,6 @@ export type Variant = type: "gateway"; } & VariantGateway); -export type InvalidDiscriminatorPropertyName = - BaseInvalidDiscriminatorPropertyName & - ( - | BaseInvalidDiscriminatorPropertyNameTypeMapping<"num", number> - | BaseInvalidDiscriminatorPropertyNameTypeMapping<"str", string> - ); - -export type PetWithEnum = BasePetWithEnum & - ( - | BasePetWithEnumPetTypeMapping - | BasePetWithEnumPetTypeMapping - | BasePetWithEnumPetTypeMapping - ); - -export type PetOnlyDiscriminator = - | ({ - pet_type: "dog"; - } & Dog) - | ({ - pet_type: "cat"; - } & Cat) - | ({ - pet_type: "lizard"; - } & Lizard); - -export type Pet = BasePet & - ( - | BasePetPetTypeMapping<"dog", Dog> - | BasePetPetTypeMapping<"cat", Cat> - | BasePetPetTypeMapping<"lizard", Lizard> - ); - -export type BlockDTO = BaseBlockDto & - ( - | BaseBlockDtoTypeMapping<"csv", CsvBlockDTO> - | BaseBlockDtoTypeMapping<"file", FileBlockDTO> - ); - -export type BlockDTOWithEnum = BaseBlockDtoWithEnum & - ( - | BaseBlockDtoWithEnumTypeMapping - | BaseBlockDtoWithEnumTypeMapping - ); - -export type SimpleDiscriminator = SimpleObject | ComplexObject; - export interface SimpleObject { objectType: string; } @@ -196,18 +196,21 @@ export interface VariantRollback { /** asdasdasdasdasdn */ export type VariantUndo = object; -type BaseInvalidDiscriminatorPropertyName = object; +interface BaseBlockDtoWithEnum { + title: string; + type: BlockDTOEnum; +} -type BaseInvalidDiscriminatorPropertyNameTypeMapping = { - "@type": Key; +type BaseBlockDtoWithEnumTypeMapping = { + type: Key; } & Type; -interface BasePetWithEnum { - pet_type: PetEnum; +interface BaseBlockDto { + title: string; } -type BasePetWithEnumPetTypeMapping = { - pet_type: Key; +type BaseBlockDtoTypeMapping = { + type: Key; } & Type; interface BasePet { @@ -218,21 +221,18 @@ type BasePetPetTypeMapping = { pet_type: Key; } & Type; -interface BaseBlockDto { - title: string; +interface BasePetWithEnum { + pet_type: PetEnum; } -type BaseBlockDtoTypeMapping = { - type: Key; +type BasePetWithEnumPetTypeMapping = { + pet_type: Key; } & Type; -interface BaseBlockDtoWithEnum { - title: string; - type: BlockDTOEnum; -} +type BaseInvalidDiscriminatorPropertyName = object; -type BaseBlockDtoWithEnumTypeMapping = { - type: Key; +type BaseInvalidDiscriminatorPropertyNameTypeMapping = { + "@type": Key; } & Type; " `; @@ -250,9 +250,55 @@ exports[`basic > discriminator with union enums 1`] = ` * --------------------------------------------------------------- */ +export type BlockDTOEnum = "csv" | "file" | "kek"; + export type PetEnum = "dog" | "lizard" | "cat"; -export type BlockDTOEnum = "csv" | "file" | "kek"; +export type SimpleDiscriminator = SimpleObject | ComplexObject; + +export type BlockDTOWithEnum = BaseBlockDtoWithEnum & + ( + | BaseBlockDtoWithEnumTypeMapping<"csv", CsvBlockWithEnumDTO> + | BaseBlockDtoWithEnumTypeMapping<"file", FileBlockWithEnumDTO> + ); + +export type BlockDTO = BaseBlockDto & + ( + | BaseBlockDtoTypeMapping<"csv", CsvBlockDTO> + | BaseBlockDtoTypeMapping<"file", FileBlockDTO> + ); + +export type Pet = BasePet & + ( + | BasePetPetTypeMapping<"dog", Dog> + | BasePetPetTypeMapping<"cat", Cat> + | BasePetPetTypeMapping<"lizard", Lizard> + ); + +export type PetOnlyDiscriminator = + | ({ + pet_type: "dog"; + } & Dog) + | ({ + pet_type: "cat"; + } & Cat) + | ({ + pet_type: "lizard"; + } & Lizard); + +export type PetWithEnum = BasePetWithEnum & + ( + | BasePetWithEnumPetTypeMapping<"dog", DogWithEnum> + | BasePetWithEnumPetTypeMapping<"cat", CatWithEnum> + | BasePetWithEnumPetTypeMapping<"lizard", LizardWithEnum> + ); + +export type InvalidDiscriminatorPropertyName = + BaseInvalidDiscriminatorPropertyName & + ( + | BaseInvalidDiscriminatorPropertyNameTypeMapping<"num", number> + | BaseInvalidDiscriminatorPropertyNameTypeMapping<"str", string> + ); /** kek pek */ export type Variant = @@ -278,52 +324,6 @@ export type Variant = type: "gateway"; } & VariantGateway); -export type InvalidDiscriminatorPropertyName = - BaseInvalidDiscriminatorPropertyName & - ( - | BaseInvalidDiscriminatorPropertyNameTypeMapping<"num", number> - | BaseInvalidDiscriminatorPropertyNameTypeMapping<"str", string> - ); - -export type PetWithEnum = BasePetWithEnum & - ( - | BasePetWithEnumPetTypeMapping<"dog", DogWithEnum> - | BasePetWithEnumPetTypeMapping<"cat", CatWithEnum> - | BasePetWithEnumPetTypeMapping<"lizard", LizardWithEnum> - ); - -export type PetOnlyDiscriminator = - | ({ - pet_type: "dog"; - } & Dog) - | ({ - pet_type: "cat"; - } & Cat) - | ({ - pet_type: "lizard"; - } & Lizard); - -export type Pet = BasePet & - ( - | BasePetPetTypeMapping<"dog", Dog> - | BasePetPetTypeMapping<"cat", Cat> - | BasePetPetTypeMapping<"lizard", Lizard> - ); - -export type BlockDTO = BaseBlockDto & - ( - | BaseBlockDtoTypeMapping<"csv", CsvBlockDTO> - | BaseBlockDtoTypeMapping<"file", FileBlockDTO> - ); - -export type BlockDTOWithEnum = BaseBlockDtoWithEnum & - ( - | BaseBlockDtoWithEnumTypeMapping<"csv", CsvBlockWithEnumDTO> - | BaseBlockDtoWithEnumTypeMapping<"file", FileBlockWithEnumDTO> - ); - -export type SimpleDiscriminator = SimpleObject | ComplexObject; - export interface SimpleObject { objectType: string; } @@ -425,18 +425,21 @@ export interface VariantRollback { /** asdasdasdasdasdn */ export type VariantUndo = object; -type BaseInvalidDiscriminatorPropertyName = object; +interface BaseBlockDtoWithEnum { + title: string; + type: BlockDTOEnum; +} -type BaseInvalidDiscriminatorPropertyNameTypeMapping = { - "@type": Key; +type BaseBlockDtoWithEnumTypeMapping = { + type: Key; } & Type; -interface BasePetWithEnum { - pet_type: PetEnum; +interface BaseBlockDto { + title: string; } -type BasePetWithEnumPetTypeMapping = { - pet_type: Key; +type BaseBlockDtoTypeMapping = { + type: Key; } & Type; interface BasePet { @@ -447,21 +450,18 @@ type BasePetPetTypeMapping = { pet_type: Key; } & Type; -interface BaseBlockDto { - title: string; +interface BasePetWithEnum { + pet_type: PetEnum; } -type BaseBlockDtoTypeMapping = { - type: Key; +type BasePetWithEnumPetTypeMapping = { + pet_type: Key; } & Type; -interface BaseBlockDtoWithEnum { - title: string; - type: BlockDTOEnum; -} +type BaseInvalidDiscriminatorPropertyName = object; -type BaseBlockDtoWithEnumTypeMapping = { - type: Key; +type BaseInvalidDiscriminatorPropertyNameTypeMapping = { + "@type": Key; } & Type; " `; diff --git a/tests/spec/enumNamesAsValues/__snapshots__/basic.test.ts.snap b/tests/spec/enumNamesAsValues/__snapshots__/basic.test.ts.snap index 626a368e..1d875e65 100644 --- a/tests/spec/enumNamesAsValues/__snapshots__/basic.test.ts.snap +++ b/tests/spec/enumNamesAsValues/__snapshots__/basic.test.ts.snap @@ -13,27 +13,6 @@ exports[`basic > --enum-names-as-values 1`] = ` * --------------------------------------------------------------- */ -export enum JobKind { - COMPANY = "COMPANY", - PERSONAL = "PERSONAL", - FREELANCE = "FREELANCE", - OPEN_SOURCE = "OPEN_SOURCE", -} - -export enum PetIdsWithWrongEnum { - Value10 = 10, - Value20 = 20, - Value30 = 30, - Value40 = 40, -} - -export enum PetIds { - Value10 = 10, - Value20 = 20, - Value30 = 30, - Value40 = 40, -} - /** * FooBar * @format int32 @@ -51,6 +30,27 @@ export enum IntEnumWithNames { BooFar = "BooFar", } +export enum PetIds { + Value10 = 10, + Value20 = 20, + Value30 = 30, + Value40 = 40, +} + +export enum PetIdsWithWrongEnum { + Value10 = 10, + Value20 = 20, + Value30 = 30, + Value40 = 40, +} + +export enum JobKind { + COMPANY = "COMPANY", + PERSONAL = "PERSONAL", + FREELANCE = "FREELANCE", + OPEN_SOURCE = "OPEN_SOURCE", +} + export type TestAllOfDc = (FooBarBaz & FooBar) & { prop?: string; }; diff --git a/tests/spec/enums-2.0/__snapshots__/basic.test.ts.snap b/tests/spec/enums-2.0/__snapshots__/basic.test.ts.snap index f0b9d9f1..07d01918 100644 --- a/tests/spec/enums-2.0/__snapshots__/basic.test.ts.snap +++ b/tests/spec/enums-2.0/__snapshots__/basic.test.ts.snap @@ -13,6 +13,43 @@ exports[`basic > enums-2.0 1`] = ` * --------------------------------------------------------------- */ +export enum XNullableEnum { + Value0 = 0, + Value1 = 1, + Value2 = 2, + Value3 = 3, + Value4 = 4, + Value5 = 5, +} + +export enum SimpleEnumNonNullable { + Value0 = 0, + Value1 = 1, + Value2 = 2, + Value3 = 3, + Value4 = 4, + Value5 = 5, +} + +export enum StringEnums { + Bla = "foo", + Blabla = "bar", + Boiler = "Boiler", +} + +export enum StringCompleteEnums { + Bla = "foo", + Blabla = "bar", + Boiler = "baz", +} + +/** @format int32 */ +export enum EnumWithMoreNames { + Bla = 1, + Blabla = "Blabla", + Boiler = "Boiler", +} + /** @format int32 */ export enum SomeInterestEnum { Bla = 6, @@ -34,43 +71,6 @@ export enum SomeInterestEnum { HSDFDS = "HSDFDS", } -/** @format int32 */ -export enum EnumWithMoreNames { - Bla = 1, - Blabla = "Blabla", - Boiler = "Boiler", -} - -export enum StringCompleteEnums { - Bla = "foo", - Blabla = "bar", - Boiler = "baz", -} - -export enum StringEnums { - Bla = "foo", - Blabla = "bar", - Boiler = "Boiler", -} - -export enum SimpleEnumNonNullable { - Value0 = 0, - Value1 = 1, - Value2 = 2, - Value3 = 3, - Value4 = 4, - Value5 = 5, -} - -export enum XNullableEnum { - Value0 = 0, - Value1 = 1, - Value2 = 2, - Value3 = 3, - Value4 = 4, - Value5 = 5, -} - export interface ObjWithEnum { "prop-enum-nullable"?: 0 | 1 | 2 | 3 | 4 | 5 | null; "prop-enum"?: 0 | 1 | 2 | 3 | 4 | 5; diff --git a/tests/spec/extractRequestBody/__snapshots__/basic.test.ts.snap b/tests/spec/extractRequestBody/__snapshots__/basic.test.ts.snap index f34bbe52..6fbfd06b 100644 --- a/tests/spec/extractRequestBody/__snapshots__/basic.test.ts.snap +++ b/tests/spec/extractRequestBody/__snapshots__/basic.test.ts.snap @@ -13,27 +13,27 @@ exports[`basic > --extract-request-body 1`] = ` * --------------------------------------------------------------- */ -export enum PetIdsWithWrongEnumTTT { +export enum PetNamesTTT { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", + UPPER_CASE = "UPPER_CASE", +} + +export enum PetIdsTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetIdsTTT { +export enum PetIdsWithWrongEnumTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetNamesTTT { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", - UPPER_CASE = "UPPER_CASE", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/extractResponseBody/__snapshots__/basic.test.ts.snap b/tests/spec/extractResponseBody/__snapshots__/basic.test.ts.snap index eaf33c1d..eb21f76c 100644 --- a/tests/spec/extractResponseBody/__snapshots__/basic.test.ts.snap +++ b/tests/spec/extractResponseBody/__snapshots__/basic.test.ts.snap @@ -13,27 +13,27 @@ exports[`basic > --extract-response-body 1`] = ` * --------------------------------------------------------------- */ -export enum PetIdsWithWrongEnumTTT { +export enum PetNamesTTT { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", + UPPER_CASE = "UPPER_CASE", +} + +export enum PetIdsTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetIdsTTT { +export enum PetIdsWithWrongEnumTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetNamesTTT { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", - UPPER_CASE = "UPPER_CASE", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/extractResponseError/__snapshots__/basic.test.ts.snap b/tests/spec/extractResponseError/__snapshots__/basic.test.ts.snap index e4a3e4f2..94c275f8 100644 --- a/tests/spec/extractResponseError/__snapshots__/basic.test.ts.snap +++ b/tests/spec/extractResponseError/__snapshots__/basic.test.ts.snap @@ -13,27 +13,27 @@ exports[`basic > --extract-response-body 1`] = ` * --------------------------------------------------------------- */ -export enum PetIdsWithWrongEnumTTT { +export enum PetNamesTTT { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", + UPPER_CASE = "UPPER_CASE", +} + +export enum PetIdsTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetIdsTTT { +export enum PetIdsWithWrongEnumTTT { Value10 = 10, Value20 = 20, Value30 = 30, Value40 = 40, } -export enum PetNamesTTT { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", - UPPER_CASE = "UPPER_CASE", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/modular/__snapshots__/basic.test.ts.snap b/tests/spec/modular/__snapshots__/basic.test.ts.snap index ec347ad1..f6e2af0c 100644 --- a/tests/spec/modular/__snapshots__/basic.test.ts.snap +++ b/tests/spec/modular/__snapshots__/basic.test.ts.snap @@ -454,6 +454,12 @@ exports[`basic > modular > dataContracts 1`] = ` * --------------------------------------------------------------- */ +export enum PetNames { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", +} + export enum PetIds { Value10 = 10, Value20 = 20, @@ -461,12 +467,6 @@ export enum PetIds { Value40 = 40, } -export enum PetNames { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/moduleNameFirstTag/__snapshots__/basic.test.ts.snap b/tests/spec/moduleNameFirstTag/__snapshots__/basic.test.ts.snap index 347ca8d6..cb81bb6c 100644 --- a/tests/spec/moduleNameFirstTag/__snapshots__/basic.test.ts.snap +++ b/tests/spec/moduleNameFirstTag/__snapshots__/basic.test.ts.snap @@ -13,6 +13,12 @@ exports[`basic > --module-name-first-tag 1`] = ` * --------------------------------------------------------------- */ +export enum PetNames { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", +} + export enum PetIds { Value10 = 10, Value20 = 20, @@ -20,12 +26,6 @@ export enum PetIds { Value40 = 40, } -export enum PetNames { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/moduleNameIndex/__snapshots__/basic.test.ts.snap b/tests/spec/moduleNameIndex/__snapshots__/basic.test.ts.snap index fe0d3c47..b3f80552 100644 --- a/tests/spec/moduleNameIndex/__snapshots__/basic.test.ts.snap +++ b/tests/spec/moduleNameIndex/__snapshots__/basic.test.ts.snap @@ -13,6 +13,12 @@ exports[`basic > --module-name-index 1`] = ` * --------------------------------------------------------------- */ +export enum PetNames { + FluffyHero = "Fluffy Hero", + PiggyPo = "Piggy Po", + SwaggerTypescriptApi = "Swagger Typescript Api", +} + export enum PetIds { Value10 = 10, Value20 = 20, @@ -20,12 +26,6 @@ export enum PetIds { Value40 = 40, } -export enum PetNames { - FluffyHero = "Fluffy Hero", - PiggyPo = "Piggy Po", - SwaggerTypescriptApi = "Swagger Typescript Api", -} - /** * Pet Order * An order for a pets from the pet store diff --git a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap index 1c138869..aaa5d7ea 100644 --- a/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap +++ b/tests/spec/unionEnums/__snapshots__/basic.test.ts.snap @@ -13,18 +13,18 @@ exports[`basic > --generate-union-enums 1`] = ` * --------------------------------------------------------------- */ +export type StringEnum = "String1" | "String2" | "String3" | "String4"; + +export type NumberEnum = 1 | 2 | 3 | 4; + +export type BooleanEnum = true | false; + /** * FooBar * @format int32 */ export type IntEnumWithNames = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; -export type BooleanEnum = true | false; - -export type NumberEnum = 1 | 2 | 3 | 4; - -export type StringEnum = "String1" | "String2" | "String3" | "String4"; - export type QueryParamsType = Record; export type ResponseFormat = keyof Omit;