diff --git a/package.json b/package.json
index 77e47ed..c618240 100644
--- a/package.json
+++ b/package.json
@@ -16,13 +16,14 @@
"@types/node": "^18.14.1",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
+ "fastify": "^4.12.0",
"pridepack": "2.4.4",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
},
"dependencies": {
- "fastify": "^4.12.0",
+ "@modal-sh/oatmeal-core": "file:../core",
"fastify-plugin": "^4.5.0"
},
"scripts": {
diff --git a/src/index.ts b/src/index.ts
index 8f28352..4ef7a0c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,40 @@
import { fastifyPlugin } from 'fastify-plugin';
+import * as oatmeal from '@modal-sh/oatmeal-core';
+import { FastifyReply, FastifyRequest} from 'fastify';
+
+const JSON_TYPES = [
+ 'application/json',
+] as const
+
+interface SendX {
+ (contentType?: string, data?: unknown): void;
+}
+
+const sendX: SendX = function sendX(this: FastifyReply, contentType?: string, data?: unknown) {
+ if (typeof contentType !== 'undefined' && typeof data !== 'undefined') {
+ const content = oatmeal.serialize(data, { type: contentType })
+ // TODO add error handling when payload can't be serialized
+ this.type(contentType).send(content);
+ return;
+ }
+ this.send();
+}
export const fastifyOatmeal = fastifyPlugin(async (fastify) => {
+ oatmeal.AVAILABLE_TYPES
+ .filter((type) => !JSON_TYPES.includes(type as typeof JSON_TYPES[number]))
+ .forEach((type) => {
+ fastify.addContentTypeParser(type, { parseAs: 'buffer' }, async (_: FastifyRequest, body: Buffer) => {
+ return oatmeal.deserialize(body.toString('utf-8'), { type });
+ // TODO add error handling when body can't be deserialized
+ });
+ });
+ fastify.decorateReply('sendX', sendX);
});
+
+declare module 'fastify' {
+ interface FastifyReply {
+ sendX: SendX;
+ }
+}
diff --git a/src/routes.ts b/src/routes.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/server.ts b/src/server.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/test/index.test.ts b/test/index.test.ts
new file mode 100644
index 0000000..2295ab6
--- /dev/null
+++ b/test/index.test.ts
@@ -0,0 +1,138 @@
+import {describe, it, expect, beforeEach, afterEach} from 'vitest';
+import {fastify, FastifyInstance} from 'fastify';
+import { fastifyOatmeal } from '../src';
+
+describe('oatmeal-fastify', () => {
+ let server: FastifyInstance;
+
+ beforeEach(() => {
+ server = fastify();
+ server.route({
+ url: '/',
+ method: 'POST',
+ handler: async (request, reply) => {
+ return reply.sendX(request.headers['accept'] as string, request.body);
+ },
+ });
+ });
+
+ afterEach(async () => {
+ await server.close();
+ });
+
+ describe('deserialize', () => {
+ it('deserializes application/json', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+
+ it('deserializes text/json', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'application/json',
+ 'Content-Type': 'text/json',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+
+ it('deserializes application/xml', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(`world`)
+ .headers({
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/xml',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+
+ it('deserializes text/xml', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(`world`)
+ .headers({
+ 'Accept': 'application/json',
+ 'Content-Type': 'text/xml',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+ });
+
+ describe('serialize', () => {
+ it('serializes application/json', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+
+ it('serializes text/json', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'text/json',
+ 'Content-Type': 'application/json',
+ });
+
+ expect(response.body).toBe(JSON.stringify({ hello: 'world' }));
+ });
+
+ it('deserializes application/xml', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'application/xml',
+ 'Content-Type': 'application/json',
+ });
+
+ expect(response.body).toBe('world');
+ });
+
+ it('deserializes application/xml', async () => {
+ await server.register(fastifyOatmeal);
+ const response = await server
+ .inject()
+ .post('/')
+ .body(JSON.stringify({ hello: 'world' }))
+ .headers({
+ 'Accept': 'text/xml',
+ 'Content-Type': 'application/json',
+ });
+
+ expect(response.body).toBe('world');
+ });
+ });
+});
diff --git a/test/index.test.tsx b/test/index.test.tsx
deleted file mode 100644
index 77cf2d5..0000000
--- a/test/index.test.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import SERVER from '../src/server';
-import '../src/routes';
-
-describe('Example', () => {
- it('should have the expected content', async () => {
- const response = await SERVER
- .inject()
- .get('/')
- .headers({
- 'Accept': 'application/json',
- });
- expect(response.statusCode).toBe(200);
- });
-});
diff --git a/yarn.lock b/yarn.lock
index 11c7b88..a938d1d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -470,6 +470,11 @@
resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.2.47.tgz#421bed6950958adf9b2aee8386aeac9d92e22045"
integrity sha512-6/QvoKeooo3J/WL7i9yjfDtUkqOZW8K6aqdzcw+bz4YdgMBzBQVZU7vZmEdCGOcV5AlsBHZT38mqx/sTrnZMDQ==
+"@modal-sh/oatmeal-core@file:../core":
+ version "0.0.0"
+ dependencies:
+ xml-js "^1.6.11"
+
"@next/eslint-plugin-next@^13.2.4":
version "13.2.4"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.2.4.tgz#3e124cd10ce24dab5d3448ce04104b4f1f4c6ca7"
@@ -3206,6 +3211,11 @@ safe-stable-stringify@^2.3.1:
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
+sax@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+
secure-json-parse@^2.5.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
@@ -3811,6 +3821,13 @@ xdg-basedir@^4.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
+xml-js@^1.6.11:
+ version "1.6.11"
+ resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
+ integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
+ dependencies:
+ sax "^1.2.4"
+
xml-name-validator@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"