Skip to content

Commit

Permalink
feat: type inference support for data argument (#130)
Browse files Browse the repository at this point in the history
Co-authored-by: Gabriel Massadas <5445926+G4brym@users.noreply.github.com>
  • Loading branch information
iann838 and G4brym committed Mar 18, 2024
1 parent 1e801cb commit c6a7e88
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 75 deletions.
21 changes: 12 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"package": "npm run build && npm pack",
"test:cov": "jest --coverage --no-cache --runInBand",
"addscope": "node config/packagejson name @cloudflare/itty-router-openapi",
"prettify": "prettier --check src tests README.md || (prettier -w src tests README.md; exit 1)",
"prettify": "prettier --check src tests README.md || (prettier -w src tests README.md)",
"lint": "npm run prettify",
"prepare": "husky install",
"test": "jest --no-cache --runInBand --config jestconfig.json --verbose",
Expand Down Expand Up @@ -84,7 +84,7 @@
"jest": "^29.0.0",
"jest-openapi": "^0.14.2",
"pinst": "^2.1.6",
"prettier": "^2.4.0",
"prettier": "^3.1.0",
"rollup": "^3.25.1",
"rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-copy": "^3.4.0",
Expand Down
15 changes: 7 additions & 8 deletions src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import yaml from 'js-yaml'
export type Route = <
RequestType = IRequest,
Args extends any[] = any[],
RT = OpenAPIRouterType
RT = OpenAPIRouterType,
>(
path: string,
...handlers: (RouteHandler<RequestType, Args> | OpenAPIRouterType | any)[] // TODO: fix this any to be instance of OpenAPIRoute
Expand All @@ -34,18 +34,17 @@ export type OpenAPIRouterType<R = Route, Args extends any[] = any[]> = {
} & RouterType<R>

// helper function to detect equality in types (used to detect custom Request on router)
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false

export function OpenAPIRouter<
RequestType = IRequest,
Args extends any[] = any[],
RouteType = Equal<RequestType, IRequest> extends true
? Route
: UniversalRoute<RequestType, Args>
: UniversalRoute<RequestType, Args>,
>(options?: RouterOptions): OpenAPIRouterType<RouteType, Args> {
const registry: OpenAPIRegistryMerger = new OpenAPIRegistryMerger()

Expand Down Expand Up @@ -86,7 +85,7 @@ export function OpenAPIRouter<
return (
route: string,
...handlers: RouteHandler<RequestType, Args>[] &
typeof OpenAPIRoute[] &
(typeof OpenAPIRoute)[] &
OpenAPIRouterType<RouteType, Args>[]
) => {
if (prop !== 'handle') {
Expand Down
139 changes: 102 additions & 37 deletions src/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import {
ParameterLocation,
ParameterType,
RegexParameterType,
RouteParameter,
HeaderParameter,
QueryParameter,
PathParameter,
StringParameterType,
LegacyParameter,
} from './types'
import { z, ZodObject } from 'zod'
import { z } from 'zod'
import { isSpecificZodType, legacyTypeIntoZod } from './zod/utils'
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'
import { ZodType } from 'zod'

if (z.string().openapi === undefined) {
// console.log('zod extension applied')
extendZodWithOpenApi(z)
}

export function convertParams(field: any, params: any): ZodType {
export function convertParams(field: any, params: any): z.ZodType {
params = params || {}
if (params.required === false)
// @ts-ignore
Expand Down Expand Up @@ -60,7 +62,7 @@ export class Obj {
}
}

export class Num {
export const Num: LegacyParameter<z.ZodNumber> = class Num {
static generator = true

constructor(params?: ParameterType) {
Expand All @@ -71,9 +73,9 @@ export class Num {
type: 'number',
})
}
}
} as unknown as LegacyParameter<z.ZodNumber>

export class Int {
export const Int: LegacyParameter<z.ZodNumber> = class Int {
static generator = true

constructor(params?: ParameterType) {
Expand All @@ -84,17 +86,17 @@ export class Int {
type: 'integer',
})
}
}
} as unknown as LegacyParameter<z.ZodNumber>

export class Str {
export const Str: LegacyParameter<z.ZodString> = class Str {
static generator = true

constructor(params?: StringParameterType) {
return convertParams(z.string(), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class DateTime {
export const DateTime: LegacyParameter<z.ZodString> = class DateTime {
static generator = true

constructor(params?: ParameterType) {
Expand All @@ -105,9 +107,9 @@ export class DateTime {
params
)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Regex {
export const Regex: LegacyParameter<z.ZodString> = class Regex {
static generator = true

constructor(params: RegexParameterType) {
Expand All @@ -117,25 +119,25 @@ export class Regex {
params
)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Email {
export const Email: LegacyParameter<z.ZodString> = class Email {
static generator = true

constructor(params?: ParameterType) {
return convertParams(z.string().email(), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Uuid {
export const Uuid: LegacyParameter<z.ZodString> = class Uuid {
static generator = true

constructor(params?: ParameterType) {
return convertParams(z.string().uuid(), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Hostname {
export const Hostname: LegacyParameter<z.ZodString> = class Hostname {
static generator = true

constructor(params?: ParameterType) {
Expand All @@ -148,33 +150,33 @@ export class Hostname {
params
)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Ipv4 {
export const Ipv4: LegacyParameter<z.ZodString> = class Ipv4 {
static generator = true

constructor(params?: ParameterType) {
return convertParams(z.coerce.string().ip({ version: 'v4' }), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Ipv6 {
export const Ipv6: LegacyParameter<z.ZodString> = class Ipv6 {
static generator = true

constructor(params?: ParameterType) {
return convertParams(z.string().ip({ version: 'v6' }), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class DateOnly {
export const DateOnly: LegacyParameter<z.ZodString> = class DateOnly {
static generator = true

constructor(params?: ParameterType) {
return convertParams(z.coerce.date(), params)
}
}
} as unknown as LegacyParameter<z.ZodString>

export class Bool {
export const Bool: LegacyParameter<z.ZodBoolean> = class Bool {
static generator = true

constructor(params?: ParameterType) {
Expand All @@ -188,7 +190,7 @@ export class Bool {
type: 'boolean',
})
}
}
} as unknown as LegacyParameter<z.ZodBoolean>

export class Enumeration {
static generator = true
Expand All @@ -202,7 +204,7 @@ export class Enumeration {

const originalKeys: [string, ...string[]] = Object.keys(values) as [
string,
...string[]
...string[],
]

if (params.enumCaseSensitive === false) {
Expand All @@ -215,7 +217,7 @@ export class Enumeration {

const keys: [string, ...string[]] = Object.keys(values) as [
string,
...string[]
...string[],
]

let field
Expand All @@ -239,32 +241,95 @@ export class Enumeration {
}
}

export function Query<Z extends z.ZodType>(type: Z): QueryParameter<Z>
export function Query<Z extends z.ZodType>(
type: Z,
params: ParameterLocation & { required: false }
): QueryParameter<z.ZodOptional<Z>>
export function Query<Z extends z.ZodType>(
type: Z,
params: ParameterLocation
): QueryParameter<Z>
export function Query<Z extends z.ZodType>(
type: [Z]
): QueryParameter<z.ZodArray<Z>>
export function Query<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation & { required: false }
): QueryParameter<z.ZodOptional<z.ZodArray<Z>>>
export function Query<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation
): QueryParameter<z.ZodArray<Z>>
export function Query(type: any): QueryParameter
export function Query(type: any, params: ParameterLocation): QueryParameter
export function Query(
type: any,
params: ParameterLocation = {}
): RouteParameter {
): QueryParameter {
return {
name: params.name,
location: 'query',
type: legacyTypeIntoZod(type, params),
}
}

export function Path(
type: any,
params: ParameterLocation = {}
): RouteParameter {
export function Path<Z extends z.ZodType>(type: Z): PathParameter<Z>
export function Path<Z extends z.ZodType>(
type: Z,
params: ParameterLocation & { required: false }
): PathParameter<z.ZodOptional<Z>>
export function Path<Z extends z.ZodType>(
type: Z,
params: ParameterLocation
): PathParameter<Z>
export function Path<Z extends z.ZodType>(
type: [Z]
): PathParameter<z.ZodArray<Z>>
export function Path<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation & { required: false }
): PathParameter<z.ZodOptional<z.ZodArray<Z>>>
export function Path<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation
): PathParameter<z.ZodArray<Z>>
export function Path(type: any): PathParameter
export function Path(type: any, params: ParameterLocation): PathParameter
export function Path(type: any, params: ParameterLocation = {}): PathParameter {
return {
name: params.name,
location: 'params',
type: legacyTypeIntoZod(type, params),
}
}

export function Header<Z extends z.ZodType>(type: Z): HeaderParameter<Z>
export function Header<Z extends z.ZodType>(
type: Z,
params: ParameterLocation & { required: false }
): HeaderParameter<z.ZodOptional<Z>>
export function Header<Z extends z.ZodType>(
type: Z,
params: ParameterLocation
): HeaderParameter<Z>
export function Header<Z extends z.ZodType>(
type: [Z]
): HeaderParameter<z.ZodArray<Z>>
export function Header<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation & { required: false }
): HeaderParameter<z.ZodOptional<z.ZodArray<Z>>>
export function Header<Z extends z.ZodType>(
type: [Z],
params: ParameterLocation
): HeaderParameter<z.ZodArray<Z>>
export function Header(type: any): HeaderParameter
export function Header(type: any, params: ParameterLocation): HeaderParameter
export function Header(
type: any,
params: ParameterLocation = {}
): RouteParameter {
): HeaderParameter {
return {
name: params.name,
location: 'headers',
Expand Down Expand Up @@ -296,7 +361,7 @@ export function extractParameter(

export function extractQueryParameters(
request: Request,
schema?: ZodObject<any>
schema?: z.ZodObject<any>
): Record<string, any> | null {
const { searchParams } = new URL(request.url)

Expand Down
Loading

0 comments on commit c6a7e88

Please sign in to comment.