diff --git a/.changeset/stale-starfishes-sell.md b/.changeset/stale-starfishes-sell.md new file mode 100644 index 000000000..13e5fcd18 --- /dev/null +++ b/.changeset/stale-starfishes-sell.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +fix(valibot): expand support for `format: int64` diff --git a/packages/openapi-ts-tests/main/test/3.1.x.test.ts b/packages/openapi-ts-tests/main/test/3.1.x.test.ts index 7203ce26a..f159eacb7 100644 --- a/packages/openapi-ts-tests/main/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.1.x.test.ts @@ -798,6 +798,15 @@ describe(`OpenAPI ${version}`, () => { }), description: "validator schemas with merged unions (can't use .merge())", }, + { + config: createConfig({ + input: 'integer-formats.yaml', + output: 'integer-formats', + plugins: ['valibot'], + }), + description: + 'generates validator schemas for all integer format combinations (number/integer/string types with int8, int16, int32, int64, uint8, uint16, uint32, uint64 formats)', + }, ]; it.each(scenarios)('$description', async ({ config }) => { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 077f21d14..e49e2e14e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -4,7 +4,11 @@ import * as v from 'valibot'; export const vFoo = v.object({ bar: v.optional(v.pipe(v.number(), v.integer())), - foo: v.optional(v.bigint(), BigInt(0)), + foo: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1')), BigInt(0)), id: v.string() }); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts index 50141df6c..f082c391b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts @@ -664,7 +664,7 @@ export const vCollectionFormatData = v.object({ export const vTypesData = v.object({ body: v.optional(v.never()), path: v.optional(v.object({ - id: v.optional(v.pipe(v.number(), v.integer())) + id: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'))) })), query: v.object({ parameterNumber: v.optional(v.number(), 123), diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 077f21d14..e49e2e14e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -4,7 +4,11 @@ import * as v from 'valibot'; export const vFoo = v.object({ bar: v.optional(v.pipe(v.number(), v.integer())), - foo: v.optional(v.bigint(), BigInt(0)), + foo: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1')), BigInt(0)), id: v.string() }); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts index bd2c582c6..d623cf75c 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts @@ -714,8 +714,8 @@ export const vDefault = v.object({ }); export const vPageable = v.object({ - page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0)), 0), - size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))), + page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'), v.minValue(0)), 0), + size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'), v.minValue(1))), sort: v.optional(v.array(v.string())) }); @@ -775,10 +775,10 @@ export const vCompositionWithOneOfAndProperties = v.intersect([ ]), v.object({ baz: v.union([ - v.pipe(v.number(), v.integer(), v.minValue(0)), + v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'), v.minValue(0)), v.null() ]), - qux: v.pipe(v.number(), v.integer(), v.minValue(0)) + qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'), v.minValue(0)) }) ]); @@ -943,10 +943,10 @@ export const vModelWithOneOfAndProperties = v.intersect([ ]), v.object({ baz: v.union([ - v.pipe(v.number(), v.integer(), v.minValue(0)), + v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'), v.minValue(0)), v.null() ]), - qux: v.pipe(v.number(), v.integer(), v.minValue(0)) + qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'), v.minValue(0)) }) ]); @@ -1812,7 +1812,7 @@ export const vCollectionFormatData = v.object({ export const vTypesData = v.object({ body: v.optional(v.never()), path: v.optional(v.object({ - id: v.optional(v.pipe(v.number(), v.integer())) + id: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'))) })), query: v.object({ parameterNumber: v.optional(v.number(), 123), @@ -1958,7 +1958,7 @@ export const vComplexParamsData = v.object({ vModelWithDictionary ]), user: v.optional(v.pipe(v.object({ - id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), + id: v.optional(v.pipe(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1')), v.readonly())), name: v.optional(v.pipe(v.union([ v.pipe(v.string(), v.readonly()), v.null() @@ -1966,7 +1966,7 @@ export const vComplexParamsData = v.object({ }), v.readonly())) })), path: v.object({ - id: v.pipe(v.number(), v.integer()), + id: v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1')), 'api-version': v.string() }), query: v.optional(v.never()) diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/integer-formats/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/integer-formats/valibot.gen.ts new file mode 100644 index 000000000..ac62ef699 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/integer-formats/valibot.gen.ts @@ -0,0 +1,50 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as v from 'valibot'; + +export const vIntegerFormats = v.object({ + numberNoFormat: v.optional(v.number()), + numberInt8: v.optional(v.pipe(v.number(), v.minValue(-128, 'Invalid value: Expected int8 to be >= -2^7'), v.maxValue(127, 'Invalid value: Expected int8 to be <= 2^7-1'))), + numberInt16: v.optional(v.pipe(v.number(), v.minValue(-32768, 'Invalid value: Expected int16 to be >= -2^15'), v.maxValue(32767, 'Invalid value: Expected int16 to be <= 2^15-1'))), + numberInt32: v.optional(v.pipe(v.number(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'))), + numberInt64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1'))), + numberUint8: v.optional(v.pipe(v.number(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'))), + numberUint16: v.optional(v.pipe(v.number(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'))), + numberUint32: v.optional(v.pipe(v.number(), v.minValue(0, 'Invalid value: Expected uint32 to be >= 0'), v.maxValue(4294967295, 'Invalid value: Expected uint32 to be <= 2^32-1'))), + numberUint64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('0'), 'Invalid value: Expected uint64 to be >= 0'), v.maxValue(BigInt('18446744073709551615'), 'Invalid value: Expected uint64 to be <= 2^64-1'))), + integerNoFormat: v.optional(v.pipe(v.number(), v.integer())), + integerInt8: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-128, 'Invalid value: Expected int8 to be >= -2^7'), v.maxValue(127, 'Invalid value: Expected int8 to be <= 2^7-1'))), + integerInt16: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-32768, 'Invalid value: Expected int16 to be >= -2^15'), v.maxValue(32767, 'Invalid value: Expected int16 to be <= 2^15-1'))), + integerInt32: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'))), + integerInt64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1'))), + integerUint8: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'))), + integerUint16: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'))), + integerUint32: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint32 to be >= 0'), v.maxValue(4294967295, 'Invalid value: Expected uint32 to be <= 2^32-1'))), + integerUint64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('0'), 'Invalid value: Expected uint64 to be >= 0'), v.maxValue(BigInt('18446744073709551615'), 'Invalid value: Expected uint64 to be <= 2^64-1'))), + stringInt64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1'))), + stringUint64: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('0'), 'Invalid value: Expected uint64 to be >= 0'), v.maxValue(BigInt('18446744073709551615'), 'Invalid value: Expected uint64 to be <= 2^64-1'))) +}); \ No newline at end of file diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 077f21d14..e49e2e14e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -4,7 +4,11 @@ import * as v from 'valibot'; export const vFoo = v.object({ bar: v.optional(v.pipe(v.number(), v.integer())), - foo: v.optional(v.bigint(), BigInt(0)), + foo: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1')), BigInt(0)), id: v.string() }); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts index aed5cb87e..62fe728b9 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts @@ -709,8 +709,8 @@ export const vDefault = v.object({ }); export const vPageable = v.object({ - page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0)), 0), - size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))), + page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'), v.minValue(0)), 0), + size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'), v.minValue(1))), sort: v.optional(v.array(v.string())) }); @@ -766,10 +766,10 @@ export const vCompositionWithOneOfAndProperties = v.intersect([ ]), v.object({ baz: v.union([ - v.pipe(v.number(), v.integer(), v.minValue(0)), + v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'), v.minValue(0)), v.null() ]), - qux: v.pipe(v.number(), v.integer(), v.minValue(0)) + qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'), v.minValue(0)) }) ]); @@ -941,10 +941,10 @@ export const vModelWithOneOfAndProperties = v.intersect([ ]), v.object({ baz: v.union([ - v.pipe(v.number(), v.integer(), v.minValue(0)), + v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 2^16-1'), v.minValue(0)), v.null() ]), - qux: v.pipe(v.number(), v.integer(), v.minValue(0)) + qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 2^8-1'), v.minValue(0)) }) ]); @@ -1817,7 +1817,7 @@ export const vCollectionFormatData = v.object({ export const vTypesData = v.object({ body: v.optional(v.never()), path: v.optional(v.object({ - id: v.optional(v.pipe(v.number(), v.integer())) + id: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1'))) })), query: v.object({ parameterNumber: v.optional(v.number(), 123), @@ -1964,7 +1964,7 @@ export const vComplexParamsData = v.object({ vModelWithDictionary ]), user: v.optional(v.pipe(v.object({ - id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), + id: v.optional(v.pipe(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1')), v.readonly())), name: v.optional(v.pipe(v.union([ v.pipe(v.string(), v.readonly()), v.null() @@ -1972,7 +1972,7 @@ export const vComplexParamsData = v.object({ }), v.readonly())) })), path: v.object({ - id: v.pipe(v.number(), v.integer()), + id: v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2^31'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2^31-1')), 'api-version': v.string() }), query: v.optional(v.never()) diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/types.gen.ts index 32a859497..7fc17b795 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/types.gen.ts @@ -16,6 +16,24 @@ export type Foo = { [key: string]: unknown; }; garply?: 10n; + numberInt8?: 100; + numberInt16?: 1000; + numberInt32?: 100000; + numberInt64?: 1000000000000; + numberUint8?: 200; + numberUint16?: 50000; + numberUint32?: 3000000000; + numberUint64?: 18000000000000000000; + integerInt8?: -100; + integerInt16?: -1000; + integerInt32?: -100000; + integerInt64?: -1000000000000; + integerUint8?: 255; + integerUint16?: 65535; + integerUint32?: 4294967295; + integerUint64?: 18446744073709551615n; + stringInt64?: '-9223372036854775808'; + stringUint64?: '18446744073709551615'; }; export type ClientOptions = { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/valibot.gen.ts index 43a69a877..01d8e66df 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/schema-const/valibot.gen.ts @@ -15,5 +15,23 @@ export const vFoo = v.object({ v.literal(true) ])), corge: v.optional(v.object({})), - garply: v.optional(v.bigint()) + garply: v.optional(v.literal(BigInt('10'))), + numberInt8: v.optional(v.literal(100)), + numberInt16: v.optional(v.literal(1000)), + numberInt32: v.optional(v.literal(100000)), + numberInt64: v.optional(v.literal(BigInt('1000000000000'))), + numberUint8: v.optional(v.literal(200)), + numberUint16: v.optional(v.literal(50000)), + numberUint32: v.optional(v.literal(3000000000)), + numberUint64: v.optional(v.literal(BigInt('18000000000000000000'))), + integerInt8: v.optional(v.literal(-100)), + integerInt16: v.optional(v.literal(-1000)), + integerInt32: v.optional(v.literal(-100000)), + integerInt64: v.optional(v.literal(BigInt('-1000000000000'))), + integerUint8: v.optional(v.literal(255)), + integerUint16: v.optional(v.literal(65535)), + integerUint32: v.optional(v.literal(4294967295)), + integerUint64: v.optional(v.literal(BigInt('18446744073709551615'))), + stringInt64: v.optional(v.literal(BigInt('-9223372036854775808'))), + stringUint64: v.optional(v.literal(BigInt('18446744073709551615'))) }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts index c438767bb..284f1ae3b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts @@ -3,5 +3,9 @@ import * as v from 'valibot'; export const vFoo = v.object({ - foo: v.optional(v.pipe(v.bigint(), v.minValue(BigInt(0)), v.maxValue(BigInt(100)))) + foo: v.optional(v.pipe(v.union([ + v.number(), + v.string(), + v.bigint() + ]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -2^63'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 2^63-1'), v.minValue(BigInt(0)), v.maxValue(BigInt(100)))) }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/const-values.yaml b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/const-values.yaml new file mode 100644 index 000000000..31fef4d21 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/const-values.yaml @@ -0,0 +1,93 @@ +openapi: '3.1.0' +info: + title: Number Type Const Values Test API + version: '1.0.0' +paths: {} +components: + schemas: + NumberNoFormat: + type: number + const: 42.5 + IntegerNoFormat: + type: integer + const: -1 + NumberInt8: + type: number + format: int8 + const: 100 + NumberInt16: + type: number + format: int16 + const: 1000 + NumberInt32: + type: number + format: int32 + const: 100000 + NumberInt64: + type: number + format: int64 + const: 1000000000000 + NumberUint8: + type: number + format: uint8 + const: 200 + NumberUint16: + type: number + format: uint16 + const: 50000 + NumberUint32: + type: number + format: uint32 + const: 3000000000 + NumberUint64: + type: number + format: uint64 + const: 18000000000000000000 + IntegerInt8: + type: integer + format: int8 + const: -100 + IntegerInt16: + type: integer + format: int16 + const: -1000 + IntegerInt32: + type: integer + format: int32 + const: -100000 + IntegerInt64: + type: integer + format: int64 + const: -1000000000000 + IntegerUint8: + type: integer + format: uint8 + const: 255 + IntegerUint16: + type: integer + format: uint16 + const: 65535 + IntegerUint32: + type: integer + format: uint32 + const: 4294967295 + IntegerUint64: + type: integer + format: uint64 + const: 1000000000000 + StringInt64: + type: string + format: int64 + const: '-9223372036854775808' + StringUint64: + type: string + format: uint64 + const: '18446744073709551615' + StringInt64n: + type: string + format: int64 + const: '-9223372036854775808n' + StringUint64n: + type: string + format: uint64 + const: '18446744073709551615n' diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/formats.yaml b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/formats.yaml new file mode 100644 index 000000000..8fc26a6f4 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/formats.yaml @@ -0,0 +1,64 @@ +openapi: '3.1.0' +info: + title: Integer Formats Test + version: '1.0.0' +components: + schemas: + NumberNoFormat: + type: number + NumberInt8: + type: number + format: int8 + NumberInt16: + type: number + format: int16 + NumberInt32: + type: number + format: int32 + NumberInt64: + type: number + format: int64 + NumberUint8: + type: number + format: uint8 + NumberUint16: + type: number + format: uint16 + NumberUint32: + type: number + format: uint32 + NumberUint64: + type: number + format: uint64 + IntegerNoFormat: + type: integer + IntegerInt8: + type: integer + format: int8 + IntegerInt16: + type: integer + format: int16 + IntegerInt32: + type: integer + format: int32 + IntegerInt64: + type: integer + format: int64 + IntegerUint8: + type: integer + format: uint8 + IntegerUint16: + type: integer + format: uint16 + IntegerUint32: + type: integer + format: uint32 + IntegerUint64: + type: integer + format: uint64 + StringInt64: + type: string + format: int64 + StringUint64: + type: string + format: uint64 diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/min-max-constraints.yaml b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/min-max-constraints.yaml new file mode 100644 index 000000000..be4c35945 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/spec/numberTypeToValibotSchema/min-max-constraints.yaml @@ -0,0 +1,141 @@ +openapi: '3.1.0' +info: + title: Number Type Min/Max Constraints Test API + version: '1.0.0' +paths: {} +components: + schemas: + NumberWithMinimum: + type: number + minimum: 10 + NumberWithMaximum: + type: number + maximum: 100 + NumberWithMinMax: + type: number + minimum: 0 + maximum: 100 + IntegerWithMinimum: + type: integer + minimum: 5 + IntegerWithMaximum: + type: integer + maximum: 999 + IntegerWithMinMax: + type: integer + minimum: 1 + maximum: 999 + NumberWithExclusiveMin: + type: number + exclusiveMinimum: 0 + NumberWithExclusiveMax: + type: number + exclusiveMaximum: 100 + NumberWithExclusiveMinMax: + type: number + exclusiveMinimum: 0 + exclusiveMaximum: 1 + IntegerWithExclusiveMin: + type: integer + exclusiveMinimum: 10 + IntegerWithExclusiveMax: + type: integer + exclusiveMaximum: 50 + IntegerWithExclusiveMinMax: + type: integer + exclusiveMinimum: 5 + exclusiveMaximum: 15 + NumberWithExclusiveMinInclusiveMax: + type: number + exclusiveMinimum: 10 + maximum: 90 + NumberWithInclusiveMinExclusiveMax: + type: number + minimum: 20 + exclusiveMaximum: 80 + IntegerWithExclusiveMinInclusiveMax: + type: integer + exclusiveMinimum: 5 + maximum: 50 + IntegerWithInclusiveMinExclusiveMax: + type: integer + minimum: 10 + exclusiveMaximum: 100 + Int64WithMinimum: + type: integer + format: int64 + minimum: -5000000000000 + Int64WithMaximum: + type: integer + format: int64 + maximum: 5000000000000 + Int64WithMinMax: + type: integer + format: int64 + minimum: -4000000000000 + maximum: 4000000000000 + Int64WithExclusiveMin: + type: integer + format: int64 + exclusiveMinimum: -3000000000000 + Int64WithExclusiveMax: + type: integer + format: int64 + exclusiveMaximum: 3000000000000 + Int64WithExclusiveMinMax: + type: integer + format: int64 + exclusiveMinimum: -2000000000000 + exclusiveMaximum: 2000000000000 + Int64WithExclusiveMinInclusiveMax: + type: integer + format: int64 + exclusiveMinimum: -6000000000000 + maximum: 6000000000000 + Int64WithInclusiveMinExclusiveMax: + type: integer + format: int64 + minimum: -7000000000000 + exclusiveMaximum: 7000000000000 + UInt64WithMinimum: + type: integer + format: uint64 + minimum: 5000000000000 + UInt64WithMaximum: + type: integer + format: uint64 + maximum: 15000000000000 + UInt64WithMinMax: + type: integer + format: uint64 + minimum: 1000000000000 + maximum: 10000000000000 + UInt64WithExclusiveMin: + type: integer + format: uint64 + exclusiveMinimum: 8000000000000 + UInt64WithExclusiveMax: + type: integer + format: uint64 + exclusiveMaximum: 12000000000000 + UInt64WithExclusiveMinMax: + type: integer + format: uint64 + exclusiveMinimum: 2000000000000 + exclusiveMaximum: 8000000000000 + UInt64WithExclusiveMinInclusiveMax: + type: integer + format: uint64 + exclusiveMinimum: 3000000000000 + maximum: 13000000000000 + UInt64WithInclusiveMinExclusiveMax: + type: integer + format: uint64 + minimum: 4000000000000 + exclusiveMaximum: 14000000000000 + PrecedenceTest: + type: number + minimum: 10 + maximum: 90 + exclusiveMinimum: 5 + exclusiveMaximum: 95 diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/test-helper.ts b/packages/openapi-ts-tests/main/test/plugins/valibot/test-helper.ts new file mode 100644 index 000000000..cb8fc413d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/test-helper.ts @@ -0,0 +1,211 @@ +/** + * Test helper for Valibot plugin tests + * Provides common functionality for schema generation and loading + */ + +import fs from 'node:fs'; +import path from 'node:path'; + +import { createClient } from '@hey-api/openapi-ts'; +import * as v from 'valibot'; + +/** + * Detect test name from the calling file + */ +function detectTestName(): string { + const stack = new Error().stack; + if (!stack) { + throw new Error('Unable to detect test name: no stack trace available'); + } + + // Find the first stack frame that contains a .test.ts file + const testFileMatch = stack.match(/([^\\/]+)\.test\.ts/); + if (!testFileMatch || !testFileMatch[1]) { + throw new Error( + 'Unable to detect test name: no .test.ts file found in stack trace', + ); + } + + return testFileMatch[1]; +} + +/** + * Detect base directory from the calling file + */ +function detectBaseDir(): string { + const stack = new Error().stack; + if (!stack) { + throw new Error( + 'Unable to detect base directory: no stack trace available', + ); + } + + // Try multiple regex patterns to match different stack trace formats + const patterns = [ + /at .* \(([^)]+\.test\.ts):\d+:\d+\)/, // Original pattern + /at ([^:]+\.test\.ts):\d+:\d+/, // Alternative pattern without parentheses + /([^:\s]+\.test\.ts):\d+:\d+/, // Simple pattern + ]; + + for (const pattern of patterns) { + const testFileMatch = stack.match(pattern); + if (testFileMatch && testFileMatch[1]) { + return path.dirname(testFileMatch[1]); + } + } + + throw new Error( + 'Unable to detect base directory: no .test.ts file found in stack trace', + ); +} + +/** + * Detect function name from the test file path + * Extracts the directory name between 'test/' and the test file + * e.g., from 'test/plugins/valibot/test/numberTypeToValibotSchema/formats.test.ts' + * extracts 'numberTypeToValibotSchema' + */ +function detectFunctionName(): string { + const stack = new Error().stack; + if (!stack) { + throw new Error('Unable to detect function name: no stack trace available'); + } + + // Try multiple regex patterns to match different stack trace formats + const patterns = [ + /at .* \(([^)]+\.test\.ts):\d+:\d+\)/, // Original pattern + /at ([^:]+\.test\.ts):\d+:\d+/, // Alternative pattern without parentheses + /([^:\s]+\.test\.ts):\d+:\d+/, // Simple pattern + ]; + + for (const pattern of patterns) { + const testFileMatch = stack.match(pattern); + if (testFileMatch && testFileMatch[1]) { + const testFilePath = testFileMatch[1]; + + // Extract function name from path pattern: .../test/[FUNCTION_NAME]/[TEST_NAME].test.ts + const pathParts = testFilePath.split(/[/\\]/); + const testIndex = pathParts.lastIndexOf('test'); + + if (testIndex !== -1 && testIndex < pathParts.length - 2) { + const functionName = pathParts[testIndex + 1]; + if (functionName) { + return functionName; + } + } + + throw new Error( + `Unable to extract function name from test path: ${testFilePath}\n` + + `Expected path pattern: .../test/[FUNCTION_NAME]/[TEST_NAME].test.ts`, + ); + } + } + + throw new Error( + 'Unable to detect function name: no .test.ts file found in stack trace', + ); +} + +/** + * Load and evaluate the generated schemas + */ +function loadGeneratedSchemas(generatedPath: string): any { + if (!fs.existsSync(generatedPath)) { + throw new Error( + `Generated schema file not found: ${generatedPath}\n` + + `Schema generation may have failed. Check the input schema file for errors.`, + ); + } + + try { + const generatedCode = fs.readFileSync(generatedPath, 'utf-8'); + + // Extract all export statements and create a proper return object + const exportMatches = generatedCode.match(/export const (\w+)/g); + if (!exportMatches) { + // noinspection ExceptionCaughtLocallyJS + throw new Error('No exported schemas found in generated code'); + } + + // Create evaluation code that returns an object with all exports + const schemaNames = exportMatches.map((match: string) => + match.replace('export const ', ''), + ); + const evalCode = + generatedCode + .replace(/import \* as v from 'valibot';/, '') + .replace(/export const/g, 'const') + .replace(/v\./g, 'vModule.') + + `\n\nreturn { ${schemaNames.join(', ')} };`; + + // Wrap in a function to capture the return value + const schemaFunction = new Function('vModule', evalCode); + return schemaFunction(v); + } catch (error) { + throw new Error( + `Failed to load generated schemas from ${generatedPath}: ${error instanceof Error ? error.message : String(error)}\n` + + `The generated file may contain syntax errors or be malformed.`, + ); + } +} + +/** + * Setup function for Valibot tests + * Automatically detects test name and paths, generates schemas, and returns them + */ +export async function setupValibotTest(): Promise { + // Detect test name, function name, and base directory from calling file + const testName = detectTestName(); + const functionName = detectFunctionName(); + const baseDir = detectBaseDir(); + + // Construct paths dynamically based on detected function name + const schemaPath = path.join( + baseDir, + '..', + '..', + 'spec', + functionName, + `${testName}.yaml`, + ); + const outputPath = path.join( + baseDir, + '..', + '..', + 'generated', + functionName, + testName, + ); + + // Check if spec file exists + if (!fs.existsSync(schemaPath)) { + throw new Error( + `Schema file not found: ${schemaPath}\n` + + `Expected schema file for test '${testName}' in function '${functionName}' at the above location.\n` + + `Please ensure the spec file exists and matches the test name.`, + ); + } + + try { + // Create output directory + fs.mkdirSync(outputPath, { recursive: true }); + + // Generate Valibot schemas + await createClient({ + input: schemaPath, + logs: { level: 'silent' }, + output: outputPath, + plugins: ['valibot'], + }); + + // Load and return the generated schemas + const generatedPath = path.join(outputPath, 'valibot.gen.ts'); + return loadGeneratedSchemas(generatedPath); + } catch (error) { + throw new Error( + `Failed to generate schemas for test '${testName}' in function '${functionName}': ${error instanceof Error ? error.message : String(error)}\n` + + `Schema path: ${schemaPath}\n` + + `Output path: ${outputPath}`, + ); + } +} diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/const-values.test.ts b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/const-values.test.ts new file mode 100644 index 000000000..473c21802 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/const-values.test.ts @@ -0,0 +1,292 @@ +import * as v from 'valibot'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { setupValibotTest } from '../../test-helper'; + +describe('Number Type Const Values Tests', () => { + let generatedSchemas: any; + + beforeAll(async () => { + generatedSchemas = await setupValibotTest(); + }); + + describe('Number Type Const Validation', () => { + it('should accept exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberNoFormat, 42.5); + expect(result.success).toBe(true); + }); + + it('should reject non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberNoFormat, 42.6); + expect(result.success).toBe(false); + }); + }); + + describe('Number Type Format Const Validation', () => { + it('should accept NumberInt8 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberInt8, 100); + expect(result.success).toBe(true); + }); + + it('should reject NumberInt8 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberInt8, 101); + expect(result.success).toBe(false); + }); + + it('should accept NumberInt16 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberInt16, 1000); + expect(result.success).toBe(true); + }); + + it('should reject NumberInt16 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberInt16, 1001); + expect(result.success).toBe(false); + }); + + it('should accept NumberInt32 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberInt32, 100000); + expect(result.success).toBe(true); + }); + + it('should reject NumberInt32 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberInt32, 100001); + expect(result.success).toBe(false); + }); + + it('should accept NumberInt64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vNumberInt64, + BigInt('1000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject NumberInt64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vNumberInt64, + BigInt('1000000000001'), + ); + expect(result.success).toBe(false); + }); + + it('should accept NumberUint8 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberUint8, 200); + expect(result.success).toBe(true); + }); + + it('should reject NumberUint8 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint8, 201); + expect(result.success).toBe(false); + }); + + it('should accept NumberUint16 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberUint16, 50000); + expect(result.success).toBe(true); + }); + + it('should reject NumberUint16 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint16, 50001); + expect(result.success).toBe(false); + }); + + it('should accept NumberUint32 exact const value', () => { + const result = v.safeParse(generatedSchemas.vNumberUint32, 3000000000); + expect(result.success).toBe(true); + }); + + it('should reject NumberUint32 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint32, 3000000001); + expect(result.success).toBe(false); + }); + + it('should accept NumberUint64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vNumberUint64, + BigInt('18000000000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject NumberUint64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vNumberUint64, + BigInt('18000000000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Integer Type Const Validation', () => { + it('should accept exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerNoFormat, -1); + expect(result.success).toBe(true); + }); + + it('should reject non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerNoFormat, 0); + expect(result.success).toBe(false); + }); + }); + + describe('Integer Type Format Const Validation', () => { + it('should accept IntegerInt8 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt8, -100); + expect(result.success).toBe(true); + }); + + it('should reject IntegerInt8 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt8, -99); + expect(result.success).toBe(false); + }); + + it('should accept IntegerInt16 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt16, -1000); + expect(result.success).toBe(true); + }); + + it('should reject IntegerInt16 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt16, -999); + expect(result.success).toBe(false); + }); + + it('should accept IntegerInt32 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt32, -100000); + expect(result.success).toBe(true); + }); + + it('should reject IntegerInt32 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt32, -99999); + expect(result.success).toBe(false); + }); + + it('should accept IntegerInt64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vIntegerInt64, + BigInt('-1000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject IntegerInt64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vIntegerInt64, + BigInt('-999999999999'), + ); + expect(result.success).toBe(false); + }); + + it('should accept IntegerUint8 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint8, 255); + expect(result.success).toBe(true); + }); + + it('should reject IntegerUint8 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint8, 254); + expect(result.success).toBe(false); + }); + + it('should accept IntegerUint16 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint16, 65535); + expect(result.success).toBe(true); + }); + + it('should reject IntegerUint16 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint16, 65534); + expect(result.success).toBe(false); + }); + + it('should accept IntegerUint32 exact const value', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint32, 4294967295); + expect(result.success).toBe(true); + }); + + it('should reject IntegerUint32 non-matching values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint32, 4294967294); + expect(result.success).toBe(false); + }); + + it('should accept IntegerUint64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vIntegerUint64, + BigInt('1000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject IntegerUint64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vIntegerUint64, + BigInt('1000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('String Type Format Const Validation', () => { + it('should accept StringInt64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64, + BigInt('-9223372036854775808'), + ); + expect(result.success).toBe(true); + }); + + it('should reject StringInt64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64, + BigInt('-9223372036854775807'), + ); + expect(result.success).toBe(false); + }); + + it('should accept StringUint64 exact const value', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64, + BigInt('18446744073709551615'), + ); + expect(result.success).toBe(true); + }); + + it('should reject StringUint64 non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64, + BigInt('18446744073709551614'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('String Type Format Const Validation (BigInt Literal)', () => { + it('should accept StringInt64n exact const value', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64n, + BigInt('-9223372036854775808'), + ); + expect(result.success).toBe(true); + }); + + it('should reject StringInt64n non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64n, + BigInt('-9223372036854775807'), + ); + expect(result.success).toBe(false); + }); + + it('should accept StringUint64n exact const value', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64n, + BigInt('18446744073709551615'), + ); + expect(result.success).toBe(true); + }); + + it('should reject StringUint64n non-matching values', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64n, + BigInt('18446744073709551614'), + ); + expect(result.success).toBe(false); + }); + }); +}); diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/formats.test.ts b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/formats.test.ts new file mode 100644 index 000000000..3f1ce37bc --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/formats.test.ts @@ -0,0 +1,550 @@ +import * as v from 'valibot'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { setupValibotTest } from '../../test-helper'; + +describe('Number Type Formats Tests', () => { + let generatedSchemas: any; + + beforeAll(async () => { + generatedSchemas = await setupValibotTest(); + }); + + // Format bounds and error messages from INTEGER_FORMATS + const FORMAT_BOUNDS = { + int16: { + max: 32767, + maxError: 'Expected int16 to be <= 2^15-1', + min: -32768, + minError: 'Expected int16 to be >= -2^15', + }, + int32: { + max: 2147483647, + maxError: 'Expected int32 to be <= 2^31-1', + min: -2147483648, + minError: 'Expected int32 to be >= -2^31', + }, + int64: { + max: '9223372036854775807', + maxError: 'Expected int64 to be <= 2^63-1', + min: '-9223372036854775808', + minError: 'Expected int64 to be >= -2^63', + }, + int8: { + max: 127, + maxError: 'Expected int8 to be <= 2^7-1', + min: -128, + minError: 'Expected int8 to be >= -2^7', + }, + uint16: { + max: 65535, + maxError: 'Expected uint16 to be <= 2^16-1', + min: 0, + minError: 'Expected uint16 to be >= 0', + }, + uint32: { + max: 4294967295, + maxError: 'Expected uint32 to be <= 2^32-1', + min: 0, + minError: 'Expected uint32 to be >= 0', + }, + uint64: { + max: '18446744073709551615', + maxError: 'Expected uint64 to be <= 2^64-1', + min: '0', + minError: 'Expected uint64 to be >= 0', + }, + uint8: { + max: 255, + maxError: 'Expected uint8 to be <= 2^8-1', + min: 0, + minError: 'Expected uint8 to be >= 0', + }, + }; + + describe('Number Type Format Validation', () => { + describe('numberNoFormat', () => { + it('should validate any number value', () => { + const result = v.safeParse(generatedSchemas.vNumberNoFormat, 123.456); + expect(result.success).toBe(true); + }); + }); + + describe('numberInt8', () => { + it('should validate values within int8 range', () => { + const result = v.safeParse(generatedSchemas.vNumberInt8, 100); + expect(result.success).toBe(true); + }); + + it('should reject values below int8 minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt8, -129); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int8.minError, + ); + }); + + it('should reject values above int8 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt8, 128); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int8.maxError, + ); + }); + }); + + describe('numberInt16', () => { + it('should validate values within int16 range', () => { + const result = v.safeParse(generatedSchemas.vNumberInt16, 30000); + expect(result.success).toBe(true); + }); + + it('should reject values below int16 minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt16, -32769); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int16.minError, + ); + }); + + it('should reject values above int16 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt16, 32768); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int16.maxError, + ); + }); + }); + + describe('numberInt32', () => { + it('should validate values within int32 range', () => { + const result = v.safeParse(generatedSchemas.vNumberInt32, 2000000000); + expect(result.success).toBe(true); + }); + + it('should reject values below int32 minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt32, -2147483649); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int32.minError, + ); + }); + + it('should reject values above int32 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberInt32, 2147483648); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int32.maxError, + ); + }); + }); + + describe('numberInt64', () => { + it('should validate values within int64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vNumberInt64, + 1000000000000, + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should validate string values within int64 range', () => { + const result = v.safeParse( + generatedSchemas.vNumberInt64, + '1000000000000', + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject values above int64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberInt64, + '9223372036854775808', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int64.maxError, + ); + }); + }); + + describe('numberUint8', () => { + it('should validate values within uint8 range', () => { + const result = v.safeParse(generatedSchemas.vNumberUint8, 200); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint8, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint8.minError, + ); + }); + + it('should reject values above uint8 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberUint8, 256); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint8.maxError, + ); + }); + }); + + describe('numberUint16', () => { + it('should validate values within uint16 range', () => { + const result = v.safeParse(generatedSchemas.vNumberUint16, 60000); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint16, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint16.minError, + ); + }); + + it('should reject values above uint16 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberUint16, 65536); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint16.maxError, + ); + }); + }); + + describe('numberUint32', () => { + it('should validate values within uint32 range', () => { + const result = v.safeParse(generatedSchemas.vNumberUint32, 4000000000); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint32, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint32.minError, + ); + }); + + it('should reject values above uint32 maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberUint32, 4294967296); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint32.maxError, + ); + }); + }); + + describe('numberUint64', () => { + it('should validate values within uint64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vNumberUint64, + 1000000000000, + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vNumberUint64, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.minError, + ); + }); + + it('should reject values above uint64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberUint64, + '18446744073709551616', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.maxError, + ); + }); + }); + }); + + describe('Integer Type Format Validation', () => { + describe('integerNoFormat', () => { + it('should validate any integer value', () => { + const result = v.safeParse(generatedSchemas.vIntegerNoFormat, 123); + expect(result.success).toBe(true); + }); + + it('should reject non-integer values', () => { + const result = v.safeParse(generatedSchemas.vIntegerNoFormat, 123.456); + expect(result.success).toBe(false); + }); + }); + + describe('integerInt8', () => { + it('should validate values within int8 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt8, 100); + expect(result.success).toBe(true); + }); + + it('should reject values below int8 minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt8, -129); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int8.minError, + ); + }); + + it('should reject values above int8 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt8, 128); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int8.maxError, + ); + }); + }); + + describe('integerInt16', () => { + it('should validate values within int16 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt16, 30000); + expect(result.success).toBe(true); + }); + + it('should reject values below int16 minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt16, -32769); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int16.minError, + ); + }); + + it('should reject values above int16 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt16, 32768); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int16.maxError, + ); + }); + }); + + describe('integerInt32', () => { + it('should validate values within int32 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt32, 2000000000); + expect(result.success).toBe(true); + }); + + it('should reject values below int32 minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt32, -2147483649); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int32.minError, + ); + }); + + it('should reject values above int32 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerInt32, 2147483648); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int32.maxError, + ); + }); + }); + + describe('integerInt64', () => { + it('should validate values within int64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vIntegerInt64, + 1000000000000, + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should validate string values within int64 range', () => { + const result = v.safeParse( + generatedSchemas.vIntegerInt64, + '1000000000000', + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject values above int64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerInt64, + '9223372036854775808', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int64.maxError, + ); + }); + }); + + describe('integerUint8', () => { + it('should validate values within uint8 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint8, 200); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint8, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint8.minError, + ); + }); + + it('should reject values above uint8 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint8, 256); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint8.maxError, + ); + }); + }); + + describe('integerUint16', () => { + it('should validate values within uint16 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint16, 60000); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint16, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint16.minError, + ); + }); + + it('should reject values above uint16 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint16, 65536); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint16.maxError, + ); + }); + }); + + describe('integerUint32', () => { + it('should validate values within uint32 range', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint32, 4000000000); + expect(result.success).toBe(true); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint32, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint32.minError, + ); + }); + + it('should reject values above uint32 maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint32, 4294967296); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint32.maxError, + ); + }); + }); + + describe('integerUint64', () => { + it('should validate values within uint64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vIntegerUint64, + 1000000000000, + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vIntegerUint64, -1); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.minError, + ); + }); + + it('should reject values above uint64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerUint64, + '18446744073709551616', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.maxError, + ); + }); + }); + }); + + describe('String Type Format Validation', () => { + describe('stringInt64', () => { + it('should validate string values within int64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64, + '1000000000000', + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject values below int64 minimum', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64, + '-9223372036854775809', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int64.minError, + ); + }); + + it('should reject values above int64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vStringInt64, + '9223372036854775808', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.int64.maxError, + ); + }); + }); + + describe('stringUint64', () => { + it('should validate string values within uint64 range and convert to BigInt', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64, + '1000000000000', + ); + expect(result.success).toBe(true); + expect(typeof result.output).toBe('bigint'); + }); + + it('should reject negative values', () => { + const result = v.safeParse(generatedSchemas.vStringUint64, '-1'); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.minError, + ); + }); + + it('should reject values above uint64 maximum', () => { + const result = v.safeParse( + generatedSchemas.vStringUint64, + '18446744073709551616', + ); + expect(result.success).toBe(false); + expect(result.issues![0].message).toContain( + FORMAT_BOUNDS.uint64.maxError, + ); + }); + }); + }); +}); diff --git a/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/min-max-constraints.test.ts b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/min-max-constraints.test.ts new file mode 100644 index 000000000..faeaafbc1 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/plugins/valibot/test/numberTypeToValibotSchema/min-max-constraints.test.ts @@ -0,0 +1,909 @@ +import * as v from 'valibot'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { setupValibotTest } from '../../test-helper'; + +describe('Number Type Min/Max Constraints Tests', () => { + let generatedSchemas: any; + + beforeAll(async () => { + generatedSchemas = await setupValibotTest(); + }); + + describe('Basic Number Constraints', () => { + describe('NumberWithMinimum', () => { + it('should accept values at minimum boundary', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinimum, 10); + expect(result.success).toBe(true); + }); + + it('should accept values above minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinimum, 15); + expect(result.success).toBe(true); + }); + + it('should reject values below minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinimum, 9); + expect(result.success).toBe(false); + }); + }); + + describe('NumberWithMaximum', () => { + it('should accept values at maximum boundary', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMaximum, 100); + expect(result.success).toBe(true); + }); + + it('should accept values below maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMaximum, 50); + expect(result.success).toBe(true); + }); + + it('should reject values above maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMaximum, 101); + expect(result.success).toBe(false); + }); + }); + + describe('NumberWithMinMax', () => { + it('should accept values within range', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinMax, 50); + expect(result.success).toBe(true); + }); + + it('should accept values at minimum boundary', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinMax, 0); + expect(result.success).toBe(true); + }); + + it('should accept values at maximum boundary', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinMax, 100); + expect(result.success).toBe(true); + }); + + it('should reject values below minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinMax, -1); + expect(result.success).toBe(false); + }); + + it('should reject values above maximum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithMinMax, 101); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Basic Integer Constraints', () => { + describe('IntegerWithMinimum', () => { + it('should accept values at minimum boundary', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinimum, 5); + expect(result.success).toBe(true); + }); + + it('should accept values above minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinimum, 10); + expect(result.success).toBe(true); + }); + + it('should reject values below minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinimum, 4); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithMaximum', () => { + it('should accept values at maximum boundary', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMaximum, 999); + expect(result.success).toBe(true); + }); + + it('should accept values below maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMaximum, 500); + expect(result.success).toBe(true); + }); + + it('should reject values above maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMaximum, 1000); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithMinMax', () => { + it('should accept values within range', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinMax, 500); + expect(result.success).toBe(true); + }); + + it('should accept values at minimum boundary', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinMax, 1); + expect(result.success).toBe(true); + }); + + it('should accept values at maximum boundary', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinMax, 999); + expect(result.success).toBe(true); + }); + + it('should reject values below minimum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinMax, 0); + expect(result.success).toBe(false); + }); + + it('should reject values above maximum', () => { + const result = v.safeParse(generatedSchemas.vIntegerWithMinMax, 1000); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Exclusive Constraints', () => { + describe('NumberWithExclusiveMin', () => { + it('should accept values above exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMin, + 0.1, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse(generatedSchemas.vNumberWithExclusiveMin, 0); + expect(result.success).toBe(false); + }); + + it('should reject values below exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMin, + -1, + ); + expect(result.success).toBe(false); + }); + }); + + describe('NumberWithExclusiveMax', () => { + it('should accept values below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMax, + 99.9, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMax, + 100, + ); + expect(result.success).toBe(false); + }); + + it('should reject values above exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMax, + 101, + ); + expect(result.success).toBe(false); + }); + }); + + describe('NumberWithExclusiveMinMax', () => { + it('should accept values within exclusive range', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinMax, + 0.5, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinMax, + 0, + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinMax, + 1, + ); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithExclusiveMin', () => { + it('should accept values above exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMin, + 11, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMin, + 10, + ); + expect(result.success).toBe(false); + }); + + it('should reject values below exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMin, + 9, + ); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithExclusiveMax', () => { + it('should accept values below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMax, + 49, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMax, + 50, + ); + expect(result.success).toBe(false); + }); + + it('should reject values above exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMax, + 51, + ); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithExclusiveMinMax', () => { + it('should accept values within exclusive range', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinMax, + 10, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinMax, + 5, + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinMax, + 15, + ); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Mixed Constraints', () => { + describe('NumberWithExclusiveMinInclusiveMax', () => { + it('should accept values above exclusive minimum and at inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinInclusiveMax, + 90, + ); + expect(result.success).toBe(true); + }); + + it('should accept values above exclusive minimum and below inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinInclusiveMax, + 50, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinInclusiveMax, + 10, + ); + expect(result.success).toBe(false); + }); + + it('should reject values above inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithExclusiveMinInclusiveMax, + 91, + ); + expect(result.success).toBe(false); + }); + }); + + describe('NumberWithInclusiveMinExclusiveMax', () => { + it('should accept values at inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithInclusiveMinExclusiveMax, + 20, + ); + expect(result.success).toBe(true); + }); + + it('should accept values above inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithInclusiveMinExclusiveMax, + 50, + ); + expect(result.success).toBe(true); + }); + + it('should reject values below inclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithInclusiveMinExclusiveMax, + 19, + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vNumberWithInclusiveMinExclusiveMax, + 80, + ); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithExclusiveMinInclusiveMax', () => { + it('should accept values above exclusive minimum and at inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinInclusiveMax, + 50, + ); + expect(result.success).toBe(true); + }); + + it('should accept values above exclusive minimum and below inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinInclusiveMax, + 25, + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinInclusiveMax, + 5, + ); + expect(result.success).toBe(false); + }); + + it('should reject values above inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithExclusiveMinInclusiveMax, + 51, + ); + expect(result.success).toBe(false); + }); + }); + + describe('IntegerWithInclusiveMinExclusiveMax', () => { + it('should accept values at inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithInclusiveMinExclusiveMax, + 10, + ); + expect(result.success).toBe(true); + }); + + it('should accept values above inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithInclusiveMinExclusiveMax, + 55, + ); + expect(result.success).toBe(true); + }); + + it('should reject values below inclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithInclusiveMinExclusiveMax, + 9, + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vIntegerWithInclusiveMinExclusiveMax, + 100, + ); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Format-Specific Constraints (Int64)', () => { + describe('Int64WithMinimum', () => { + it('should accept BigInt values at minimum boundary', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinimum, + BigInt('-5000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values above minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinimum, + BigInt('0'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values below minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinimum, + BigInt('-5000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithMaximum', () => { + it('should accept BigInt values at maximum boundary', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMaximum, + BigInt('5000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values below maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMaximum, + BigInt('1000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values above maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMaximum, + BigInt('5000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithMinMax', () => { + it('should accept BigInt values within range', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinMax, + BigInt('0'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values at minimum boundary', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinMax, + BigInt('-4000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values at maximum boundary', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinMax, + BigInt('4000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values below minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinMax, + BigInt('-4000000000001'), + ); + expect(result.success).toBe(false); + }); + + it('should reject BigInt values above maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithMinMax, + BigInt('4000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithExclusiveMin', () => { + it('should accept BigInt values above exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMin, + BigInt('-2999999999999'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMin, + BigInt('-3000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithExclusiveMax', () => { + it('should accept BigInt values below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMax, + BigInt('2999999999999'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMax, + BigInt('3000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithExclusiveMinMax', () => { + it('should accept BigInt values within exclusive range', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinMax, + BigInt('0'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinMax, + BigInt('-2000000000000'), + ); + expect(result.success).toBe(false); + }); + + it('should reject BigInt values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinMax, + BigInt('2000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithExclusiveMinInclusiveMax', () => { + it('should accept values above exclusive minimum and at inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinInclusiveMax, + BigInt('6000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept values above exclusive minimum and below inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinInclusiveMax, + BigInt('0'), + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinInclusiveMax, + BigInt('-6000000000000'), + ); + expect(result.success).toBe(false); + }); + + it('should reject values above inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithExclusiveMinInclusiveMax, + BigInt('6000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('Int64WithInclusiveMinExclusiveMax', () => { + it('should accept values at inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithInclusiveMinExclusiveMax, + BigInt('-7000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept values above inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithInclusiveMinExclusiveMax, + BigInt('0'), + ); + expect(result.success).toBe(true); + }); + + it('should reject values below inclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithInclusiveMinExclusiveMax, + BigInt('-7000000000001'), + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vInt64WithInclusiveMinExclusiveMax, + BigInt('7000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Format-Specific Constraints (UInt64)', () => { + describe('UInt64WithMinimum', () => { + it('should accept BigInt values at minimum boundary', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinimum, + BigInt('5000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values above minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinimum, + BigInt('8000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values below minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinimum, + BigInt('4999999999999'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithMaximum', () => { + it('should accept BigInt values at maximum boundary', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMaximum, + BigInt('15000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values below maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMaximum, + BigInt('10000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values above maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMaximum, + BigInt('15000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithMinMax', () => { + it('should accept BigInt values within range', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinMax, + BigInt('5000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values at minimum boundary', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinMax, + BigInt('1000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept BigInt values at maximum boundary', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinMax, + BigInt('10000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values below minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinMax, + BigInt('999999999999'), + ); + expect(result.success).toBe(false); + }); + + it('should reject BigInt values above maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithMinMax, + BigInt('10000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithExclusiveMin', () => { + it('should accept BigInt values above exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMin, + BigInt('8000000000001'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMin, + BigInt('8000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithExclusiveMax', () => { + it('should accept BigInt values below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMax, + BigInt('11999999999999'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMax, + BigInt('12000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithExclusiveMinMax', () => { + it('should accept BigInt values within exclusive range', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinMax, + BigInt('5000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject BigInt values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinMax, + BigInt('2000000000000'), + ); + expect(result.success).toBe(false); + }); + + it('should reject BigInt values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinMax, + BigInt('8000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithExclusiveMinInclusiveMax', () => { + it('should accept values above exclusive minimum and at inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinInclusiveMax, + BigInt('13000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept values above exclusive minimum and below inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinInclusiveMax, + BigInt('8000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinInclusiveMax, + BigInt('3000000000000'), + ); + expect(result.success).toBe(false); + }); + + it('should reject values above inclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithExclusiveMinInclusiveMax, + BigInt('13000000000001'), + ); + expect(result.success).toBe(false); + }); + }); + + describe('UInt64WithInclusiveMinExclusiveMax', () => { + it('should accept values at inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithInclusiveMinExclusiveMax, + BigInt('4000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should accept values above inclusive minimum and below exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithInclusiveMinExclusiveMax, + BigInt('9000000000000'), + ); + expect(result.success).toBe(true); + }); + + it('should reject values below inclusive minimum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithInclusiveMinExclusiveMax, + BigInt('3999999999999'), + ); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum', () => { + const result = v.safeParse( + generatedSchemas.vUInt64WithInclusiveMinExclusiveMax, + BigInt('14000000000000'), + ); + expect(result.success).toBe(false); + }); + }); + }); + + describe('Special Cases', () => { + describe('PrecedenceTest', () => { + it('should use exclusive constraints over inclusive (exclusive minimum takes precedence)', () => { + // exclusiveMinimum: 5, minimum: 10 - exclusive should take precedence + const result = v.safeParse(generatedSchemas.vPrecedenceTest, 6); + expect(result.success).toBe(true); + }); + + it('should use exclusive constraints over inclusive (exclusive maximum takes precedence)', () => { + // exclusiveMaximum: 95, maximum: 90 - exclusive should take precedence + const result = v.safeParse(generatedSchemas.vPrecedenceTest, 94); + expect(result.success).toBe(true); + }); + + it('should reject values at exclusive minimum boundary', () => { + const result = v.safeParse(generatedSchemas.vPrecedenceTest, 5); + expect(result.success).toBe(false); + }); + + it('should reject values at exclusive maximum boundary', () => { + const result = v.safeParse(generatedSchemas.vPrecedenceTest, 95); + expect(result.success).toBe(false); + }); + }); + }); +}); diff --git a/packages/openapi-ts-tests/specs/3.1.x/integer-formats.yaml b/packages/openapi-ts-tests/specs/3.1.x/integer-formats.yaml new file mode 100644 index 000000000..c2d2ade1a --- /dev/null +++ b/packages/openapi-ts-tests/specs/3.1.x/integer-formats.yaml @@ -0,0 +1,67 @@ +openapi: '3.1.0' +info: + title: Integer Formats Test + version: '1.0.0' +components: + schemas: + IntegerFormats: + type: object + properties: + numberNoFormat: + type: number + numberInt8: + type: number + format: int8 + numberInt16: + type: number + format: int16 + numberInt32: + type: number + format: int32 + numberInt64: + type: number + format: int64 + numberUint8: + type: number + format: uint8 + numberUint16: + type: number + format: uint16 + numberUint32: + type: number + format: uint32 + numberUint64: + type: number + format: uint64 + integerNoFormat: + type: integer + integerInt8: + type: integer + format: int8 + integerInt16: + type: integer + format: int16 + integerInt32: + type: integer + format: int32 + integerInt64: + type: integer + format: int64 + integerUint8: + type: integer + format: uint8 + integerUint16: + type: integer + format: uint16 + integerUint32: + type: integer + format: uint32 + integerUint64: + type: integer + format: uint64 + stringInt64: + type: string + format: int64 + stringUint64: + type: string + format: uint64 diff --git a/packages/openapi-ts-tests/specs/3.1.x/schema-const.yaml b/packages/openapi-ts-tests/specs/3.1.x/schema-const.yaml index b7fc8e828..0298bc595 100644 --- a/packages/openapi-ts-tests/specs/3.1.x/schema-const.yaml +++ b/packages/openapi-ts-tests/specs/3.1.x/schema-const.yaml @@ -31,4 +31,79 @@ components: const: 10n format: int64 type: integer + # Integer format const examples - number type + numberInt8: + const: 100 + format: int8 + type: number + numberInt16: + const: 1000 + format: int16 + type: number + numberInt32: + const: 100000 + format: int32 + type: number + numberInt64: + const: 1000000000000 + format: int64 + type: number + numberUint8: + const: 200 + format: uint8 + type: number + numberUint16: + const: 50000 + format: uint16 + type: number + numberUint32: + const: 3000000000 + format: uint32 + type: number + numberUint64: + const: 18000000000000000000 + format: uint64 + type: number + # Integer format const examples - integer type + integerInt8: + const: -100 + format: int8 + type: integer + integerInt16: + const: -1000 + format: int16 + type: integer + integerInt32: + const: -100000 + format: int32 + type: integer + integerInt64: + const: -1000000000000 + format: int64 + type: integer + integerUint8: + const: 255 + format: uint8 + type: integer + integerUint16: + const: 65535 + format: uint16 + type: integer + integerUint32: + const: 4294967295 + format: uint32 + type: integer + integerUint64: + const: 18446744073709551615n + format: uint64 + type: integer + # Integer format const examples - string type (only for 64-bit formats) + stringInt64: + const: '-9223372036854775808' + format: int64 + type: string + stringUint64: + const: '18446744073709551615' + format: uint64 + type: string type: object diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts index 08519c658..76b32a6f4 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ])), corge: z.optional(z.object({})), - garply: z.optional(z.coerce.bigint()) + garply: z.optional(z.coerce.bigint()), + numberInt8: z.optional(z.literal(100)), + numberInt16: z.optional(z.literal(1000)), + numberInt32: z.optional(z.literal(100000)), + numberInt64: z.optional(z.literal(1000000000000)), + numberUint8: z.optional(z.literal(200)), + numberUint16: z.optional(z.literal(50000)), + numberUint32: z.optional(z.literal(3000000000)), + numberUint64: z.optional(z.literal(18000000000000000000)), + integerInt8: z.optional(z.literal(-100)), + integerInt16: z.optional(z.literal(-1000)), + integerInt32: z.optional(z.literal(-100000)), + integerInt64: z.optional(z.literal(-1000000000000)), + integerUint8: z.optional(z.literal(255)), + integerUint16: z.optional(z.literal(65535)), + integerUint32: z.optional(z.literal(4294967295)), + integerUint64: z.optional(z.int()), + stringInt64: z.optional(z.literal('-9223372036854775808')), + stringUint64: z.optional(z.literal('18446744073709551615')) }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts index 808d0e91b..a7aad3761 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ]).optional(), corge: z.object({}).optional(), - garply: z.coerce.bigint().optional() + garply: z.coerce.bigint().optional(), + numberInt8: z.literal(100).optional(), + numberInt16: z.literal(1000).optional(), + numberInt32: z.literal(100000).optional(), + numberInt64: z.literal(1000000000000).optional(), + numberUint8: z.literal(200).optional(), + numberUint16: z.literal(50000).optional(), + numberUint32: z.literal(3000000000).optional(), + numberUint64: z.literal(18000000000000000000).optional(), + integerInt8: z.literal(-100).optional(), + integerInt16: z.literal(-1000).optional(), + integerInt32: z.literal(-100000).optional(), + integerInt64: z.literal(-1000000000000).optional(), + integerUint8: z.literal(255).optional(), + integerUint16: z.literal(65535).optional(), + integerUint32: z.literal(4294967295).optional(), + integerUint64: z.number().int().optional(), + stringInt64: z.literal('-9223372036854775808').optional(), + stringUint64: z.literal('18446744073709551615').optional() }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts index 7135338e0..ee6fdf50e 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ])), corge: z.optional(z.object({})), - garply: z.optional(z.coerce.bigint()) + garply: z.optional(z.coerce.bigint()), + numberInt8: z.optional(z.literal(100)), + numberInt16: z.optional(z.literal(1000)), + numberInt32: z.optional(z.literal(100000)), + numberInt64: z.optional(z.literal(1000000000000)), + numberUint8: z.optional(z.literal(200)), + numberUint16: z.optional(z.literal(50000)), + numberUint32: z.optional(z.literal(3000000000)), + numberUint64: z.optional(z.literal(18000000000000000000)), + integerInt8: z.optional(z.literal(-100)), + integerInt16: z.optional(z.literal(-1000)), + integerInt32: z.optional(z.literal(-100000)), + integerInt64: z.optional(z.literal(-1000000000000)), + integerUint8: z.optional(z.literal(255)), + integerUint16: z.optional(z.literal(65535)), + integerUint32: z.optional(z.literal(4294967295)), + integerUint64: z.optional(z.int()), + stringInt64: z.optional(z.literal('-9223372036854775808')), + stringUint64: z.optional(z.literal('18446744073709551615')) }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts index 0381c10ec..c3a7d4a82 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ])), corge: z.optional(z.object({})), - garply: z.optional(z.coerce.bigint()) + garply: z.optional(z.coerce.bigint()), + numberInt8: z.optional(z.literal(100)), + numberInt16: z.optional(z.literal(1000)), + numberInt32: z.optional(z.literal(100000)), + numberInt64: z.optional(z.literal(1000000000000)), + numberUint8: z.optional(z.literal(200)), + numberUint16: z.optional(z.literal(50000)), + numberUint32: z.optional(z.literal(3000000000)), + numberUint64: z.optional(z.literal(18000000000000000000)), + integerInt8: z.optional(z.literal(-100)), + integerInt16: z.optional(z.literal(-1000)), + integerInt32: z.optional(z.literal(-100000)), + integerInt64: z.optional(z.literal(-1000000000000)), + integerUint8: z.optional(z.literal(255)), + integerUint16: z.optional(z.literal(65535)), + integerUint32: z.optional(z.literal(4294967295)), + integerUint64: z.optional(z.int()), + stringInt64: z.optional(z.literal('-9223372036854775808')), + stringUint64: z.optional(z.literal('18446744073709551615')) }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts index 9a5156813..354d86ddb 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ]).optional(), corge: z.object({}).optional(), - garply: z.coerce.bigint().optional() + garply: z.coerce.bigint().optional(), + numberInt8: z.literal(100).optional(), + numberInt16: z.literal(1000).optional(), + numberInt32: z.literal(100000).optional(), + numberInt64: z.literal(1000000000000).optional(), + numberUint8: z.literal(200).optional(), + numberUint16: z.literal(50000).optional(), + numberUint32: z.literal(3000000000).optional(), + numberUint64: z.literal(18000000000000000000).optional(), + integerInt8: z.literal(-100).optional(), + integerInt16: z.literal(-1000).optional(), + integerInt32: z.literal(-100000).optional(), + integerInt64: z.literal(-1000000000000).optional(), + integerUint8: z.literal(255).optional(), + integerUint16: z.literal(65535).optional(), + integerUint32: z.literal(4294967295).optional(), + integerUint64: z.number().int().optional(), + stringInt64: z.literal('-9223372036854775808').optional(), + stringUint64: z.literal('18446744073709551615').optional() }); \ No newline at end of file diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts index e6b50257a..e4e073a25 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/schema-const/zod.gen.ts @@ -15,5 +15,23 @@ export const zFoo = z.object({ z.literal(true) ])), corge: z.optional(z.object({})), - garply: z.optional(z.coerce.bigint()) + garply: z.optional(z.coerce.bigint()), + numberInt8: z.optional(z.literal(100)), + numberInt16: z.optional(z.literal(1000)), + numberInt32: z.optional(z.literal(100000)), + numberInt64: z.optional(z.literal(1000000000000)), + numberUint8: z.optional(z.literal(200)), + numberUint16: z.optional(z.literal(50000)), + numberUint32: z.optional(z.literal(3000000000)), + numberUint64: z.optional(z.literal(18000000000000000000)), + integerInt8: z.optional(z.literal(-100)), + integerInt16: z.optional(z.literal(-1000)), + integerInt32: z.optional(z.literal(-100000)), + integerInt64: z.optional(z.literal(-1000000000000)), + integerUint8: z.optional(z.literal(255)), + integerUint16: z.optional(z.literal(65535)), + integerUint32: z.optional(z.literal(4294967295)), + integerUint64: z.optional(z.int()), + stringInt64: z.optional(z.literal('-9223372036854775808')), + stringUint64: z.optional(z.literal('18446744073709551615')) }); \ No newline at end of file diff --git a/packages/openapi-ts/src/plugins/valibot/number-helpers.ts b/packages/openapi-ts/src/plugins/valibot/number-helpers.ts new file mode 100644 index 000000000..bc75c42a2 --- /dev/null +++ b/packages/openapi-ts/src/plugins/valibot/number-helpers.ts @@ -0,0 +1,95 @@ +import { compiler } from '../../compiler'; + +// Integer format ranges and properties +export const INTEGER_FORMATS = { + int16: { + max: 32767, + maxError: 'Invalid value: Expected int16 to be <= 2^15-1', + min: -32768, + minError: 'Invalid value: Expected int16 to be >= -2^15', + needsBigInt: false, + }, + int32: { + max: 2147483647, + maxError: 'Invalid value: Expected int32 to be <= 2^31-1', + min: -2147483648, + minError: 'Invalid value: Expected int32 to be >= -2^31', + needsBigInt: false, + }, + int64: { + max: '9223372036854775807', + maxError: 'Invalid value: Expected int64 to be <= 2^63-1', + min: '-9223372036854775808', + minError: 'Invalid value: Expected int64 to be >= -2^63', + needsBigInt: true, + }, + int8: { + max: 127, + maxError: 'Invalid value: Expected int8 to be <= 2^7-1', + min: -128, + minError: 'Invalid value: Expected int8 to be >= -2^7', + needsBigInt: false, + }, + uint16: { + max: 65535, + maxError: 'Invalid value: Expected uint16 to be <= 2^16-1', + min: 0, + minError: 'Invalid value: Expected uint16 to be >= 0', + needsBigInt: false, + }, + uint32: { + max: 4294967295, + maxError: 'Invalid value: Expected uint32 to be <= 2^32-1', + min: 0, + minError: 'Invalid value: Expected uint32 to be >= 0', + needsBigInt: false, + }, + uint64: { + max: '18446744073709551615', + maxError: 'Invalid value: Expected uint64 to be <= 2^64-1', + min: '0', + minError: 'Invalid value: Expected uint64 to be >= 0', + needsBigInt: true, + }, + uint8: { + max: 255, + maxError: 'Invalid value: Expected uint8 to be <= 2^8-1', + min: 0, + minError: 'Invalid value: Expected uint8 to be >= 0', + needsBigInt: false, + }, +} as const; + +export type IntegerFormat = keyof typeof INTEGER_FORMATS; + +export const isIntegerFormat = ( + format: string | undefined, +): format is IntegerFormat => format !== undefined && format in INTEGER_FORMATS; + +export const needsBigIntForFormat = (format: string | undefined): boolean => + isIntegerFormat(format) && INTEGER_FORMATS[format].needsBigInt; + +export const numberParameter = ({ + isBigInt, + value, +}: { + isBigInt: boolean; + value: unknown; +}) => { + const expression = compiler.valueToExpression({ value }); + + if ( + isBigInt && + (typeof value === 'bigint' || + typeof value === 'number' || + typeof value === 'string' || + typeof value === 'boolean') + ) { + return compiler.callExpression({ + functionName: 'BigInt', + parameters: [expression], + }); + } + + return expression; +}; diff --git a/packages/openapi-ts/src/plugins/valibot/plugin.ts b/packages/openapi-ts/src/plugins/valibot/plugin.ts index de92eb04e..6ec86f477 100644 --- a/packages/openapi-ts/src/plugins/valibot/plugin.ts +++ b/packages/openapi-ts/src/plugins/valibot/plugin.ts @@ -8,6 +8,12 @@ import type { StringCase, StringName } from '../../types/case'; import { numberRegExp } from '../../utils/regexp'; import { createSchemaComment } from '../shared/utils/schema'; import { identifiers, valibotId } from './constants'; +import { + INTEGER_FORMATS, + isIntegerFormat, + needsBigIntForFormat, + numberParameter, +} from './number-helpers'; import { operationToValibotSchema } from './operation'; import type { ValibotPlugin } from './types'; @@ -253,67 +259,142 @@ const nullTypeToValibotSchema = (_props: { return expression; }; -const numberParameter = ({ - isBigInt, - value, -}: { - isBigInt: boolean; - value: unknown; -}) => { - const expression = compiler.valueToExpression({ value }); - - if ( - isBigInt && - (typeof value === 'bigint' || - typeof value === 'number' || - typeof value === 'string' || - typeof value === 'boolean') - ) { - return compiler.callExpression({ - functionName: 'BigInt', - parameters: [expression], - }); - } - - return expression; -}; - const numberTypeToValibotSchema = ({ schema, }: { schema: SchemaWithType<'integer' | 'number'>; }) => { - const isBigInt = schema.type === 'integer' && schema.format === 'int64'; + const format = schema.format; + const isInteger = schema.type === 'integer'; + const isBigInt = needsBigIntForFormat(format); + const formatInfo = isIntegerFormat(format) ? INTEGER_FORMATS[format] : null; + + // Return early if const is defined since we can create a literal type directly without additional validation + if (schema.const !== undefined && schema.const !== null) { + const constValue = schema.const; + let literalValue; + + // Case 1: Number with no format -> generate literal with the number + if (typeof constValue === 'number' && !format) { + literalValue = compiler.ots.number(constValue); + } + // Case 2: Number with format -> check if format needs BigInt, generate appropriate literal + else if (typeof constValue === 'number' && format) { + if (isBigInt) { + // Format requires BigInt, convert number to BigInt + literalValue = compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.ots.string(constValue.toString())], + }); + } else { + // Regular format, use number as-is + literalValue = compiler.ots.number(constValue); + } + } + // Case 3: Format that allows string -> generate BigInt literal (for int64/uint64 formats) + else if (typeof constValue === 'string' && isBigInt) { + // Remove 'n' suffix if present in string + const cleanString = constValue.endsWith('n') + ? constValue.slice(0, -1) + : constValue; + literalValue = compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.ots.string(cleanString)], + }); + } + // Case 4: Const is typeof bigint (literal) -> transform from literal to BigInt() + else if (typeof constValue === 'bigint') { + // Convert BigInt to string and remove 'n' suffix that toString() adds + const bigintString = constValue.toString(); + const cleanString = bigintString.endsWith('n') + ? bigintString.slice(0, -1) + : bigintString; + literalValue = compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.ots.string(cleanString)], + }); + } + // Default case: use value as-is for other types + else { + literalValue = compiler.valueToExpression({ value: constValue }); + } - if (typeof schema.const === 'number') { - // TODO: parser - handle bigint constants - const expression = compiler.callExpression({ + return compiler.callExpression({ functionName: compiler.propertyAccessExpression({ expression: identifiers.v, name: identifiers.schemas.literal, }), - parameters: [compiler.ots.number(schema.const)], + parameters: [literalValue], }); - return expression; } const pipes: Array = []; - // Zod uses coerce for bigint here, might be needed for Valibot too - const expression = compiler.callExpression({ - functionName: isBigInt - ? compiler.propertyAccessExpression({ - expression: identifiers.v, - name: identifiers.schemas.bigInt, - }) - : compiler.propertyAccessExpression({ - expression: identifiers.v, - name: identifiers.schemas.number, + // For bigint formats (int64, uint64), create union of number, string, and bigint with transform + if (isBigInt) { + const unionExpression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.schemas.union, + }), + parameters: [ + compiler.arrayLiteralExpression({ + elements: [ + compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.schemas.number, + }), + }), + compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.schemas.string, + }), + }), + compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.schemas.bigInt, + }), + }), + ], + multiLine: false, }), - }); - pipes.push(expression); + ], + }); + pipes.push(unionExpression); + + // Add transform to convert to BigInt + const transformExpression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.actions.transform, + }), + parameters: [ + compiler.arrowFunction({ + parameters: [{ name: 'x' }], + statements: compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.identifier({ text: 'x' })], + }), + }), + ], + }); + pipes.push(transformExpression); + } else { + // For regular number formats, use number schema + const expression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.schemas.number, + }), + }); + pipes.push(expression); + } - if (!isBigInt && schema.type === 'integer') { + // Add integer validation for integer types (except when using bigint union) + if (!isBigInt && isInteger) { const expression = compiler.callExpression({ functionName: compiler.propertyAccessExpression({ expression: identifiers.v, @@ -323,6 +404,50 @@ const numberTypeToValibotSchema = ({ pipes.push(expression); } + // Add format-specific range validations + if (formatInfo) { + const minValue = formatInfo.min; + const maxValue = formatInfo.max; + const minErrorMessage = formatInfo.minError; + const maxErrorMessage = formatInfo.maxError; + + // Add minimum value validation + const minExpression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.actions.minValue, + }), + parameters: [ + isBigInt + ? compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.ots.string(minValue.toString())], + }) + : compiler.ots.number(minValue as number), + compiler.ots.string(minErrorMessage), + ], + }); + pipes.push(minExpression); + + // Add maximum value validation + const maxExpression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.actions.maxValue, + }), + parameters: [ + isBigInt + ? compiler.callExpression({ + functionName: 'BigInt', + parameters: [compiler.ots.string(maxValue.toString())], + }) + : compiler.ots.number(maxValue as number), + compiler.ots.string(maxErrorMessage), + ], + }); + pipes.push(maxExpression); + } + if (schema.exclusiveMinimum !== undefined) { const expression = compiler.callExpression({ functionName: compiler.propertyAccessExpression({ @@ -763,6 +888,14 @@ const schemaTypeToValibotSchema = ({ state, }); case 'string': + // For string schemas with int64/uint64 formats, use number handler to generate union with transform + if (schema.format === 'int64' || schema.format === 'uint64') { + return { + expression: numberTypeToValibotSchema({ + schema: schema as SchemaWithType<'integer' | 'number'>, + }), + }; + } return { expression: stringTypeToValibotSchema({ schema: schema as SchemaWithType<'string'>,