Skip to content

Commit

Permalink
Improve path parameter validation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
blomqma committed Apr 13, 2024
1 parent 586205f commit 8fb1816
Show file tree
Hide file tree
Showing 23 changed files with 617 additions and 244 deletions.
238 changes: 162 additions & 76 deletions apps/example/public/openapi.json

Large diffs are not rendered by default.

58 changes: 44 additions & 14 deletions apps/example/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { z } from 'zod';
export const getTodos = rpcOperation()
.outputs([
{
body: z.array(todoSchema),
body: z.array(todoSchema).describe('List of TODOs.'),
contentType: 'application/json'
}
])
Expand All @@ -25,17 +25,19 @@ export const getTodos = rpcOperation()
export const getTodoById = rpcOperation()
.input({
contentType: 'application/json',
body: z.string()
body: z.string().describe('TODO name.')
})
.outputs([
{
body: z.object({
error: z.string()
}),
body: z
.object({
error: z.string()
})
.describe('TODO not found.'),
contentType: 'application/json'
},
{
body: todoSchema,
body: todoSchema.describe('TODO response.'),
contentType: 'application/json'
}
])
Expand All @@ -52,9 +54,11 @@ export const getTodoById = rpcOperation()
export const createTodo = rpcOperation()
.input({
contentType: 'application/json',
body: z.object({
name: z.string()
})
body: z
.object({
name: z.string()
})
.describe("New TODO's name.")
})
.outputs([{ body: todoSchema, contentType: 'application/json' }])
.handler(async ({ name }) => {
Expand All @@ -68,8 +72,14 @@ export const deleteTodo = rpcOperation()
body: z.string()
})
.outputs([
{ body: z.object({ error: z.string() }), contentType: 'application/json' },
{ body: z.object({ message: z.string() }), contentType: 'application/json' }
{
body: z.object({ error: z.string() }).describe('TODO not found.'),
contentType: 'application/json'
},
{
body: z.object({ message: z.string() }).describe('TODO deleted message.'),
contentType: 'application/json'
}
])
.handler((id) => {
const todo = MOCK_TODOS.find((t) => t.id === Number(id));
Expand All @@ -86,9 +96,14 @@ export const deleteTodo = rpcOperation()
export const formDataUrlEncoded = rpcOperation()
.input({
contentType: 'application/x-www-form-urlencoded',
body: formSchema // A zod-form-data schema is required.
body: formSchema.describe('Test form description.') // A zod-form-data schema is required.
})
.outputs([{ body: formSchema, contentType: 'application/json' }])
.outputs([
{
body: formSchema.describe('Test form response.'),
contentType: 'application/json'
}
])
.handler((formData) => {
return {
text: formData.get('text')
Expand All @@ -98,13 +113,28 @@ export const formDataUrlEncoded = rpcOperation()
export const formDataMultipart = rpcOperation()
.input({
contentType: 'multipart/form-data',
body: multipartFormSchema // A zod-form-data schema is required.
body: multipartFormSchema, // A zod-form-data schema is required.
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'Test form description.',
type: 'object',
properties: {
text: {
type: 'string'
},
file: {
type: 'string',
format: 'binary'
}
}
}
})
.outputs([
{
body: z.custom<File>(),
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'File response.',
type: 'string',
format: 'binary'
},
Expand Down
4 changes: 3 additions & 1 deletion apps/example/src/app/api/v2/form-data/multipart/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const { POST } = route({
body: multipartFormSchema, // A zod-form-data schema is required.
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'Test form description.',
type: 'object',
properties: {
text: {
Expand All @@ -29,9 +30,10 @@ export const { POST } = route({
{
status: 200,
contentType: 'application/octet-stream',
body: z.unknown(),
body: z.custom<File>(),
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'File response.',
type: 'string',
format: 'binary'
}
Expand Down
4 changes: 2 additions & 2 deletions apps/example/src/app/api/v2/form-data/url-encoded/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export const { POST } = route({
})
.input({
contentType: 'application/x-www-form-urlencoded',
body: formSchema // A zod-form-data schema is required.
body: formSchema.describe('Test form description.') // A zod-form-data schema is required.
})
.outputs([
{
status: 200,
contentType: 'application/octet-stream',
body: formSchema
body: formSchema.describe('Test form response.') // A zod-form-data schema is required.
}
])
.handler(async (req) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
import { TypedNextResponse, route, routeOperation } from 'next-rest-framework';
import { z } from 'zod';

const paramsSchema = z.object({
slug: z.enum(['foo', 'bar', 'baz'])
});

const querySchema = z.object({
total: z.string()
});

export const runtime = 'edge';

export const { GET } = route({
getQueryParams: routeOperation({
getPathParams: routeOperation({
method: 'GET'
})
.input({
contentType: 'application/json',
query: querySchema
params: paramsSchema.describe('Path parameters input.'),
query: querySchema.describe('Query parameters input.')
})
.outputs([
{
status: 200,
contentType: 'application/json',
body: querySchema
body: paramsSchema.merge(querySchema).describe('Parameters response.')
}
])
.handler((req) => {
.handler((req, { params: { slug } }) => {
const query = req.nextUrl.searchParams;

return TypedNextResponse.json({
slug,
total: query.get('total') ?? ''
});
})
Expand Down
30 changes: 0 additions & 30 deletions apps/example/src/app/api/v2/route-with-path-params/[slug]/route.ts

This file was deleted.

20 changes: 16 additions & 4 deletions apps/example/src/app/api/v2/todos/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ import { z } from 'zod';

export const runtime = 'edge';

const paramsSchema = z
.object({
id: z.string()
})
.describe('TODO ID path parameter.');

export const { GET, DELETE } = route({
getTodoById: routeOperation({
method: 'GET'
})
.input({
params: paramsSchema
})
.outputs([
{
body: todoSchema,
body: todoSchema.describe('TODO response.'),
status: 200,
contentType: 'application/json'
},
{
body: z.string(),
body: z.string().describe('TODO not found.'),
status: 404,
contentType: 'application/json'
}
Expand All @@ -37,14 +46,17 @@ export const { GET, DELETE } = route({
deleteTodo: routeOperation({
method: 'DELETE'
})
.input({
params: paramsSchema
})
.outputs([
{
body: z.string(),
body: z.string().describe('TODO deleted.'),
status: 204,
contentType: 'application/json'
},
{
body: z.string(),
body: z.string().describe('TODO not found.'),
status: 404,
contentType: 'application/json'
}
Expand Down
16 changes: 9 additions & 7 deletions apps/example/src/app/api/v2/todos/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const { GET, POST } = route({
{
status: 200,
contentType: 'application/json',
body: z.array(todoSchema)
body: z.array(todoSchema).describe('List of TODOs.')
}
])
.handler(() => {
Expand All @@ -26,26 +26,28 @@ export const { GET, POST } = route({
})
.input({
contentType: 'application/json',
body: z.object({
name: z.string()
})
body: z
.object({
name: z.string()
})
.describe("New TODO's name.")
})
.outputs([
{
status: 201,
contentType: 'application/json',
body: z.string()
body: z.string().describe('New TODO created message.')
},
{
status: 401,
contentType: 'application/json',
body: z.string()
body: z.string().describe('Unauthorized.')
}
])
// Optional middleware logic executed before request validation.
.middleware((req) => {
if (!req.headers.get('very-secure')) {
return TypedNextResponse.json('Unauthorized', {
return TypedNextResponse.json('Unauthorized.', {
status: 401
});
}
Expand Down
4 changes: 3 additions & 1 deletion apps/example/src/pages/api/v1/form-data/multipart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default apiRoute({
body: multipartFormSchema, // A zod-form-data schema is required.
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'Test form description.',
type: 'object',
properties: {
text: {
Expand All @@ -34,9 +35,10 @@ export default apiRoute({
{
status: 200,
contentType: 'application/octet-stream',
body: z.unknown(),
body: z.custom<File>(),
// The binary file cannot described with a Zod schema so we define it by hand for the OpenAPI spec.
bodySchema: {
description: 'File response.',
type: 'string',
format: 'binary'
}
Expand Down
4 changes: 2 additions & 2 deletions apps/example/src/pages/api/v1/form-data/url-encoded/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ export default apiRoute({
})
.input({
contentType: 'application/x-www-form-urlencoded',
body: formSchema // A zod-form-data schema is required.
body: formSchema.describe('Test form description.') // A zod-form-data schema is required.
})
.outputs([
{
status: 200,
contentType: 'application/json',
body: formSchema
body: formSchema.describe('Test form response.')
}
])
.handler((req, res) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ const paramsSchema = z.object({
slug: z.enum(['foo', 'bar', 'baz'])
});

const querySchema = z.object({
total: z.string()
});

export default apiRoute({
getQueryParams: apiRouteOperation({
getParams: apiRouteOperation({
method: 'GET'
})
.input({
contentType: 'application/json',
query: paramsSchema
params: paramsSchema.describe('Path parameters input.'),
query: querySchema.describe('Query parameters input.')
})
.outputs([
{
status: 200,
contentType: 'application/json',
body: paramsSchema
body: paramsSchema.merge(querySchema).describe('Parameters response.')
}
])
.handler((req, res) => {
Expand Down
Loading

0 comments on commit 8fb1816

Please sign in to comment.