Skip to content

Commit

Permalink
fix(zui): handle all literal types when transforming to typescript (#377
Browse files Browse the repository at this point in the history
)
  • Loading branch information
franklevasseur authored Sep 25, 2024
1 parent 72c5668 commit 4f3acd3
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 7 deletions.
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",
Expand Down
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
Expand Up @@ -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
Expand Up @@ -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()
Expand Down Expand Up @@ -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 })
Expand Down
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
Expand Up @@ -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)
Expand Down
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
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion zui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit 4f3acd3

Please sign in to comment.