Repository: twilio-labs/serverless-toolkit (ts)

Issue:
Content-Type header check in response is not handling charset
Broken:

```js
exports.handler = async (context, event, callback) => {
  const response = new Twilio.Response();
  response.appendHeader('Content-Type', 'application/json; charset=UTF-8');
  response.setStatusCode(400);
  response.setBody(
    {message: 'I am a JSON message'}
  )
  return callback(null, response);
};
```

Working:
```js
exports.handler = async (context, event, callback) => {
  const response = new Twilio.Response();
  response.appendHeader('Content-Type', 'application/json');
  response.setStatusCode(400);
  response.setBody(
    {message: 'I am a JSON message'}
  )
  return callback(null, response);
};
```

Instead we should adjust the check for the `Content-Type` header to validate that it includes `application/json` as opposed to being explicitly `application-json`.

https://github.com/twilio-labs/serverless-toolkit/blob/33ca348aa7886cdf58255900077b156f00461433/packages/runtime-handler/src/dev-runtime/internal/response.ts#L139

Pull request description:
fix(runtime-handler): don't check for exact Content-Type matches
Content-Type headers may have the media type followed by a semicolon,
followed by some parameters, for example "charset=UTF-8". This fix
ensures that only the start of the Content-Type header matches
application/json when stringifying the body to JSON.

Fixes #391

<!-- Describe your Pull Request -->

**Contributing to Twilio**

> All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.

- [x] I acknowledge that all my contributions will be made under the project's license.


Patch:
diff --git a/.changeset/friendly-turtles-pretend.md b/.changeset/friendly-turtles-pretend.md
new file mode 100644
index 0000000..03d29d6
--- /dev/null
+++ b/.changeset/friendly-turtles-pretend.md
@@ -0,0 +1,5 @@
+---
+'@twilio/runtime-handler': patch
+---
+
+Don't check for exact Content-Type matches
diff --git a/packages/runtime-handler/src/dev-runtime/internal/response.ts b/packages/runtime-handler/src/dev-runtime/internal/response.ts
index 20ec828..5f41888 100644
--- a/packages/runtime-handler/src/dev-runtime/internal/response.ts
+++ b/packages/runtime-handler/src/dev-runtime/internal/response.ts
@@ -79,7 +79,7 @@ export class Response implements TwilioResponse {
           this.headers[COOKIE_HEADER] = newHeaderValue;
         }
       } else {
-        this.headers[COOKIE_HEADER] = Array.isArray(value) ? value: [value];
+        this.headers[COOKIE_HEADER] = Array.isArray(value) ? value : [value];
       }
     } else {
       const existingValue = this.headers[key];
@@ -133,12 +133,17 @@ export class Response implements TwilioResponse {
   }
 
   serialize() {
+    const contentType = this.headers['Content-Type'];
+    let body = this.body;
+    if (
+      typeof contentType === 'string' &&
+      contentType.startsWith('application/json')
+    ) {
+      body = JSON.stringify(body);
+    }
     return {
       statusCode: this.statusCode,
-      body:
-        this.headers['Content-Type'] === 'application/json'
-          ? JSON.stringify(this.body)
-          : this.body,
+      body: body,
       headers: this.headers,
     };
   }


Test patch:
diff --git a/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts b/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts
index 8a54e45..aee8704 100644
--- a/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts
+++ b/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts
@@ -292,3 +292,21 @@ test('serializes a response with content type set to application/json', () => {
     'Set-Cookie': [],
   });
 });
+
+test('serializes a response with content type set to application/json with a charset', () => {
+  const response = new Response();
+  response.setBody({ url: 'https://dkundel.com' });
+  response.setStatusCode(200);
+  response.appendHeader('Content-Type', 'application/json; charset=UTF-8');
+
+  const serialized = response.serialize();
+
+  expect(serialized.body).toEqual(
+    JSON.stringify({ url: 'https://dkundel.com' })
+  );
+  expect(serialized.statusCode).toEqual(200);
+  expect(serialized.headers).toEqual({
+    'Content-Type': 'application/json; charset=UTF-8',
+    'Set-Cookie': [],
+  });
+});