Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(zui): handle all literal types when transforming to typescript #377

Merged
merged 3 commits into from
Sep 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bpinternal/zui",
"version": "0.10.0",
"version": "0.10.1",
"description": "A fork of Zod with additional features",
"type": "module",
"source": "./src/index.ts",
2 changes: 1 addition & 1 deletion zui/src/transforms/zui-to-json-schema/parsers/literal.ts
Original file line number Diff line number Diff line change
@@ -32,5 +32,5 @@ export function parseLiteralDef(def: ZodLiteralDef, refs: Refs): JsonSchema7Lite
return {
type: parsedType === 'bigint' ? 'integer' : parsedType,
const: def.value,
}
} as JsonSchema7LiteralType
}
44 changes: 43 additions & 1 deletion zui/src/transforms/zui-to-typescript-next/index.test.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'
import { UntitledDeclarationError, toTypescript } from '.'
import z from '../../z'

describe('functions', () => {
describe.concurrent('functions', () => {
it('title mandatory to declare', async () => {
const fn = z
.function()
@@ -114,6 +114,48 @@ describe('functions', () => {
`)
})

it('number literals', async () => {
const code = toTypescript(z.literal(1))
expect(code).toMatchWithoutFormatting('1')
})

it('boolean literals', async () => {
const code = toTypescript(z.literal(true))
expect(code).toMatchWithoutFormatting('true')
})

it('undefined literals', async () => {
const typings = toTypescript(z.literal(undefined))
expect(typings).toMatchWithoutFormatting('undefined')
})

it('null literals', async () => {
const typings = toTypescript(z.literal(null))
expect(typings).toMatchWithoutFormatting('null')
})

it('bigint literals', async () => {
const n = BigInt(100)
const fn = () => toTypescript(z.literal(n))
expect(fn).toThrowError()
})

it('non explicitly discriminated union', async () => {
const schema = z.union([
z.object({ enabled: z.literal(true), foo: z.string() }),
z.object({ enabled: z.literal(false), bar: z.number() }),
])
const typings = toTypescript(schema)
expect(typings).toMatchWithoutFormatting(`{
enabled: true;
foo: string
} | {
enabled: false;
bar: number
}
`)
})

it('function with named args', async () => {
const fn = z.function().title('fn').args(z.string().title('firstName').optional())
const typings = toTypescript(fn, { declaration: true })
6 changes: 5 additions & 1 deletion zui/src/transforms/zui-to-typescript-next/index.ts
Original file line number Diff line number Diff line change
@@ -274,8 +274,12 @@ ${opts.join(' | ')}`
return sUnwrapZod(def.getter(), newConfig)

case z.ZodFirstPartyTypeKind.ZodLiteral:
if (typeof def.value === 'bigint') {
throw new Error('BigInt literals are not supported yet')
}
const value: string = typeof def.value === 'string' ? escapeString(def.value) : `${def.value}`
return `${getMultilineComment(def.description)}
${escapeString((schema as z.ZodLiteral<any>).value)}`.trim()
${value}`.trim()

case z.ZodFirstPartyTypeKind.ZodEnum:
const values = def.values.map(escapeString)
52 changes: 52 additions & 0 deletions zui/src/transforms/zui-to-typescript-next/tmp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest'
import { toTypescript } from '.'
import z from '../../z'

describe('functions', () => {
it('string literals', async () => {
const typings = toTypescript(z.literal('Hello, world!'))
expect(typings).toMatchWithoutFormatting(`'Hello, world!'`)
})

it('number literals', async () => {
const code = toTypescript(z.literal(1))
expect(code).toMatchWithoutFormatting('1')
})

it('boolean literals', async () => {
const code = toTypescript(z.literal(true))
expect(code).toMatchWithoutFormatting('true')
})

it('undefined literals', async () => {
const typings = toTypescript(z.literal(undefined))
expect(typings).toMatchWithoutFormatting('undefined')
})

it('null literals', async () => {
const typings = toTypescript(z.literal(null))
expect(typings).toMatchWithoutFormatting('null')
})

it('bigint literals', async () => {
const n = BigInt(100)
const fn = () => toTypescript(z.literal(n))
expect(fn).toThrowError()
})

it('non explicitly discriminated union', async () => {
const schema = z.union([
z.object({ enabled: z.literal(true), foo: z.string() }),
z.object({ enabled: z.literal(false), bar: z.number() }),
])
const typings = toTypescript(schema)
expect(typings).toMatchWithoutFormatting(`{
enabled: true;
foo: string
} | {
enabled: false;
bar: number
}
`)
})
})
4 changes: 2 additions & 2 deletions zui/src/z/types/literal/index.ts
Original file line number Diff line number Diff line change
@@ -12,12 +12,12 @@ import {
Primitive,
} from '../index'

export interface ZodLiteralDef<T = any> extends ZodTypeDef {
export interface ZodLiteralDef<T extends Primitive = Primitive> extends ZodTypeDef {
value: T
typeName: ZodFirstPartyTypeKind.ZodLiteral
}

export class ZodLiteral<T> extends ZodType<T, ZodLiteralDef<T>> {
export class ZodLiteral<T extends Primitive> extends ZodType<T, ZodLiteralDef<T>> {
_parse(input: ParseInput): ParseReturnType<this['_output']> {
if (input.data !== this._def.value) {
const ctx = this._getOrReturnCtx(input)
5 changes: 4 additions & 1 deletion zui/tsconfig.json
Original file line number Diff line number Diff line change
@@ -20,5 +20,8 @@
"isolatedModules": true
},
"exclude": ["node_modules", "dist", "*.test.ts", "**/benchmark/**/*.ts"],
"include": ["src/**/*", "vitest.d.ts"]
"include": ["src/**/*", "vitest.d.ts"],
"ts-node": {
"esm": true
}
}