Repository: nodejs/undici

Problem statement:
Error message for invalid headers could be improved
## Bug Description

I'm getting the error message:

> TypeError: Headers contructor: init is a symbol, which cannot be converted to a DOMString.

## Reproducible By

```
const headers = new Headers({'x-fake-header': Symbol('why is this here?')})
```

## Expected Behavior

`contructor` should not be typo'd

"init is a symbol" is incorrect. It _contains_ a symbol, but isn't one

## Logs & Screenshots

```
TypeError: Headers contructor: init is a symbol, which cannot be converted to a DOMString.
    at webidl.errors.exception (node:internal/deps/undici/undici:3384:14)
    at webidl.converters.DOMString (node:internal/deps/undici/undici:3650:29)
    at webidl.converters.ByteString (node:internal/deps/undici/undici:3658:35)
    at Object.record<ByteString, ByteString> (node:internal/deps/undici/undici:3568:32)
    at webidl.converters.HeadersInit (node:internal/deps/undici/undici:8695:67)
```

## Environment

Tested on Node.js v20.17.0 and v22.11.0

### Additional context

Users were running into this in SvelteKit projects. I'm not quite sure how/why yet, but I think maybe `connect` (which is used by `vite`) has something like this in its headers and the headers are being copied from there


Hints:

Patch:
diff --git a/lib/web/fetch/headers.js b/lib/web/fetch/headers.js
--- a/lib/web/fetch/headers.js
+++ b/lib/web/fetch/headers.js
@@ -451,7 +451,7 @@ class Headers {
 
     // 2. If init is given, then fill this with init.
     if (init !== undefined) {
-      init = webidl.converters.HeadersInit(init, 'Headers contructor', 'init')
+      init = webidl.converters.HeadersInit(init, 'Headers constructor', 'init')
       fill(this, init)
     }
   }
diff --git a/lib/web/fetch/webidl.js b/lib/web/fetch/webidl.js
--- a/lib/web/fetch/webidl.js
+++ b/lib/web/fetch/webidl.js
@@ -345,12 +345,14 @@ webidl.recordConverter = function (keyConverter, valueConverter) {
       const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]
 
       for (const key of keys) {
+        const keyName = webidl.util.Stringify(key)
+
         // 1. Let typedKey be key converted to an IDL value of type K.
-        const typedKey = keyConverter(key, prefix, argument)
+        const typedKey = keyConverter(key, prefix, `Key ${keyName} in ${argument}`)
 
         // 2. Let value be ? Get(O, key).
         // 3. Let typedValue be value converted to an IDL value of type V.
-        const typedValue = valueConverter(O[key], prefix, argument)
+        const typedValue = valueConverter(O[key], prefix, `${argument}[${keyName}]`)
 
         // 4. Set result[typedKey] to typedValue.
         result[typedKey] = typedValue
@@ -501,8 +503,14 @@ webidl.converters.DOMString = function (V, prefix, argument, opts) {
 // https://webidl.spec.whatwg.org/#es-ByteString
 webidl.converters.ByteString = function (V, prefix, argument) {
   // 1. Let x be ? ToString(V).
-  // Note: DOMString converter perform ? ToString(V)
-  const x = webidl.converters.DOMString(V, prefix, argument)
+  if (typeof V === 'symbol') {
+    throw webidl.errors.exception({
+      header: prefix,
+      message: `${argument} is a symbol, which cannot be converted to a ByteString.`
+    })
+  }
+
+  const x = String(V)
 
   // 2. If the value of any element of x is greater than
   //    255, then throw a TypeError.


Test patch:
diff --git a/test/fetch/headers.js b/test/fetch/headers.js
--- a/test/fetch/headers.js
+++ b/test/fetch/headers.js
@@ -26,7 +26,7 @@ test('Headers initialization', async (t) => {
       throws(() => new Headers(['undici', 'fetch', 'fetch']), TypeError)
       throws(
         () => new Headers([0, 1, 2]),
-        TypeError('Headers contructor: init[0] (0) is not iterable.')
+        TypeError('Headers constructor: init[0] (0) is not iterable.')
       )
     })
 
@@ -41,7 +41,7 @@ test('Headers initialization', async (t) => {
       const init = ['undici', 'fetch', 'fetch', 'undici']
       throws(
         () => new Headers(init),
-        TypeError('Headers contructor: init[0] ("undici") is not iterable.')
+        TypeError('Headers constructor: init[0] ("undici") is not iterable.')
       )
     })
   })
@@ -767,3 +767,16 @@ test('Invalid Symbol.iterators', (t) => {
     new Headers(obj) // eslint-disable-line no-new
   }, TypeError)
 })
+
+// https://github.com/nodejs/undici/issues/3829
+test('Invalid key/value records passed to constructor (issue #3829)', (t) => {
+  assert.throws(
+    () => new Headers({ [Symbol('x-fake-header')]: '??' }),
+    new TypeError('Headers constructor: Key Symbol(x-fake-header) in init is a symbol, which cannot be converted to a ByteString.')
+  )
+
+  assert.throws(
+    () => new Headers({ 'x-fake-header': Symbol('why is this here?') }),
+    new TypeError('Headers constructor: init["x-fake-header"] is a symbol, which cannot be converted to a ByteString.')
+  )
+})
diff --git a/test/webidl/errors.js b/test/webidl/errors.js
--- a/test/webidl/errors.js
+++ b/test/webidl/errors.js
@@ -17,7 +17,7 @@ test('ByteString', (t) => {
   ]) {
     assert.throws(
       () => new Headers()[method](name, value),
-      new TypeError(`Headers.${method}: name is a symbol, which cannot be converted to a DOMString.`)
+      new TypeError(`Headers.${method}: name is a symbol, which cannot be converted to a ByteString.`)
     )
   }
 })