Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
blomqma committed Mar 19, 2024
1 parent 9c0dc41 commit 530e154
Show file tree
Hide file tree
Showing 23 changed files with 599 additions and 228 deletions.
48 changes: 27 additions & 21 deletions apps/example/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"version": "v5.1.11"
},
"paths": {
"/api/v1/form-data": {
"/api/v1/form-data/url-encoded": {
"post": {
"operationId": "formData",
"operationId": "urlEncodedFormData",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": { "$ref": "#/components/schemas/FormDataRequestBody" }
"schema": {
"$ref": "#/components/schemas/UrlEncodedFormDataRequestBody"
}
}
}
},
Expand All @@ -22,7 +24,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FormData200ResponseBody"
"$ref": "#/components/schemas/UrlEncodedFormData200ResponseBody"
}
}
}
Expand All @@ -36,7 +38,7 @@
}
}
},
"tags": ["example-api", "pages-router", "form-data"]
"tags": ["example-api", "pages-router", "form-data", "url-encoded"]
}
},
"/api/v1/route-with-query-params": {
Expand Down Expand Up @@ -398,23 +400,25 @@
"tags": ["example-api", "pages-router", "todos"]
}
},
"/api/v2/form-data": {
"/api/v2/form-data/url-encoded": {
"post": {
"operationId": "formData",
"operationId": "urlEncodedFormData",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": { "$ref": "#/components/schemas/FormDataRequestBody" }
"schema": {
"$ref": "#/components/schemas/UrlEncodedFormDataRequestBody"
}
}
}
},
"responses": {
"200": {
"description": "Response for status 200",
"content": {
"application/json": {
"application/octet-stream": {
"schema": {
"$ref": "#/components/schemas/FormData200ResponseBody"
"$ref": "#/components/schemas/UrlEncodedFormData200ResponseBody"
}
}
}
Expand All @@ -428,7 +432,7 @@
}
}
},
"tags": ["example-api", "app-router", "form-data"]
"tags": ["example-api", "app-router", "form-data", "url-encoded"]
}
},
"/api/v2/route-with-query-params": {
Expand Down Expand Up @@ -826,16 +830,6 @@
"required": ["message"],
"additionalProperties": false
},
"FormData200ResponseBody": {
"type": "object",
"properties": {
"foo": { "type": "string", "format": "uuid" },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"required": ["foo", "baz"],
"additionalProperties": false
},
"FormDataRequestBody": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -924,6 +918,18 @@
"type": "object",
"properties": { "message": { "type": "string" } },
"additionalProperties": false
},
"UrlEncodedFormData200ResponseBody": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
"additionalProperties": false
},
"UrlEncodedFormDataRequestBody": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
"additionalProperties": false
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions apps/example/src/app/api/v2/form-data/multipart/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { TypedNextResponse, route, routeOperation } from 'next-rest-framework';
import { zfd } from 'zod-form-data';
import { z } from 'zod';
import { jsonFileSchema } from '@/utils';

/*
* Example app router route handler with multipart form-data.
* Edge runtime is not supported for multipart/form-data.
* A zod-form-data schema is required for the input instead of a regular Zod schema.
*/
export const { POST } = route({
multipartFormData: routeOperation({
method: 'POST',
openApiOperation: {
tags: ['example-api', 'app-router', 'form-data', 'multipart']
}
})
.input({
contentType: 'multipart/form-data',
body: zfd.formData(
z.object({
text: z.string(),
file: jsonFileSchema
})
)
})
.outputs([
{
status: 200,
contentType: 'application/octet-stream',
schema: jsonFileSchema
}
])
.handler(async (req) => {
// const json = await req.json(); // Strongly typed parsed form data as JSON - multipart content like files are empty objects in the JSON but can be accessed from the form data below.
const formData = await req.formData(); // Strongly typed form data.
const file = formData.get('file');

// Type-checked response.
return new TypedNextResponse(file, {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${file.name}"`
}
});
})
});
46 changes: 0 additions & 46 deletions apps/example/src/app/api/v2/form-data/route.ts

This file was deleted.

42 changes: 42 additions & 0 deletions apps/example/src/app/api/v2/form-data/url-encoded/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TypedNextResponse, route, routeOperation } from 'next-rest-framework';
import { zfd } from 'zod-form-data';
import { z } from 'zod';

const schema = z.object({
text: z.string()
});

export const runtime = 'edge'; // Edge runtime is supported for application/x-www-form-urlencoded.

/*
* Example app router route handler with application/x-www-form-urlencoded form data.
* A zod-form-data schema is required for the input instead of a regular Zod schema.
*/
export const { POST } = route({
urlEncodedFormData: routeOperation({
method: 'POST',
openApiOperation: {
tags: ['example-api', 'app-router', 'form-data', 'url-encoded']
}
})
.input({
contentType: 'application/x-www-form-urlencoded',
body: zfd.formData(schema)
})
.outputs([
{
status: 200,
contentType: 'application/octet-stream',
schema
}
])
.handler(async (req) => {
const { text } = await req.json(); // Strongly typed parsed form data as JSON.
// const formData = await req.formData(); // Strongly typed form data.

// Type-checked response.
return TypedNextResponse.json({
text
});
})
});
43 changes: 0 additions & 43 deletions apps/example/src/pages/api/v1/form-data/index.ts

This file was deleted.

58 changes: 58 additions & 0 deletions apps/example/src/pages/api/v1/form-data/multipart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { jsonFileSchema } from '@/utils';
import { apiRoute, apiRouteOperation } from 'next-rest-framework';
import { z } from 'zod';
import { zfd } from 'zod-form-data';

const schema = z.object({
text: z.string(),
file: jsonFileSchema
});

// Body parser must be disabled when parsing multipart/form-data requests with pages router.
export const config = {
api: {
bodyParser: false
}
};

/*
* Example pages router route handler with multipart form data.
* A zod-form-data schema is required for the input instead of a regular Zod schema.
*/
export default apiRoute({
multipartFormData: apiRouteOperation({
method: 'POST',
openApiOperation: {
tags: ['example-api', 'pages-router', 'form-data', 'multipart']
}
})
.input({
contentType: 'multipart/form-data',
body: zfd.formData(schema)
})
.handler(async (req, res) => {
const formData = req.body; // Strongly typed form data.
const file = formData.get('file');
const reader = file.stream().getReader();
res.setHeader('Content-Type', 'application/octet-stream');

res.setHeader(
'Content-Disposition',
`attachment; filename="${file.name}"`
);

const pump = async () => {
await reader.read().then(async ({ done, value }) => {
if (done) {
res.end();
return;
}

res.write(value);
await pump();
});
};

await pump();
})
});
39 changes: 39 additions & 0 deletions apps/example/src/pages/api/v1/form-data/url-encoded/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { apiRoute, apiRouteOperation } from 'next-rest-framework';
import { z } from 'zod';
import { zfd } from 'zod-form-data';

const schema = z.object({
text: z.string()
});

/*
* Example pages router route handler with application/x-www-form-urlencoded form data form data.
* A zod-form-data schema is required for the input instead of a regular Zod schema.
*/
export default apiRoute({
urlEncodedFormData: apiRouteOperation({
method: 'POST',
openApiOperation: {
tags: ['example-api', 'pages-router', 'form-data', 'url-encoded']
}
})
.input({
contentType: 'application/x-www-form-urlencoded',
body: zfd.formData(schema)
})
.outputs([
{
status: 200,
contentType: 'application/json',
schema
}
])
.handler((req, res) => {
const formData = req.body; // Strongly typed form data.

// Type-checked response.
res.json({
text: formData.get('text')
});
})
});
Loading

0 comments on commit 530e154

Please sign in to comment.