Repository: unjs/h3
Pull request #429 (2023-07-06 15:24:55)

feat: add `event.method` and `event.headers`
<!---
☝️ PR title should follow conventional commits (https://conventionalcommits.org) 
-->

### 🔗 Linked issue

#423, #73 

<!-- Please ensure there is an open issue and mention its number as #123 -->

### ❓ Type of change

<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->

- [ ] 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)
- [ ] 👌 Enhancement (improving an existing functionality like performance)
- [x] ✨ New feature (a non-breaking change that adds functionality)
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

### 📚 Description

This PR adds `event.method` and `event.headers` support. 

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] I have linked an issue or discussion.
- [ ]  I have updated the documentation accordingly.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/unjs/h3/pull/429?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs) Report
> Merging [#429](https://app.codecov.io/gh/unjs/h3/pull/429?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs) (2361c85) into [main](https://app.codecov.io/gh/unjs/h3/commit/fb9e3e34babbe4b5682637fe50b83a410d592d96?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs) (fb9e3e3) will **decrease** coverage by `1.66%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head 2361c85 differs from pull request most recent head 80aed9e. Consider uploading reports for the commit 80aed9e to get more accurate results

```diff
@@            Coverage Diff             @@
##             main     #429      +/-   ##
==========================================
- Coverage   76.82%   75.16%   -1.66%     
==========================================
  Files          26       26              
  Lines        2554     2569      +15     
  Branches      367      369       +2     
==========================================
- Hits         1962     1931      -31     
- Misses        592      638      +46     
```


| [Impacted Files](https://app.codecov.io/gh/unjs/h3/pull/429?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs) | Coverage Δ | |
|---|---|---|
| [src/event/event.ts](https://app.codecov.io/gh/unjs/h3/pull/429?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs#diff-c3JjL2V2ZW50L2V2ZW50LnRz) | `57.75% <100.00%> (+6.27%)` | :arrow_up: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/unjs/h3/pull/429/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=unjs)


pi0: Thanks for the PR! I've made a couple of updates:

- Use a getter with lazy evaluation and keep the public interface read-only.
  - Similar to `_handled` we have an internal way to override them. Not to be advised.
- Fix tests for Node 16 not having global Headers. In H3 we have a working polyfill but thinking to not include it in base bundle.

Patch:
diff --git a/src/event/event.ts b/src/event/event.ts
index 6b7b9cc..b08f355 100644
--- a/src/event/event.ts
+++ b/src/event/event.ts
@@ -1,4 +1,5 @@
-import type { H3EventContext } from "../types";
+import type { IncomingHttpHeaders } from "node:http";
+import type { H3EventContext, HTTPMethod } from "../types";
 import type { NodeIncomingMessage, NodeServerResponse } from "../node";
 import {
   MIMES,
@@ -15,11 +16,18 @@ export interface NodeEventContext {
 
 export class H3Event implements Pick<FetchEvent, "respondWith"> {
   "__is_event__" = true;
-  _handled = false;
 
+  // Context
   node: NodeEventContext;
   context: H3EventContext = {};
 
+  // Request
+  _method: HTTPMethod | undefined;
+  _headers: Headers | undefined;
+
+  // Response
+  _handled = false;
+
   constructor(req: NodeIncomingMessage, res: NodeServerResponse) {
     this.node = { req, res };
   }
@@ -34,6 +42,17 @@ export class H3Event implements Pick<FetchEvent, "respondWith"> {
     );
   }
 
+  get method(): HTTPMethod | undefined {
+    return this._method || (this.node.req.method as HTTPMethod);
+  }
+
+  get headers(): Headers {
+    if (!this._headers) {
+      this._headers = _normalizeNodeHeaders(this.node.req.headers);
+    }
+    return this._headers;
+  }
+
   /** @deprecated Please use `event.node.req` instead. **/
   get req() {
     return this.node.req;
@@ -99,3 +118,21 @@ export function createEvent(
 ): H3Event {
   return new H3Event(req, res);
 }
+
+// --- Internal ---
+
+function _normalizeNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {
+  const headers = new Headers();
+
+  for (const [name, value] of Object.entries(nodeHeaders)) {
+    if (Array.isArray(value)) {
+      for (const item of value) {
+        headers.append(name, item);
+      }
+    } else if (value) {
+      headers.set(name, value);
+    }
+  }
+
+  return headers;
+}


Test patch:
diff --git a/test/event.test.ts b/test/event.test.ts
new file mode 100644
index 0000000..ce2bab0
--- /dev/null
+++ b/test/event.test.ts
@@ -0,0 +1,52 @@
+import supertest, { SuperTest, Test } from "supertest";
+import { describe, it, expect, beforeEach } from "vitest";
+import {
+  createApp,
+  App,
+  toNodeListener,
+  eventHandler,
+  getMethod,
+} from "../src";
+
+const supportsHeaders = typeof Headers !== "undefined";
+
+describe("Event", () => {
+  let app: App;
+  let request: SuperTest<Test>;
+
+  beforeEach(() => {
+    app = createApp({ debug: false });
+    request = supertest(toNodeListener(app));
+  });
+
+  it("can read the method", async () => {
+    app.use(
+      "/",
+      eventHandler((event) => {
+        expect(event.method).toBe(getMethod(event));
+        expect(event.method).toBe("POST");
+        return "200";
+      })
+    );
+    const result = await request.post("/hello");
+    expect(result.text).toBe("200");
+  });
+
+  it.runIf(supportsHeaders)("can read the headers", async () => {
+    app.use(
+      "/",
+      eventHandler((event) => {
+        return {
+          headers: [...event.headers.entries()],
+        };
+      })
+    );
+    const result = await request
+      .post("/hello")
+      .set("X-Test", "works")
+      .set("Cookie", ["a", "b"]);
+    const { headers } = JSON.parse(result.text);
+    expect(headers.find(([key]) => key === "x-test")[1]).toBe("works");
+    expect(headers.find(([key]) => key === "cookie")[1]).toBe("a; b");
+  });
+});