diff --git a/.github/workflows/create-pullrequest-prerelease.yml b/.github/workflows/create-pullrequest-prerelease.yml index 7350524..188337a 100644 --- a/.github/workflows/create-pullrequest-prerelease.yml +++ b/.github/workflows/create-pullrequest-prerelease.yml @@ -26,3 +26,44 @@ jobs: with: name: npm-package-chanfana-${{ github.event.number }} # encode the PR number into the artifact name path: chanfana-*.tgz + + - name: 'Put PR and workflow ID on the environment' + uses: actions/github-script@v7 + with: + script: | + const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + for (const artifact of allArtifacts.data.artifacts) { + // Extract the PR number from the artifact name + const match = /^npm-package-chanfana-(\d+)$/.exec(artifact.name); + if (match) { + require("fs").appendFileSync( + process.env.GITHUB_ENV, + `\nWORKFLOW_RUN_PR=${match[1]}` + + `\nWORKFLOW_RUN_ID=${context.payload.workflow_run.id}` + ); + break; + } + } + + - name: 'Comment on PR with Link' + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ env.WORKFLOW_RUN_PR }} + message: | + 🧪 A prerelease is available for testing 🧪 + + You can install this latest build in your project with: + + ```sh + npm install --save https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} + ``` + + Or you can immediately run this with `npx`: + + ```sh + npx https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} + ``` diff --git a/.github/workflows/write-prerelease-comment.yml b/.github/workflows/write-prerelease-comment.yml deleted file mode 100644 index f0d4285..0000000 --- a/.github/workflows/write-prerelease-comment.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Write prerelease comment - -on: - workflow_run: - workflows: ['Create Pull Request Prerelease'] - types: - - completed - -jobs: - comment: - if: ${{ github.repository_owner == 'cloudflare' }} - runs-on: ubuntu-latest - name: Write comment to the PR - steps: - - name: 'Put PR and workflow ID on the environment' - uses: actions/github-script@v7 - with: - script: | - const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - for (const artifact of allArtifacts.data.artifacts) { - // Extract the PR number from the artifact name - const match = /^npm-package-chanfana-(\d+)$/.exec(artifact.name); - if (match) { - require("fs").appendFileSync( - process.env.GITHUB_ENV, - `\nWORKFLOW_RUN_PR=${match[1]}` + - `\nWORKFLOW_RUN_ID=${context.payload.workflow_run.id}` - ); - break; - } - } - - name: 'Comment on PR with Link' - uses: marocchino/sticky-pull-request-comment@v2 - with: - number: ${{ env.WORKFLOW_RUN_PR }} - message: | - 🧪 A prerelease is available for testing 🧪 - - You can install this latest build in your project with: - - ```sh - npm install --save https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} - ``` - - Or you can immediately run this with `npx`: - - ```sh - npx https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} - ``` diff --git a/README.md b/README.md index b626d61..1570e15 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ library for Cloudflare Workers but runs on any runtime supported by the base rou The key features are: - OpenAPI 3 and 3.1 schema generator and validator -- Query, Path, Headers and Body typescript inference +- [Query](https://chanfana.pages.dev/user-guide/query-parameters/), [Path](https://chanfana.pages.dev/user-guide/path-parameters/), [Headers](https://chanfana.pages.dev/user-guide/header-parameters/) and [Body](https://chanfana.pages.dev/user-guide/request-body/) typescript inference - Fully written in typescript -- Class-based endpoints -- Extend existing Hono, itty-router, etc application, without touching old routes +- [Class-based endpoints](https://chanfana.pages.dev/user-guide/first-steps/) +- Extend existing [Hono](https://chanfana.pages.dev/routers/hono/), [itty-router](https://chanfana.pages.dev/routers/itty-router/), etc application, without touching old routes ## Getting started diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 92ebddf..85e53d1 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -51,6 +51,7 @@ nav: - user-guide/first-steps.md - user-guide/path-parameters.md - user-guide/query-parameters.md + - user-guide/header-parameters.md - user-guide/request-body.md - user-guide/response-format.md - user-guide/router-options.md diff --git a/docs/pages/assets/logo.svg b/docs/pages/assets/logo.svg index a7976e5..c5343a2 100644 --- a/docs/pages/assets/logo.svg +++ b/docs/pages/assets/logo.svg @@ -1,5 +1,72 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/pages/index.md b/docs/pages/index.md index b626d61..1570e15 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -26,10 +26,10 @@ library for Cloudflare Workers but runs on any runtime supported by the base rou The key features are: - OpenAPI 3 and 3.1 schema generator and validator -- Query, Path, Headers and Body typescript inference +- [Query](https://chanfana.pages.dev/user-guide/query-parameters/), [Path](https://chanfana.pages.dev/user-guide/path-parameters/), [Headers](https://chanfana.pages.dev/user-guide/header-parameters/) and [Body](https://chanfana.pages.dev/user-guide/request-body/) typescript inference - Fully written in typescript -- Class-based endpoints -- Extend existing Hono, itty-router, etc application, without touching old routes +- [Class-based endpoints](https://chanfana.pages.dev/user-guide/first-steps/) +- Extend existing [Hono](https://chanfana.pages.dev/routers/hono/), [itty-router](https://chanfana.pages.dev/routers/itty-router/), etc application, without touching old routes ## Getting started diff --git a/docs/pages/user-guide/header-parameters.md b/docs/pages/user-guide/header-parameters.md new file mode 100644 index 0000000..f40d46e --- /dev/null +++ b/docs/pages/user-guide/header-parameters.md @@ -0,0 +1,33 @@ +**Please make sure to read the [Types](../types.md) page before continuing.** + + +You can declare `headers` parameters in the `request` property of your endpoint schema. + +The validated data is available under `data.headers.`. + +```ts hl_lines="10-12" +import {OpenAPIRoute, Ip} from 'chanfana' +import {z} from 'zod' +import {Context} from 'hono' + +export class ToDoFetch extends OpenAPIRoute { + schema = { + tags: ['ToDo'], + summary: 'Fetch a ToDo', + request: { + headers: z.object({ + forwardedFor: Ip() + }) + } + } + + async handle(c: Context) { + const data = await this.getValidatedData() + + // You get full type inference when accessing the data variable + data.headers.forwardedFor + + // ... + } +} +``` diff --git a/package.json b/package.json index 5efc113..f6dc84c 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,12 @@ "umd", "typed" ], - "author": "gmassadas@cloudflare.com", + "author": "Gabriel Massadas (https://github.com/g4brym)", "license": "MIT", "homepage": "https://chanfana.pages.dev", "repository": { "type": "git", - "url": "git+ssh://git@github.com/cloudflare/chanfana.git" + "url": "https://github.com/cloudflare/chanfana.git" }, "bugs": { "url": "https://github.com/cloudflare/chanfana/issues" diff --git a/src/route.ts b/src/route.ts index ce3303c..fe66160 100644 --- a/src/route.ts +++ b/src/route.ts @@ -122,8 +122,10 @@ export class OpenAPIRoute = any> { rawSchema.query = schema.request?.query; unvalidatedData.query = {}; } + console.log(schema.request); if (schema.request?.headers) { rawSchema.headers = schema.request?.headers; + console.log(schema.request?.headers); unvalidatedData.headers = {}; } @@ -134,9 +136,9 @@ export class OpenAPIRoute = any> { if (schema.request?.headers) { const tmpHeaders: Record = {}; - // @ts-ignore - for (const header of Object.keys(schema.request?.headers.shape)) { - tmpHeaders[header] = request.headers.get(header); + const rHeaders = new Headers(request.headers); + for (const header of Object.keys((schema.request?.headers as AnyZodObject).shape)) { + tmpHeaders[header] = rHeaders.get(header); } unvalidatedData.headers = coerceInputs(tmpHeaders, schema.request?.headers as AnyZodObject); diff --git a/tests/integration/parameters.test.ts b/tests/integration/parameters.test.ts index 8b5781c..6432569 100644 --- a/tests/integration/parameters.test.ts +++ b/tests/integration/parameters.test.ts @@ -482,7 +482,7 @@ describe("bodyParametersValidation", () => { ); const resp = await request.json(); - //expect(request.status).toEqual(200) + expect(request.status).toEqual(200); expect(resp).toEqual({ todo: { @@ -492,4 +492,55 @@ describe("bodyParametersValidation", () => { }, }); }); + + it("header should be required", async () => { + const request = await todoRouter.fetch( + buildRequest({ + method: "get", + path: "/header", + }), + {}, + {}, + ); + const resp = await request.json(); + + expect(request.status).toEqual(400); + + expect(resp).toEqual({ + errors: [ + { + code: "invalid_type", + expected: "string", + message: "Expected string, received null", + path: ["headers", "p_hostname"], + received: "null", + }, + ], + result: {}, + success: false, + }); + }); + + it("header should be accepted if sent", async () => { + const request = await todoRouter.fetch( + buildRequest({ + method: "get", + path: "/header", + headers: { + p_hostname: "www.cloudflare.com", + }, + }), + {}, + {}, + ); + const resp = await request.json(); + + expect(request.status).toEqual(200); + + expect(resp).toEqual({ + headers: { + p_hostname: "www.cloudflare.com", + }, + }); + }); }); diff --git a/tests/router.ts b/tests/router.ts index aa53907..d841abb 100644 --- a/tests/router.ts +++ b/tests/router.ts @@ -261,12 +261,33 @@ export class ToDoCreateTyped extends OpenAPIRoute { } } +export class ToDoHeaderCheck extends OpenAPIRoute { + schema = { + tags: ["ToDo"], + summary: "List all ToDos", + request: { + headers: z.object({ + p_hostname: Hostname(), + }), + }, + }; + + async handle(request: Request, env: any, context: any) { + const data = await this.getValidatedData(); + + return { + headers: data.headers, + }; + } +} + export const todoRouter = fromIttyRouter(AutoRouter(), { openapiVersion: "3" }); todoRouter.get("/todos", ToDoList); todoRouter.get("/todos/:id", ToDoGet); todoRouter.post("/todos", ToDoCreate); todoRouter.post("/todos-typed", ToDoCreateTyped); todoRouter.get("/contenttype", ContentTypeGet); +todoRouter.get("/header", ToDoHeaderCheck); // 404 for everything else todoRouter.all("*", () => new Response("Not Found.", { status: 404 }));