From c7f97941fb90f02fad704ed6e36cbb5fdb00da0a Mon Sep 17 00:00:00 2001 From: Gabriel Massadas Date: Thu, 20 Jun 2024 14:39:14 +0100 Subject: [PATCH] Update c3 openapi template to use chanfana and Hono --- .changeset/metal-seahorses-occur.md | 6 + .../templates/openapi/ts/README.md | 6 +- .../templates/openapi/ts/package.json | 4 +- .../openapi/ts/src/endpoints/taskCreate.ts | 42 ++-- .../openapi/ts/src/endpoints/taskDelete.ts | 39 ++-- .../openapi/ts/src/endpoints/taskFetch.ts | 53 ++--- .../openapi/ts/src/endpoints/taskList.ts | 51 ++--- .../templates/openapi/ts/src/index.ts | 34 ++-- .../templates/openapi/ts/src/types.ts | 17 +- .../functions/utils/repoAllowlist.ts | 2 +- templates/worker-openapi/.gitignore | 171 +++++++++++++++- templates/worker-openapi/README.md | 42 ++-- templates/worker-openapi/package.json | 21 +- .../src/endpoints/taskCreate.ts | 58 ++++++ .../src/endpoints/taskDelete.ts | 56 ++++++ .../worker-openapi/src/endpoints/taskFetch.ts | 81 ++++++++ .../worker-openapi/src/endpoints/taskList.ts | 69 +++++++ templates/worker-openapi/src/index.ts | 43 ++-- templates/worker-openapi/src/tasks.ts | 188 ------------------ templates/worker-openapi/src/types.ts | 10 + templates/worker-openapi/tsconfig.json | 32 ++- .../worker-openapi/worker-configuration.d.ts | 3 + templates/worker-openapi/wrangler.toml | 2 +- 23 files changed, 660 insertions(+), 370 deletions(-) create mode 100644 .changeset/metal-seahorses-occur.md create mode 100644 templates/worker-openapi/src/endpoints/taskCreate.ts create mode 100644 templates/worker-openapi/src/endpoints/taskDelete.ts create mode 100644 templates/worker-openapi/src/endpoints/taskFetch.ts create mode 100644 templates/worker-openapi/src/endpoints/taskList.ts delete mode 100644 templates/worker-openapi/src/tasks.ts create mode 100644 templates/worker-openapi/src/types.ts create mode 100644 templates/worker-openapi/worker-configuration.d.ts diff --git a/.changeset/metal-seahorses-occur.md b/.changeset/metal-seahorses-occur.md new file mode 100644 index 000000000000..f4a420652ceb --- /dev/null +++ b/.changeset/metal-seahorses-occur.md @@ -0,0 +1,6 @@ +--- +"create-cloudflare": minor +"@cloudflare/prerelease-registry": patch +--- + +Update c3 openapi template to use chanfana and Hono diff --git a/packages/create-cloudflare/templates/openapi/ts/README.md b/packages/create-cloudflare/templates/openapi/ts/README.md index 8263f12c5409..2aedc3bb1331 100644 --- a/packages/create-cloudflare/templates/openapi/ts/README.md +++ b/packages/create-cloudflare/templates/openapi/ts/README.md @@ -1,6 +1,6 @@ # Cloudflare Workers OpenAPI 3.1 -This is a Cloudflare Worker with OpenAPI 3.1 using [itty-router-openapi](https://github.com/cloudflare/itty-router-openapi). +This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono). This is an example project made to be used as a quick start into building OpenAPI compliant Workers that generates the `openapi.json` schema automatically from code and validates the incoming request to the defined parameters or request body. @@ -16,10 +16,10 @@ This is an example project made to be used as a quick start into building OpenAP 1. Your main router is defined in `src/index.ts`. 2. Each endpoint has its own file in `src/endpoints/`. -3. For more information read the [itty-router-openapi official documentation](https://cloudflare.github.io/itty-router-openapi/). +3. For more information read the [chanfana documentation](https://chanfana.pages.dev/) and [Hono documentation](https://hono.dev/docs). ## Development 1. Run `wrangler dev` to start a local instance of the API. -2. Open `http://localhost:9000/` in your browser to see the Swagger interface where you can try the endpoints. +2. Open `http://localhost:8787/` in your browser to see the Swagger interface where you can try the endpoints. 3. Changes made in the `src/` folder will automatically trigger the server to reload, you only need to refresh the Swagger interface. diff --git a/packages/create-cloudflare/templates/openapi/ts/package.json b/packages/create-cloudflare/templates/openapi/ts/package.json index 662ac025a73e..7cf58f5df31d 100644 --- a/packages/create-cloudflare/templates/openapi/ts/package.json +++ b/packages/create-cloudflare/templates/openapi/ts/package.json @@ -9,7 +9,9 @@ "cf-typegen": "wrangler types" }, "dependencies": { - "@cloudflare/itty-router-openapi": "^1.0.1" + "chanfana": "^2.0.2", + "zod": "^3.23.8", + "hono": "^4.4.7" }, "devDependencies": { "@types/node": "20.8.3", diff --git a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskCreate.ts b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskCreate.ts index 2acca12b5c7a..798aa82ceddb 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskCreate.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskCreate.ts @@ -1,33 +1,43 @@ -import { - OpenAPIRoute, - OpenAPIRouteSchema, -} from "@cloudflare/itty-router-openapi"; +import { Bool, OpenAPIRoute } from "chanfana"; +import { z } from "zod"; import { Task } from "../types"; export class TaskCreate extends OpenAPIRoute { - static schema: OpenAPIRouteSchema = { + schema = { tags: ["Tasks"], summary: "Create a new Task", - requestBody: Task, + request: { + body: { + content: { + "application/json": { + schema: Task, + }, + }, + }, + }, responses: { "200": { description: "Returns the created task", - schema: { - success: Boolean, - result: { - task: Task, + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), }, }, }, }, }; - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + // Retrieve the validated request body const taskToCreate = data.body; diff --git a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskDelete.ts b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskDelete.ts index 0a8c41a8eb64..ed6f691e78eb 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskDelete.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskDelete.ts @@ -1,38 +1,39 @@ -import { - OpenAPIRoute, - OpenAPIRouteSchema, - Path, -} from "@cloudflare/itty-router-openapi"; +import { Bool, OpenAPIRoute, Str } from "chanfana"; +import { z } from "zod"; import { Task } from "../types"; export class TaskDelete extends OpenAPIRoute { - static schema: OpenAPIRouteSchema = { + schema = { tags: ["Tasks"], summary: "Delete a Task", - parameters: { - taskSlug: Path(String, { - description: "Task slug", + request: { + params: z.object({ + taskSlug: Str({ description: "Task slug" }), }), }, responses: { "200": { description: "Returns if the task was deleted successfully", - schema: { - success: Boolean, - result: { - task: Task, + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), }, }, }, }, }; - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + // Retrieve the validated slug const { taskSlug } = data.params; diff --git a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskFetch.ts b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskFetch.ts index fa081c081b8a..07cf324e856e 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskFetch.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskFetch.ts @@ -1,45 +1,52 @@ -import { - OpenAPIRoute, - OpenAPIRouteSchema, - Path, -} from "@cloudflare/itty-router-openapi"; +import { Bool, OpenAPIRoute, Str } from "chanfana"; +import { z } from "zod"; import { Task } from "../types"; export class TaskFetch extends OpenAPIRoute { - static schema: OpenAPIRouteSchema = { + schema = { tags: ["Tasks"], summary: "Get a single Task by slug", - parameters: { - taskSlug: Path(String, { - description: "Task slug", + request: { + params: z.object({ + taskSlug: Str({ description: "Task slug" }), }), }, responses: { "200": { description: "Returns a single task if found", - schema: { - success: Boolean, - result: { - task: Task, + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), }, }, }, "404": { description: "Task not found", - schema: { - success: Boolean, - error: String, + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + error: Str(), + }), + }), + }, }, }, }, }; - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + // Retrieve the validated slug const { taskSlug } = data.params; @@ -56,7 +63,7 @@ export class TaskFetch extends OpenAPIRoute { }, { status: 404, - } + }, ); } diff --git a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskList.ts b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskList.ts index 7aa074377bfa..8f50506e085d 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskList.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/endpoints/taskList.ts @@ -1,43 +1,46 @@ -import { - OpenAPIRoute, - OpenAPIRouteSchema, - Query, -} from "@cloudflare/itty-router-openapi"; +import { Bool, Num, OpenAPIRoute } from "chanfana"; +import { z } from "zod"; import { Task } from "../types"; export class TaskList extends OpenAPIRoute { - static schema: OpenAPIRouteSchema = { + schema = { tags: ["Tasks"], summary: "List Tasks", - parameters: { - page: Query(Number, { - description: "Page number", - default: 0, - }), - isCompleted: Query(Boolean, { - description: "Filter by completed flag", - required: false, + request: { + query: z.object({ + page: Num({ + description: "Page number", + default: 0, + }), + isCompleted: Bool({ + description: "Filter by completed flag", + required: false, + }), }), }, responses: { "200": { description: "Returns a list of tasks", - schema: { - success: Boolean, - result: { - tasks: [Task], + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + tasks: Task.array(), + }), + }), + }), }, }, }, }, }; - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + // Retrieve the validated parameters const { page, isCompleted } = data.query; diff --git a/packages/create-cloudflare/templates/openapi/ts/src/index.ts b/packages/create-cloudflare/templates/openapi/ts/src/index.ts index a3f2f4dec6bc..46aee732be23 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/index.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/index.ts @@ -1,29 +1,23 @@ -import { OpenAPIRouter } from "@cloudflare/itty-router-openapi"; +import { fromHono } from "chanfana"; +import { Hono } from "hono"; import { TaskCreate } from "./endpoints/taskCreate"; import { TaskDelete } from "./endpoints/taskDelete"; import { TaskFetch } from "./endpoints/taskFetch"; import { TaskList } from "./endpoints/taskList"; -export const router = OpenAPIRouter({ +// Start a Hono app +const app = new Hono(); + +// Setup OpenAPI registry +const openapi = fromHono(app, { docs_url: "/", }); -router.get("/api/tasks/", TaskList); -router.post("/api/tasks/", TaskCreate); -router.get("/api/tasks/:taskSlug/", TaskFetch); -router.delete("/api/tasks/:taskSlug/", TaskDelete); - -// 404 for everything else -router.all("*", () => - Response.json( - { - success: false, - error: "Route not found", - }, - { status: 404 } - ) -); +// Register OpenAPI endpoints +openapi.get("/api/tasks", TaskList); +openapi.post("/api/tasks", TaskCreate); +openapi.get("/api/tasks/:taskSlug", TaskFetch); +openapi.delete("/api/tasks/:taskSlug", TaskDelete); -export default { - fetch: router.handle, -} satisfies ExportedHandler; +// Export the Hono app +export default app; diff --git a/packages/create-cloudflare/templates/openapi/ts/src/types.ts b/packages/create-cloudflare/templates/openapi/ts/src/types.ts index f5f0eff5f8d3..f91817f6c44a 100644 --- a/packages/create-cloudflare/templates/openapi/ts/src/types.ts +++ b/packages/create-cloudflare/templates/openapi/ts/src/types.ts @@ -1,9 +1,10 @@ -import { DateTime, Str } from "@cloudflare/itty-router-openapi"; +import { DateTime, Str } from "chanfana"; +import { z } from "zod"; -export const Task = { - name: new Str({ example: "lorem" }), - slug: String, - description: new Str({ required: false }), - completed: Boolean, - due_date: new DateTime(), -}; +export const Task = z.object({ + name: Str({ example: "lorem" }), + slug: Str(), + description: Str({ required: false }), + completed: z.boolean().default(false), + due_date: DateTime(), +}); diff --git a/packages/prerelease-registry/functions/utils/repoAllowlist.ts b/packages/prerelease-registry/functions/utils/repoAllowlist.ts index b89d1e60232b..74d9da741db1 100644 --- a/packages/prerelease-registry/functions/utils/repoAllowlist.ts +++ b/packages/prerelease-registry/functions/utils/repoAllowlist.ts @@ -2,5 +2,5 @@ export const repos = [ "workers-sdk", "next-on-pages", "pages-plugins", - "itty-router-openapi", + "chanfana", ]; diff --git a/templates/worker-openapi/.gitignore b/templates/worker-openapi/.gitignore index ceb7ecfd0b9b..42387803be0c 100644 --- a/templates/worker-openapi/.gitignore +++ b/templates/worker-openapi/.gitignore @@ -1,2 +1,171 @@ -wrangler-local-state +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars diff --git a/templates/worker-openapi/README.md b/templates/worker-openapi/README.md index 7b4e9c31b2aa..2aedc3bb1331 100644 --- a/templates/worker-openapi/README.md +++ b/templates/worker-openapi/README.md @@ -1,33 +1,25 @@ -## Template: worker-openapi +# Cloudflare Workers OpenAPI 3.1 -[![Deploy with Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/workers-sdk/tree/main/templates/worker-openapi) +This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono). -This template demonstrates using the [`itty-router-openapi`](https://github.com/cloudflare/itty-router-openapi) package to add openapi 3 schema generation and validation. +This is an example project made to be used as a quick start into building OpenAPI compliant Workers that generates the +`openapi.json` schema automatically from code and validates the incoming request to the defined parameters or request body. -You can try this template in your browser [here](https://worker-openapi-example.radar.cloudflare.com/docs)! +## Get started -## Setup +1. Sign up for [Cloudflare Workers](https://workers.dev). The free tier is more than enough for most use cases. +2. Clone this project and install dependencies with `npm install` +3. Run `wrangler login` to login to your Cloudflare account in wrangler +4. Run `wrangler deploy` to publish the API to Cloudflare Workers -To create a `my-project` directory using this template, run: +## Project structure -```sh -$ npx wrangler generate my-project worker-openapi -# or -$ yarn wrangler generate my-project worker-openapi -# or -$ pnpm wrangler generate my-project worker-openapi -``` +1. Your main router is defined in `src/index.ts`. +2. Each endpoint has its own file in `src/endpoints/`. +3. For more information read the [chanfana documentation](https://chanfana.pages.dev/) and [Hono documentation](https://hono.dev/docs). -## Local development +## Development -Run `wrangler dev` and head to `/docs` our `/redocs` with your browser. - -You'll be greeted with an OpenAPI page that you can use to test and call your endpoints. - -## Deploy - -Once you are ready, you can publish your code by running the following command: - -```sh -$ wrangler deploy -``` +1. Run `wrangler dev` to start a local instance of the API. +2. Open `http://localhost:8787/` in your browser to see the Swagger interface where you can try the endpoints. +3. Changes made in the `src/` folder will automatically trigger the server to reload, you only need to refresh the Swagger interface. diff --git a/templates/worker-openapi/package.json b/templates/worker-openapi/package.json index 04c431f5069c..f1b6ec422d30 100644 --- a/templates/worker-openapi/package.json +++ b/templates/worker-openapi/package.json @@ -1,21 +1,22 @@ { - "name": "worker-openapi", - "version": "1.0.0", + "name": "cloudflare-workers-openapi", + "version": "0.0.1", "private": true, - "description": "Cloudflare workers template with OpenAPI 3 schema generation and validation", - "keywords": [], - "license": "MIT", - "author": "", - "main": "src/index.ts", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "cf-typegen": "wrangler types", + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev" }, "dependencies": { - "@cloudflare/itty-router-openapi": "^0.0.10" + "chanfana": "^2.0.2", + "hono": "^4.4.7", + "zod": "^3.23.8" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240620.0", + "@types/node": "20.8.3", "@types/service-worker-mock": "^2.0.1", - "wrangler": "^3.0.0" + "wrangler": "^3.60.3" } } diff --git a/templates/worker-openapi/src/endpoints/taskCreate.ts b/templates/worker-openapi/src/endpoints/taskCreate.ts new file mode 100644 index 000000000000..798aa82ceddb --- /dev/null +++ b/templates/worker-openapi/src/endpoints/taskCreate.ts @@ -0,0 +1,58 @@ +import { Bool, OpenAPIRoute } from "chanfana"; +import { z } from "zod"; +import { Task } from "../types"; + +export class TaskCreate extends OpenAPIRoute { + schema = { + tags: ["Tasks"], + summary: "Create a new Task", + request: { + body: { + content: { + "application/json": { + schema: Task, + }, + }, + }, + }, + responses: { + "200": { + description: "Returns the created task", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), + }, + }, + }, + }, + }; + + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + + // Retrieve the validated request body + const taskToCreate = data.body; + + // Implement your own object insertion here + + // return the new task + return { + success: true, + task: { + name: taskToCreate.name, + slug: taskToCreate.slug, + description: taskToCreate.description, + completed: taskToCreate.completed, + due_date: taskToCreate.due_date, + }, + }; + } +} diff --git a/templates/worker-openapi/src/endpoints/taskDelete.ts b/templates/worker-openapi/src/endpoints/taskDelete.ts new file mode 100644 index 000000000000..ed6f691e78eb --- /dev/null +++ b/templates/worker-openapi/src/endpoints/taskDelete.ts @@ -0,0 +1,56 @@ +import { Bool, OpenAPIRoute, Str } from "chanfana"; +import { z } from "zod"; +import { Task } from "../types"; + +export class TaskDelete extends OpenAPIRoute { + schema = { + tags: ["Tasks"], + summary: "Delete a Task", + request: { + params: z.object({ + taskSlug: Str({ description: "Task slug" }), + }), + }, + responses: { + "200": { + description: "Returns if the task was deleted successfully", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), + }, + }, + }, + }, + }; + + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + + // Retrieve the validated slug + const { taskSlug } = data.params; + + // Implement your own object deletion here + + // Return the deleted task for confirmation + return { + result: { + task: { + name: "Build something awesome with Cloudflare Workers", + slug: taskSlug, + description: "Lorem Ipsum", + completed: true, + due_date: "2022-12-24", + }, + }, + success: true, + }; + } +} diff --git a/templates/worker-openapi/src/endpoints/taskFetch.ts b/templates/worker-openapi/src/endpoints/taskFetch.ts new file mode 100644 index 000000000000..7d032ea1d2bf --- /dev/null +++ b/templates/worker-openapi/src/endpoints/taskFetch.ts @@ -0,0 +1,81 @@ +import { Bool, OpenAPIRoute, Str } from "chanfana"; +import { z } from "zod"; +import { Task } from "../types"; + +export class TaskFetch extends OpenAPIRoute { + schema = { + tags: ["Tasks"], + summary: "Get a single Task by slug", + request: { + params: z.object({ + taskSlug: Str({ description: "Task slug" }), + }), + }, + responses: { + "200": { + description: "Returns a single task if found", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + task: Task, + }), + }), + }), + }, + }, + }, + "404": { + description: "Task not found", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + error: Str(), + }), + }), + }, + }, + }, + }, + }; + + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + + // Retrieve the validated slug + const { taskSlug } = data.params; + + // Implement your own object fetch here + + const exists = true; + + // @ts-ignore: check if the object exists + if (exists === false) { + return Response.json( + { + success: false, + error: "Object not found", + }, + { + status: 404, + } + ); + } + + return { + success: true, + task: { + name: "my task", + slug: taskSlug, + description: "this needs to be done", + completed: false, + due_date: new Date().toISOString().slice(0, 10), + }, + }; + } +} diff --git a/templates/worker-openapi/src/endpoints/taskList.ts b/templates/worker-openapi/src/endpoints/taskList.ts new file mode 100644 index 000000000000..8f50506e085d --- /dev/null +++ b/templates/worker-openapi/src/endpoints/taskList.ts @@ -0,0 +1,69 @@ +import { Bool, Num, OpenAPIRoute } from "chanfana"; +import { z } from "zod"; +import { Task } from "../types"; + +export class TaskList extends OpenAPIRoute { + schema = { + tags: ["Tasks"], + summary: "List Tasks", + request: { + query: z.object({ + page: Num({ + description: "Page number", + default: 0, + }), + isCompleted: Bool({ + description: "Filter by completed flag", + required: false, + }), + }), + }, + responses: { + "200": { + description: "Returns a list of tasks", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + tasks: Task.array(), + }), + }), + }), + }, + }, + }, + }, + }; + + async handle(c) { + // Get validated data + const data = await this.getValidatedData(); + + // Retrieve the validated parameters + const { page, isCompleted } = data.query; + + // Implement your own object list here + + return { + success: true, + tasks: [ + { + name: "Clean my room", + slug: "clean-room", + description: null, + completed: false, + due_date: "2025-01-05", + }, + { + name: "Build something awesome with Cloudflare Workers", + slug: "cloudflare-workers", + description: "Lorem Ipsum", + completed: true, + due_date: "2022-12-24", + }, + ], + }; + } +} diff --git a/templates/worker-openapi/src/index.ts b/templates/worker-openapi/src/index.ts index 1bc2faecb1bf..46aee732be23 100644 --- a/templates/worker-openapi/src/index.ts +++ b/templates/worker-openapi/src/index.ts @@ -1,28 +1,23 @@ -import { OpenAPIRouter } from "@cloudflare/itty-router-openapi"; -import { TaskCreate, TaskDelete, TaskFetch, TaskList } from "./tasks"; +import { fromHono } from "chanfana"; +import { Hono } from "hono"; +import { TaskCreate } from "./endpoints/taskCreate"; +import { TaskDelete } from "./endpoints/taskDelete"; +import { TaskFetch } from "./endpoints/taskFetch"; +import { TaskList } from "./endpoints/taskList"; -const router = OpenAPIRouter({ - schema: { - info: { - title: "Worker OpenAPI Example", - version: "1.0", - }, - }, -}); - -router.get("/api/tasks/", TaskList); -router.post("/api/tasks/", TaskCreate); -router.get("/api/tasks/:taskSlug/", TaskFetch); -router.delete("/api/tasks/:taskSlug/", TaskDelete); +// Start a Hono app +const app = new Hono(); -// Redirect root request to the /docs page -router.original.get("/", (request) => - Response.redirect(`${request.url}docs`, 302) -); +// Setup OpenAPI registry +const openapi = fromHono(app, { + docs_url: "/", +}); -// 404 for everything else -router.all("*", () => new Response("Not Found.", { status: 404 })); +// Register OpenAPI endpoints +openapi.get("/api/tasks", TaskList); +openapi.post("/api/tasks", TaskCreate); +openapi.get("/api/tasks/:taskSlug", TaskFetch); +openapi.delete("/api/tasks/:taskSlug", TaskDelete); -export default { - fetch: router.handle, -}; +// Export the Hono app +export default app; diff --git a/templates/worker-openapi/src/tasks.ts b/templates/worker-openapi/src/tasks.ts deleted file mode 100644 index 3f09560b602e..000000000000 --- a/templates/worker-openapi/src/tasks.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { - Bool, - DateOnly, - Int, - OpenAPIRoute, - Path, - Query, - Str, -} from "@cloudflare/itty-router-openapi"; - -const Task = { - name: new Str({ example: "lorem" }), - slug: String, - description: new Str({ required: false }), - completed: Boolean, - due_date: new DateOnly(), -}; - -export class TaskFetch extends OpenAPIRoute { - static schema = { - tags: ["Tasks"], - summary: "Get a single Task by slug", - parameters: { - taskSlug: Path(Str, { - description: "Task slug", - }), - }, - responses: { - "200": { - schema: { - metaData: {}, - task: Task, - }, - }, - }, - }; - - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { - // Retrieve the validated slug - const { taskSlug } = data; - - // Actually fetch a task using the taskSlug - - return { - metaData: { meta: "data" }, - task: { - name: "my task", - slug: taskSlug, - description: "this needs to be done", - completed: false, - due_date: new Date().toISOString().slice(0, 10), - }, - }; - } -} - -export class TaskCreate extends OpenAPIRoute { - static schema = { - tags: ["Tasks"], - summary: "Create a new Task", - requestBody: Task, - responses: { - "200": { - schema: { - task: Task, - }, - }, - }, - }; - - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { - // Retrieve the validated request body - const { body } = data; - - // Actually insert the task - - // return the new task - return { - metaData: { meta: "data" }, - task: { - name: body.name, - slug: body.slug, - description: body.description, - completed: body.completed, - due_date: body.due_date, - }, - }; - } -} - -export class TaskList extends OpenAPIRoute { - static schema = { - tags: ["Tasks"], - summary: "List Tasks", - parameters: { - page: Query(Int, { - description: "Page number", - default: 0, - }), - isCompleted: Query(Bool, { - description: "Filter by completed flag", - required: false, - }), - }, - responses: { - "200": { - schema: { - tasks: [Task], - }, - }, - }, - }; - - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { - // Retrieve the validated parameters - const { page, isCompleted } = data; - - return { - metaData: { page: page, isCompleted: isCompleted }, - tasks: [ - { - name: "Clean my room", - slug: "clean-room", - description: null, - completed: false, - due_date: "2025-01-05", - }, - { - name: "Build something awesome with Cloudflare Workers", - slug: "cloudflare-workers", - description: "Lorem Ipsum", - completed: true, - due_date: "2022-12-24", - }, - ], - }; - } -} - -export class TaskDelete extends OpenAPIRoute { - static schema = { - tags: ["Tasks"], - summary: "Delete a Task", - parameters: { - taskSlug: Path(Str, { - description: "Task slug", - }), - }, - responses: { - "200": { - schema: { - metaData: {}, - success: Boolean, - }, - }, - }, - }; - - async handle( - request: Request, - env: any, - context: any, - data: Record - ) { - // Retrieve the validated slug - const { taskSlug } = data; - - return { - metaData: { taskSlug: taskSlug }, - success: true, - }; - } -} diff --git a/templates/worker-openapi/src/types.ts b/templates/worker-openapi/src/types.ts new file mode 100644 index 000000000000..f91817f6c44a --- /dev/null +++ b/templates/worker-openapi/src/types.ts @@ -0,0 +1,10 @@ +import { DateTime, Str } from "chanfana"; +import { z } from "zod"; + +export const Task = z.object({ + name: Str({ example: "lorem" }), + slug: Str(), + description: Str({ required: false }), + completed: z.boolean().default(false), + due_date: DateTime(), +}); diff --git a/templates/worker-openapi/tsconfig.json b/templates/worker-openapi/tsconfig.json index 7b91086bfc80..196166eb6db1 100644 --- a/templates/worker-openapi/tsconfig.json +++ b/templates/worker-openapi/tsconfig.json @@ -1,12 +1,32 @@ { "compilerOptions": { - "noEmit": true, - "module": "esnext", - "target": "esnext", + "allowJs": true, + "allowSyntheticDefaultImports": true, + "baseUrl": "src", + "declaration": true, + "sourceMap": true, + "esModuleInterop": true, + "inlineSourceMap": false, "lib": ["esnext"], - "strict": true, + "listEmittedFiles": false, + "listFiles": false, "moduleResolution": "node", - "types": ["@cloudflare/workers-types", "@types/service-worker-mock"] + "noFallthroughCasesInSwitch": true, + "pretty": true, + "resolveJsonModule": true, + "rootDir": ".", + "skipLibCheck": true, + "strict": false, + "traceResolution": false, + "outDir": "", + "target": "esnext", + "module": "esnext", + "types": [ + "@types/node", + "@types/service-worker-mock", + "@cloudflare/workers-types" + ] }, - "exclude": ["node_modules"] + "exclude": ["node_modules", "dist", "tests"], + "include": ["src", "scripts"] } diff --git a/templates/worker-openapi/worker-configuration.d.ts b/templates/worker-openapi/worker-configuration.d.ts new file mode 100644 index 000000000000..a3f43d2a431f --- /dev/null +++ b/templates/worker-openapi/worker-configuration.d.ts @@ -0,0 +1,3 @@ +// Generated by Wrangler +// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen` +interface Env {} diff --git a/templates/worker-openapi/wrangler.toml b/templates/worker-openapi/wrangler.toml index 7d0a655df60f..c67458455856 100644 --- a/templates/worker-openapi/wrangler.toml +++ b/templates/worker-openapi/wrangler.toml @@ -1,3 +1,3 @@ name = "worker-openapi" main = "src/index.ts" -compatibility_date = "2022-11-28" +compatibility_date = "2024-06-01"