Skip to content

Commit

Permalink
AP-2181 Checking type agains zod input (#7)
Browse files Browse the repository at this point in the history
* AP-2181 Fixing type to differentiate between zod input and output

* AP-2181 Client changes

* AP-2181 Adding tests

* AP-2181 Fixing lint

* AP-2181 FIxing any issues
  • Loading branch information
CarlosGamero authored Feb 9, 2024
1 parent 1b85671 commit aeb1d26
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 47 deletions.
83 changes: 82 additions & 1 deletion src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('frontend-http-client', () => {
`)
})

it('allows posting request with correct params even if queryParamsSchema is not provided', async () => {
it('allows posting request with correct params even if schemas are not provided', async () => {
const client = wretch(mockServer.url)

const testQueryParams = { param1: 'test', param2: 'test' }
Expand All @@ -174,6 +174,8 @@ describe('frontend-http-client', () => {
queryParams: testQueryParams,
queryParamsSchema: undefined,
responseBodySchema: responseSchema,
body: { id: 1 },
requestBodySchema: undefined,
})

expect(response).toEqual({ success: true })
Expand Down Expand Up @@ -240,6 +242,32 @@ describe('frontend-http-client', () => {

expect(response).toEqual({ success: true })
})

it('should check types against schema input type', async () => {
const client = wretch(mockServer.url)
await mockServer.forPost('/').thenJson(200, { success: true })

const schema = z.object({
numberAsText: z
.number()
.transform((val) => val.toString())
.pipe(z.string()),
})
const responseSchema = z.object({
success: z.boolean(),
})

const response = await sendPost(client, {
path: '/',
queryParams: { numberAsText: 1 },
queryParamsSchema: schema,
responseBodySchema: responseSchema,
body: { numberAsText: 1 },
requestBodySchema: schema,
})

expect(response).toEqual({ success: true })
})
})

describe('sendPut', () => {
Expand Down Expand Up @@ -719,6 +747,59 @@ describe('frontend-http-client', () => {

expect(response.data.code).toBe(99)
})

it('should work without specifying an schema', async () => {
const client = wretch(mockServer.url)

await mockServer.forGet('/').thenJson(200, { data: { code: 99 } })

const responseSchema = z.object({
data: z.object({
code: z.number(),
}),
})

const response = await sendGet(client, {
path: '/',
queryParams: {
requestCode: 99,
},
queryParamsSchema: undefined,
responseBodySchema: responseSchema,
})

expect(response.data.code).toBe(99)
})

it('should check types against schema input type', async () => {
const client = wretch(mockServer.url)
await mockServer.forGet('/').thenJson(200, { data: { code: 99 } })

const querySchema = z.object({
numberAsText: z
.number()
.transform((val) => val.toString())
.pipe(z.string()),
})
const responseSchema = z.object({
data: z.object({
code: z.number(),
}),
})

const responseBody = await sendGet(client, {
path: '/',
queryParams: { numberAsText: 1 },
queryParamsSchema: querySchema,
responseBodySchema: responseSchema,
})

expect(responseBody).toEqual({
data: {
code: 99,
},
})
})
})

describe('sendDelete', () => {
Expand Down
58 changes: 29 additions & 29 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { stringify } from 'fast-querystring'
import { type ZodSchema, type ZodError } from 'zod'
import type { z } from 'zod'

import { type Either, failure, success, isFailure } from './either.js'
import type {
CommonRequestParams,
NoQueryParams,
FreeQueryParams,
QueryParams,
ResourceChangeParams,
WretchInstance,
} from './types.js'

function parseRequestBody<RequestBody>({
function parseRequestBody<RequestBodySchema extends z.Schema>({
body,
requestBodySchema,
path,
}: {
body: RequestBody
requestBodySchema?: ZodSchema<RequestBody>
body: unknown
requestBodySchema?: RequestBodySchema
path: string
}): Either<ZodError, RequestBody> {
}): Either<z.ZodError, z.input<RequestBodySchema>> {
if (!body) {
return success(body)
}

if (!requestBodySchema) {
return success(body as RequestBody)
return success(body)
}

const result = requestBodySchema.safeParse(body)
Expand All @@ -41,15 +41,15 @@ function parseRequestBody<RequestBody>({
return success(body)
}

function parseQueryParams<RequestQueryParams>({
function parseQueryParams<RequestQuerySchema extends z.Schema>({
queryParams,
queryParamsSchema,
path,
}: {
queryParams: RequestQueryParams
queryParamsSchema?: ZodSchema<RequestQueryParams>
queryParams: unknown
queryParamsSchema?: RequestQuerySchema
path: string
}): Either<ZodError, string> {
}): Either<z.ZodError, string> {
if (!queryParams) {
return success('')
}
Expand Down Expand Up @@ -78,9 +78,9 @@ function parseResponseBody<ResponseBody>({
path,
}: {
response: ResponseBody
responseBodySchema?: ZodSchema<ResponseBody>
responseBodySchema?: z.ZodSchema<ResponseBody>
path: string
}): Either<ZodError, ResponseBody> {
}): Either<z.ZodError, ResponseBody> {
if (!responseBodySchema) {
return success(response)
}
Expand All @@ -103,12 +103,12 @@ function parseResponseBody<ResponseBody>({
async function sendResourceChange<
T extends WretchInstance,
ResponseBody,
RequestBody extends object | undefined = undefined,
RequestQueryParams extends object | undefined = undefined,
RequestBodySchema extends z.Schema | undefined = undefined,
RequestQuerySchema extends z.Schema | undefined = undefined,
>(
wretch: T,
method: 'post' | 'put' | 'patch',
params: ResourceChangeParams<RequestBody, ResponseBody, RequestQueryParams>,
params: ResourceChangeParams<RequestBodySchema, ResponseBody, RequestQuerySchema>,
) {
const body = parseRequestBody({
body: params.body,
Expand Down Expand Up @@ -158,12 +158,12 @@ async function sendResourceChange<
export async function sendGet<
T extends WretchInstance,
ResponseBody,
RequestQueryParams extends object | undefined = undefined,
RequestQuerySchema extends z.Schema | undefined = undefined,
>(
wretch: T,
params: RequestQueryParams extends undefined
? NoQueryParams<ResponseBody>
: QueryParams<RequestQueryParams, ResponseBody>,
params: RequestQuerySchema extends z.Schema
? QueryParams<RequestQuerySchema, ResponseBody>
: FreeQueryParams<ResponseBody>,
): Promise<ResponseBody> {
const queryParams = parseQueryParams({
queryParams: params.queryParams,
Expand Down Expand Up @@ -198,9 +198,9 @@ export async function sendGet<
export function sendPost<
T extends WretchInstance,
ResponseBody,
RequestBody extends object | undefined = undefined,
RequestQueryParams extends object | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBody, ResponseBody, RequestQueryParams>) {
RequestBodySchema extends z.Schema | undefined = undefined,
RequestQuerySchema extends z.Schema | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBodySchema, ResponseBody, RequestQuerySchema>) {
return sendResourceChange(wretch, 'post', params)
}

Expand All @@ -209,9 +209,9 @@ export function sendPost<
export function sendPut<
T extends WretchInstance,
ResponseBody,
RequestBody extends object | undefined = undefined,
RequestQueryParams extends object | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBody, ResponseBody, RequestQueryParams>) {
RequestBodySchema extends z.Schema | undefined = undefined,
RequestQuerySchema extends z.Schema | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBodySchema, ResponseBody, RequestQuerySchema>) {
return sendResourceChange(wretch, 'put', params)
}

Expand All @@ -220,9 +220,9 @@ export function sendPut<
export function sendPatch<
T extends WretchInstance,
ResponseBody,
RequestBody extends object | undefined = undefined,
RequestQueryParams extends object | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBody, ResponseBody, RequestQueryParams>) {
RequestBodySchema extends z.Schema | undefined = undefined,
RequestQuerySchema extends z.Schema | undefined = undefined,
>(wretch: T, params: ResourceChangeParams<RequestBodySchema, ResponseBody, RequestQuerySchema>) {
return sendResourceChange(wretch, 'patch', params)
}

Expand Down
36 changes: 19 additions & 17 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import type { Wretch } from 'wretch'
import type { ZodSchema } from 'zod'
import type { ZodSchema, z } from 'zod'

type FreeformRecord = Record<string, unknown>

export type CommonRequestParams<ResponseBody> = {
path: string
responseBodySchema?: ZodSchema<ResponseBody>
}

export type BodyRequestParams<RequestBody extends object, ResponseBody> = {
body: RequestBody | undefined
requestBodySchema: ZodSchema<RequestBody> | undefined
export type BodyRequestParams<RequestBodySchema extends z.ZodSchema, ResponseBody> = {
body: z.input<RequestBodySchema> | undefined
requestBodySchema: RequestBodySchema | undefined
} & CommonRequestParams<ResponseBody>

export type NoBodyRequestParams<ResponseBody> = {
body?: never
export type FreeBodyRequestParams<ResponseBody> = {
body?: FreeformRecord
requestBodySchema?: never
} & CommonRequestParams<ResponseBody>

export type QueryParams<RequestQueryParams extends object | undefined, ResponseBody> = {
queryParams: RequestQueryParams | undefined
queryParamsSchema: ZodSchema<RequestQueryParams> | undefined
export type QueryParams<RequestQuerySchema extends z.ZodSchema, ResponseBody> = {
queryParams: z.input<RequestQuerySchema> | undefined
queryParamsSchema: RequestQuerySchema | undefined
} & CommonRequestParams<ResponseBody>

export type NoQueryParams<ResponseBody> = {
queryParams?: never
export type FreeQueryParams<ResponseBody> = {
queryParams?: FreeformRecord
queryParamsSchema?: never
} & CommonRequestParams<ResponseBody>

export type ResourceChangeParams<
RequestBody,
ResponseBody,
RequestQueryParams extends object | undefined = undefined,
> = (RequestBody extends object
RequestQuerySchema extends z.Schema | undefined = undefined,
> = (RequestBody extends z.Schema
? BodyRequestParams<RequestBody, ResponseBody>
: NoBodyRequestParams<ResponseBody>) &
(RequestQueryParams extends undefined
? NoQueryParams<ResponseBody>
: QueryParams<RequestQueryParams, ResponseBody>)
: FreeBodyRequestParams<ResponseBody>) &
(RequestQuerySchema extends z.Schema
? QueryParams<RequestQuerySchema, ResponseBody>
: FreeQueryParams<ResponseBody>)

// We don't know which addons Wretch will have, and we don't really care, hence any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down

0 comments on commit aeb1d26

Please sign in to comment.