Skip to content

Commit

Permalink
feat(zui): remove templateLiteral type as its not used (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
franklevasseur authored Oct 24, 2024
1 parent fc4bfb8 commit c27e6de
Show file tree
Hide file tree
Showing 14 changed files with 7 additions and 1,271 deletions.
119 changes: 2 additions & 117 deletions zui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,11 @@ Saves the title to display in the UI, if not specified, a title will be generate

Saves the placeholder to display in the UI's field, if not specified, no placeholder will be displayed.


### `.displayAs<ComponentDefinition>({ id: string, params: object })`

Specifies the component to use for displaying the field, if not specified, the default component will be used.
The type of `params` comes from the component definition.


### `.hidden(condition?: boolean | (currentValue) => boolean | object)`

Hides/shows the component, the condition is optional, if `.hidden()` is called without a condition, the component will be hidden by default.
Expand Down Expand Up @@ -353,9 +351,10 @@ z.object({

### .toJsonSchema(options?: ToJsonSchemaOptions)

Converts the schema to a JSON schema, by default it targets 'openApi3'
Converts the schema to a JSON schema, by default it targets 'openApi3'

options can be passed to customize the output:

```ts
{
target: "openApi3" | "jsonSchema7" | undefined, // defaults to openApi3
Expand Down Expand Up @@ -1637,120 +1636,6 @@ myFunction.returnType()
* `args: ZodTuple` The first argument is a tuple (created with `z.tuple([...])` and defines the schema of the arguments to your function. If the function doesn't accept arguments, you can pass an empty tuple (`z.tuple([])`).
* `returnType: any Zod schema` The second argument is the function's return type. This can be any Zod schema. -->

## Template Literals

Building on the knowledge above, Zod supports creating typescript [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) with runtime validation. These types allow for stricter type checking of string inputs, as an alternative to `z.string()` which infers to a string.

A template literal type consists of [string literal types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) and interpolated positions (typescript types inside `${}` slots, e.g. `${number}`).

To create a template literal builder:

```ts
const templateLiteral = z.templateLiteral() // infers to ``.
```

- To add string literal types to an existing template literal:

```ts
templateLiteral.literal('Hello') // infers to `Hello`.
templateLiteral.literal(3.14) // infers to `3.14`.
```

This method accepts strings, numbers, booleans, nulls and undefined.

- To add interpolated positions to an existing template literal:

```ts
templateLiteral.interpolated(z.string()) // infers to `${string}`.
templateLiteral.interpolated(z.number()) // infers to `${number}`.
templateLiteral.interpolated(z.boolean()) // infers to `true` | `false`.
templateLiteral.interpolated(z.literal('foo')) // infers to `foo`.
templateLiteral.interpolated(z.null()) // infers to `null`.
templateLiteral.interpolated(z.undefined()) // infers to `undefined`.
templateLiteral.interpolated(z.bigint()) // infers to `${bigint}`.
templateLiteral.interpolated(z.any()) // infers to `${any}`.
```

Any Zod type (or union) with an underlying type of string, number, boolean, null,
undefined or bigint can be used as an interpolated position (template literals
included!). You can use additional built-in runtime validations (refinements
excluded) in each of these types and the template literal builder will do its
best (within the limitations of regular expressions) to support them when parsing.

### Examples

URL:

```ts
const url = z
.templateLiteral()
.literal('https://')
.interpolated(z.string().min(1))
.literal('.')
.interpolated(z.enum(['com', 'net']))
// infers to `https://${string}.com` | `https://${string}.net`.

url.parse('https://google.com') // passes
url.parse('https://google.net') // passes
url.parse('http://google.com') // throws
url.parse('https://.com') // throws
url.parse('https://google') // throws
url.parse('https://google.') // throws
url.parse('https://google.gov') // throws
```

Measurement:

```ts
const measurement = z.coerce
.templateLiteral()
.interpolated(z.number().finite())
.interpolated(z.enum(['px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax']).optional())
// infers to `${number}` | `${number}px` | `${number}em` | `${number}rem` | `${number}vh` | `${number}vw` | `${number}vmin` | `${number}vmax
```

MongoDB connection string:

```ts
const connectionString = z
.templateLiteral()
.literal('mongodb://')
.interpolated(
z
.templateLiteral()
.interpolated(z.string().regex(/\w+/).describe('username'))
.literal(':')
.interpolated(z.string().regex(/\w+/).describe('password'))
.literal('@')
.optional(),
)
.interpolated(z.string().regex(/\w+/).describe('host'))
.literal(':')
.interpolated(z.number().finite().int().positive().describe('port'))
.interpolated(
z
.templateLiteral()
.literal('/')
.interpolated(z.string().regex(/\w+/).optional().describe('defaultauthdb'))
.interpolated(
z
.templateLiteral()
.literal('?')
.interpolated(z.string().regex(/^\w+=\w+(&\w+=\w+)*$/))
.optional()
.describe('options'),
)
.optional(),
)
// infers to:
// | `mongodb://${string}:${number}`
// | `mongodb://${string}:${number}/${string}`
// | `mongodb://${string}:${number}/${string}?${string}`
// | `mongodb://${string}:${string}@${string}:${number}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`
```

## Preprocess

> Zod now supports primitive coercion without the need for `.preprocess()`. See the [coercion docs](#coercion-for-primitives) for more information.
Expand Down
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.12.1",
"version": "0.13.0",
"description": "A fork of Zod with additional features",
"type": "module",
"source": "./src/index.ts",
Expand Down
3 changes: 0 additions & 3 deletions zui/src/transforms/zui-to-json-schema/parseDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { Refs, Seen } from './Refs'
import { parseReadonlyDef } from './parsers/readonly'
import { zuiKey } from '../../ui/constants'
import { JsonSchema7RefType, parseRefDef } from './parsers/ref'
import * as errors from '../common/errors'

type JsonSchema7Meta = {
default?: any
Expand Down Expand Up @@ -204,8 +203,6 @@ const selectParser = (def: any, typeName: ZodFirstPartyTypeKind, refs: Refs): Js
return parseCatchDef(def, refs)
case ZodFirstPartyTypeKind.ZodPipeline:
return parsePipelineDef(def, refs)
case ZodFirstPartyTypeKind.ZodTemplateLiteral:
throw new errors.UnsupportedZuiToJsonSchemaError(ZodFirstPartyTypeKind.ZodTemplateLiteral)
case ZodFirstPartyTypeKind.ZodFunction:
case ZodFirstPartyTypeKind.ZodVoid:
case ZodFirstPartyTypeKind.ZodSymbol:
Expand Down
4 changes: 0 additions & 4 deletions zui/src/transforms/zui-to-typescript-schema/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,4 @@ describe('toTypescriptZuiString', () => {
const schema = `z.ref("#item")`
await assert(schema).toGenerateItself()
})
test('templateLiteral', async () => {
const schema = `z.templateLiteral()`
await assert(schema).toThrowErrorWhenGenerating()
})
})
3 changes: 0 additions & 3 deletions zui/src/transforms/zui-to-typescript-schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,6 @@ function sUnwrapZod(schema: z.Schema): string {
const uri = escapeString(def.uri)
return `${getMultilineComment(def.description)}z.ref(${uri})`.trim()

case z.ZodFirstPartyTypeKind.ZodTemplateLiteral:
throw new errors.UnsupportedZuiToTypescriptSchemaError(z.ZodFirstPartyTypeKind.ZodTemplateLiteral)

default:
util.assertNever(def)
}
Expand Down
18 changes: 0 additions & 18 deletions zui/src/transforms/zui-to-typescript-type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,24 +310,6 @@ ${value}`.trim()
case z.ZodFirstPartyTypeKind.ZodRef:
return toTypeArgumentName(def.uri)

case z.ZodFirstPartyTypeKind.ZodTemplateLiteral:
const inner = def.parts
.map((p) => {
if (typeof p === 'undefined' || p === null) {
return ''
}
if (typeof p === 'string') {
return p
}
if (typeof p === 'boolean' || typeof p === 'number') {
return `${p}`
}
return '${' + sUnwrapZod(p, { ...newConfig }) + '}'
})
.join('')

return `\`${inner}\``

default:
util.assertNever(def)
}
Expand Down
18 changes: 0 additions & 18 deletions zui/src/z/__tests__/coerce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,3 @@ test('date coercion', () => {
expect(() => schema.parse([])).toThrow() // z.ZodError
expect(schema.parse(new Date())).toBeInstanceOf(Date)
})

test('template literal coercion', () => {
const schema = z.coerce
.templateLiteral()
.interpolated(z.number().finite())
.interpolated(z.enum(['px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax']).optional())
expect(schema.parse(300)).toEqual('300')
expect(schema.parse(BigInt(300))).toEqual('300')
expect(schema.parse('300')).toEqual('300')
expect(schema.parse('300px')).toEqual('300px')
expect(schema.parse('300em')).toEqual('300em')
expect(schema.parse('300rem')).toEqual('300rem')
expect(schema.parse('300vh')).toEqual('300vh')
expect(schema.parse('300vw')).toEqual('300vw')
expect(schema.parse('300vmin')).toEqual('300vmin')
expect(schema.parse('300vmax')).toEqual('300vmax')
expect(schema.parse(['300px'])).toEqual('300px')
})
2 changes: 0 additions & 2 deletions zui/src/z/types/defs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ test('first party switch', () => {
break
case z.ZodFirstPartyTypeKind.ZodReadonly:
break
case z.ZodFirstPartyTypeKind.ZodTemplateLiteral:
break
default:
util.assertNever(def)
}
Expand Down
11 changes: 3 additions & 8 deletions zui/src/z/types/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import type {
ZodReadonlyDef,
ZodRecordDef,
ZodStringDef,
ZodTemplateLiteralDef,
ZodEffectsDef,
ZodTupleDef,
ZodUndefinedDef,
Expand All @@ -45,7 +44,6 @@ export type ZodDef =
| ZodNullDef
| ZodDefaultDef
| ZodCatchDef
| ZodTemplateLiteralDef
| ZodReadonlyDef
| ZodDiscriminatedUnionDef<any>
| ZodBrandedDef<any>
Expand Down Expand Up @@ -108,7 +106,6 @@ export enum ZodFirstPartyTypeKind {
ZodPromise = 'ZodPromise',
ZodBranded = 'ZodBranded',
ZodPipeline = 'ZodPipeline',
ZodTemplateLiteral = 'ZodTemplateLiteral',
ZodReadonly = 'ZodReadonly',
}

Expand Down Expand Up @@ -176,8 +173,6 @@ export type KindToDef<T extends ZodFirstPartyTypeKind> = T extends ZodFirstParty
? ZodBrandedDef<any>
: T extends ZodFirstPartyTypeKind.ZodPipeline
? ZodPipelineDef<any, any>
: T extends ZodFirstPartyTypeKind.ZodTemplateLiteral
? ZodTemplateLiteralDef
: T extends ZodFirstPartyTypeKind.ZodReadonly
? ZodReadonlyDef
: never
: T extends ZodFirstPartyTypeKind.ZodReadonly
? ZodReadonlyDef
: never
31 changes: 1 addition & 30 deletions zui/src/z/types/error/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { errorMap as defaultErrorMap } from './locales/en'
import { TypeOf, ZodType, ZodFirstPartyTypeKind, ZodParsedType, util, Primitive } from '../index'
import { TypeOf, ZodType, ZodParsedType, util, Primitive } from '../index'

type allKeys<T> = T extends any ? keyof T : never

Expand Down Expand Up @@ -325,35 +325,6 @@ export type ErrorMapCtx = {

export type ZodErrorMap = (issue: ZodIssueOptionalMessage, _ctx: ErrorMapCtx) => { message: string }

export class ZodTemplateLiteralUnsupportedTypeError extends Error {
constructor() {
super('Unsupported zod type!')

const actualProto = new.target.prototype
if (Object.setPrototypeOf) {
// eslint-disable-next-line ban/ban
Object.setPrototypeOf(this, actualProto)
} else {
;(this as any).__proto__ = actualProto
}
this.name = 'ZodTemplateLiteralUnsupportedTypeError'
}
}

export class ZodTemplateLiteralUnsupportedCheckError extends Error {
constructor(typeKind: ZodFirstPartyTypeKind, check: string) {
super(`${typeKind}'s "${check}" check is not supported in template literals!`)

const actualProto = new.target.prototype
if (Object.setPrototypeOf) {
// eslint-disable-next-line ban/ban
Object.setPrototypeOf(this, actualProto)
} else {
;(this as any).__proto__ = actualProto
}
this.name = 'ZodTemplateLiteralUnsupportedCheckError'
}
}
let overrideErrorMap = defaultErrorMap
export { defaultErrorMap }

Expand Down
1 change: 0 additions & 1 deletion zui/src/z/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export * from './ref'
export * from './set'
export * from './string'
export * from './symbol'
export * from './templateLiteral'
export * from './transformer'
export * from './tuple'
export * from './undefined'
Expand Down
Loading

0 comments on commit c27e6de

Please sign in to comment.