@@ -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": { | |||
@@ -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; | |||
} | |||
} |
@@ -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(`<root type="object"><hello type="string">world</hello></root>`) | |||
.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(`<root type="object"><hello type="string">world</hello></root>`) | |||
.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('<root type="object"><hello type="string">world</hello></root>'); | |||
}); | |||
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('<root type="object"><hello type="string">world</hello></root>'); | |||
}); | |||
}); | |||
}); |
@@ -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); | |||
}); | |||
}); |
@@ -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" | |||