@middy/http-response-serializer
Advanced tools
Comparing version 1.5.2 to 2.0.0-alpha.0
@@ -0,7 +1,9 @@ | ||
const test = require('ava') | ||
const sinon = require('sinon') | ||
const middy = require('../../core/index.js') | ||
const createError = require('http-errors') | ||
const { invoke } = require('../../test-helpers') | ||
const middy = require('../../core') | ||
const httpErrorHandler = require('../../http-error-handler') | ||
const httpResponseSerializer = require('../') | ||
const httpErrorHandler = require('../../http-error-handler/index.js') | ||
const httpResponseSerializer = require('../index.js') | ||
const standardConfiguration = { | ||
@@ -30,274 +32,268 @@ serializers: [ | ||
describe('📦 Middleware Http Response Serializer', () => { | ||
describe('It should pass-through when `content-type` header is already set', () => { | ||
test.each([ | ||
['Content-Type'], | ||
['content-type'], | ||
['CONTENT-TYPE'] | ||
])( | ||
'%s skips response serialization', | ||
async (key) => { | ||
const handlerResponse = Object.assign({}, createHttpResponse(), { | ||
headers: { | ||
[key]: 'text/plain' | ||
} | ||
}) | ||
const handler = middy((event, context, cb) => cb(null, handlerResponse)) | ||
for (const [key] of [ | ||
['Content-Type'], | ||
['content-type'], | ||
['CONTENT-TYPE'] | ||
]) { | ||
test(`${key} skips response serialization`, async (t) => { | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const handlerResponse = Object.assign({}, createHttpResponse(), { | ||
headers: { | ||
[key]: 'text/plain' | ||
} | ||
}) | ||
const handler = middy((event, context) => handlerResponse) | ||
const response = await invoke(handler) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
expect(response).toEqual(handlerResponse) | ||
}) | ||
const response = await handler() | ||
t.is(response, handlerResponse) | ||
}) | ||
} | ||
describe('It should find the correct serializer from the request accept header', () => { | ||
test.each([ | ||
['application/xml, text/x-dvi; q=0.8, text/x-c', '<message>Hello World</message>'], | ||
['application/json, text/plain, */*', '{"message":"Hello World"}'], | ||
['text/plain, text/x-c', 'Hello World'] | ||
])( | ||
'%s returns %s', | ||
async (accept, result) => { | ||
const handler = middy((event, context, cb) => cb(null, createHttpResponse())) | ||
for (const [accept, result] of [ | ||
['application/xml, text/x-dvi; q=0.8, text/x-c', '<message>Hello World</message>'], | ||
['text/x-dvi; q=0.8, application/xml, text/x-c', '<message>Hello World</message>'], | ||
['text/x-dvi, application/xml, text/x-c', '<message>Hello World</message>'], | ||
['application/json, text/plain, */*', '{"message":"Hello World"}'], | ||
['*/*', '{"message":"Hello World"}'], | ||
['text/x-dvi, */*', '{"message":"Hello World"}'], | ||
['text/plain, text/x-c', 'Hello World'] | ||
]) { | ||
test(`${accept} returns ${result}`, async (t) => { | ||
const handler = middy((event, context) => createHttpResponse()) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const event = { | ||
headers: { | ||
Accept: accept | ||
} | ||
} | ||
const event = { | ||
headers: { | ||
Accept: accept | ||
} | ||
} | ||
const response = await invoke(handler, event) | ||
const response = await handler(event) | ||
expect(response.body).toEqual(result) | ||
}) | ||
t.is(response.body, result) | ||
}) | ||
} | ||
test('It should use `event.requiredContentType` instead of accept headers', async () => { | ||
const handler = middy((event, context, cb) => { | ||
event.requiredContentType = 'text/plain' | ||
test('It should use `event.requiredContentType` instead of accept headers', async (t) => { | ||
const handler = middy((event, context) => { | ||
event.requiredContentType = 'text/plain' | ||
cb(null, createHttpResponse()) | ||
}) | ||
return createHttpResponse() | ||
}) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const event = { | ||
headers: { | ||
Accept: 'application/xml, text/x-dvi; q=0.8, text/x-c' | ||
} | ||
const event = { | ||
headers: { | ||
Accept: 'application/xml, text/x-dvi; q=0.8, text/x-c' | ||
} | ||
} | ||
const response = await invoke(handler, event) | ||
const response = await handler(event) | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'Hello World' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'Hello World' | ||
}) | ||
}) | ||
test('It should use the default when no accept preferences are given', async () => { | ||
const handler = middy((event, context, cb) => | ||
cb(null, createHttpResponse()) | ||
) | ||
test('It should use the default when no accept preferences are given', async (t) => { | ||
const handler = middy((event, context) => createHttpResponse() | ||
) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const response = await invoke(handler) | ||
const response = await handler() | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
}) | ||
test('It should use the default when no matching accept preferences are found', async () => { | ||
const handler = middy((event, context, cb) => { | ||
event.preferredContentType = 'text/java' | ||
test('It should use the default when no matching accept preferences are found', async (t) => { | ||
const handler = middy((event, context) => { | ||
event.preferredContentType = 'text/java' | ||
cb(null, createHttpResponse()) | ||
}) | ||
return createHttpResponse() | ||
}) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const event = { | ||
headers: { | ||
Accept: 'application/java, text/x-dvi; q=0.8, text/x-c' | ||
} | ||
const event = { | ||
headers: { | ||
Accept: 'application/java, text/x-dvi; q=0.8, text/x-c' | ||
} | ||
} | ||
const response = await invoke(handler, event) | ||
const response = await handler(event) | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
}) | ||
test('It should use `event.preferredContentType` instead of the default', async () => { | ||
const handler = middy((event, context, cb) => { | ||
event.preferredContentType = 'text/plain' | ||
test('It should use `event.preferredContentType` instead of the default', async (t) => { | ||
const handler = middy((event, context) => { | ||
event.preferredContentType = 'text/plain' | ||
cb(null, createHttpResponse()) | ||
}) | ||
return createHttpResponse() | ||
}) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const response = await invoke(handler) | ||
const response = await handler() | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'Hello World' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'Hello World' | ||
}) | ||
}) | ||
test('It should pass-through when no preference or default is found', async () => { | ||
const handler = middy((event, context, cb) => | ||
cb(null, createHttpResponse()) | ||
) | ||
test('It should pass-through when no preference or default is found', async (t) => { | ||
const handler = middy((event, context) => createHttpResponse() | ||
) | ||
handler.use(httpResponseSerializer({ | ||
serializers: standardConfiguration.serializers | ||
})) | ||
handler.use(httpResponseSerializer({ | ||
serializers: standardConfiguration.serializers | ||
})) | ||
const response = await invoke(handler) | ||
const response = await handler() | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
body: 'Hello World' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
body: 'Hello World' | ||
}) | ||
}) | ||
test('It should not pass-through when request content-type is set', async () => { | ||
const handler = middy((event, context, cb) => | ||
cb(null, createHttpResponse()) | ||
) | ||
test('It should not pass-through when request content-type is set', async (t) => { | ||
const handler = middy((event, context) => createHttpResponse() | ||
) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const event = { | ||
headers: { | ||
'Content-Type': 'application/xml' | ||
} | ||
const event = { | ||
headers: { | ||
'Content-Type': 'application/xml' | ||
} | ||
} | ||
const response = await invoke(handler, event) | ||
const response = await handler(event) | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{"message":"Hello World"}' | ||
}) | ||
}) | ||
test('It should replace the response object when the serializer returns an object', async () => { | ||
const handler = middy((event, context, cb) => | ||
cb(null, createHttpResponse()) | ||
) | ||
test('It should replace the response object when the serializer returns an object', async (t) => { | ||
const handler = middy((event, context) => createHttpResponse() | ||
) | ||
handler.use(httpResponseSerializer({ | ||
serializers: [ | ||
{ | ||
regex: /^text\/plain$/, | ||
serializer: (response) => (Object.assign({}, response, { | ||
body: Buffer.from(response.body).toString('base64'), | ||
isBase64Encoded: true | ||
})) | ||
} | ||
], | ||
default: 'text/plain' | ||
})) | ||
handler.use(httpResponseSerializer({ | ||
serializers: [ | ||
{ | ||
regex: /^text\/plain$/, | ||
serializer: (response) => (Object.assign({}, response, { | ||
body: Buffer.from(response.body).toString('base64'), | ||
isBase64Encoded: true | ||
})) | ||
} | ||
], | ||
default: 'text/plain' | ||
})) | ||
const response = await invoke(handler) | ||
const response = await handler() | ||
expect(response).toEqual({ | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'SGVsbG8gV29ybGQ=', | ||
isBase64Encoded: true | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: 'SGVsbG8gV29ybGQ=', | ||
isBase64Encoded: true | ||
}) | ||
}) | ||
test('It should work with `http-error-handler` middleware', async () => { | ||
const handler = middy((event, context, cb) => { | ||
throw new createError.UnprocessableEntity() | ||
}) | ||
test('It should work with `http-error-handler` middleware', async (t) => { | ||
const handler = middy((event, context) => { | ||
throw new createError.UnprocessableEntity() | ||
}) | ||
handler | ||
.use(httpErrorHandler()) | ||
.use(httpResponseSerializer(standardConfiguration)) | ||
handler | ||
.use(httpResponseSerializer(standardConfiguration)) | ||
.use(httpErrorHandler({logger:false})) | ||
const response = await invoke(handler) | ||
const response = await handler() | ||
expect(response).toEqual({ | ||
statusCode: 422, | ||
body: '{"message":"Unprocessable Entity"}', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}) | ||
t.deepEqual(response, { | ||
statusCode: 422, | ||
body: 'Unprocessable Entity', | ||
headers: { | ||
'Content-Type': 'plain/text' | ||
} | ||
}) | ||
}) | ||
test('It should not crash if the response is undefined', async () => { | ||
const handler = middy((event, context, cb) => | ||
cb(null, undefined) | ||
) | ||
test('It should not crash if the response is undefined', async (t) => { | ||
const handler = middy((event, context) => undefined) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const event = { | ||
headers: { | ||
'Content-Type': 'application/xml' | ||
} | ||
const event = { | ||
headers: { | ||
'Content-Type': 'application/xml' | ||
} | ||
} | ||
const response = await invoke(handler, event) | ||
const response = await handler(event) | ||
expect(response).toEqual({ | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{}' | ||
}) | ||
t.deepEqual(response, { | ||
headers: { | ||
'Content-Type': standardConfiguration.default | ||
}, | ||
body: '{}' | ||
}) | ||
}) | ||
test('It should return false when response body is falsey', async () => { | ||
const handler = middy((event, context, cb) => { | ||
cb(null, false) | ||
}) | ||
test('It should return false when response body is falsey', async (t) => { | ||
const handler = middy((event, context) => { | ||
return false | ||
}) | ||
const event = { | ||
headers: { | ||
Accept: 'text/plain' | ||
} | ||
const event = { | ||
headers: { | ||
Accept: 'text/plain' | ||
} | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const response = await invoke(handler, event) | ||
} | ||
handler.use(httpResponseSerializer(standardConfiguration)) | ||
const response = await handler(event) | ||
expect(response).toEqual({ | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: false | ||
}) | ||
t.deepEqual(response, { | ||
headers: { | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: false | ||
}) | ||
}) |
115
index.js
@@ -1,73 +0,76 @@ | ||
const Accept = require('@hapi/accept') | ||
const Accept = require('@hapi/accept'); | ||
const getNormalisedHeaders = (source) => Object | ||
.keys(source) | ||
.reduce((destination, key) => { | ||
destination[key.toLowerCase()] = source[key] | ||
const defaults = {}; | ||
return destination | ||
}, {}) | ||
module.exports = (opts = {}) => { | ||
const options = { ...defaults, | ||
...opts | ||
}; | ||
const middleware = (opts, handler, next) => { | ||
// normalise headers for internal use only | ||
const requestHeaders = getNormalisedHeaders((handler.event && handler.event.headers) || {}) | ||
const responseHeaders = getNormalisedHeaders((handler.response && handler.response.headers) || {}) | ||
const httpResponseSerializerMiddlewareAfter = async handler => { | ||
// normalise headers for internal use only | ||
const requestHeaders = getNormalisedHeaders(handler.event?.headers ?? {}); | ||
const responseHeaders = getNormalisedHeaders(handler.response?.headers ?? {}); // skip serialization when content-type is already set | ||
// skip serialization when content-type is already set | ||
if (responseHeaders['content-type']) { | ||
return next() | ||
} | ||
if (responseHeaders['content-type']) { | ||
return; | ||
} // find accept value(s) | ||
// find accept value(s) | ||
let types | ||
if (handler.event.requiredContentType) { | ||
types = [].concat(handler.event.requiredContentType) | ||
} else { | ||
types = [].concat( | ||
(requestHeaders.accept && Accept.mediaTypes(requestHeaders.accept)) || [], | ||
handler.event.preferredContentType || [], | ||
opts.default || [] | ||
) | ||
} | ||
let types; | ||
const handlerEvent = handler.event; | ||
// dont bother finding a serializer if no types are given | ||
if (!types.length) { | ||
return next() | ||
} | ||
if (handlerEvent?.requiredContentType) { | ||
types = [].concat(handlerEvent.requiredContentType); | ||
} else { | ||
types = [].concat(requestHeaders.accept && Accept.mediaTypes(requestHeaders.accept) || [], handlerEvent.preferredContentType || [], options.default || []); | ||
} // dont bother finding a serializer if no types are given | ||
// find in order of first preferred type that has a matching serializer | ||
types.find(type => opts.serializers.find(s => { | ||
const test = s.regex.test(type) | ||
if (!test) { return false } | ||
if (!types.length) { | ||
return; | ||
} // find in order of first preferred type that has a matching serializer | ||
// if the response is not an object, assign it to body. { body: undefined } is not serialized | ||
handler.response = typeof handler.response === 'object' ? handler.response : { body: handler.response } | ||
// set header | ||
handler.response.headers = Object.assign({}, handler.response.headers, { 'Content-Type': type }) | ||
types.find(type => options.serializers.find(s => { | ||
const test = s.regex.test(type); | ||
// run serializer | ||
const result = s.serializer(handler.response) | ||
if (!test) { | ||
return false; | ||
} // if the response is not an object, assign it to body. { body: undefined } is not serialized | ||
if (typeof result === 'object') { | ||
// replace response object if result is object | ||
handler.response = result | ||
} else { | ||
// otherwise only replace the body attribute | ||
handler.response.body = result | ||
} | ||
return true | ||
})) | ||
handler.response = handler.response !== null && typeof handler.response === 'object' ? handler.response : { | ||
body: handler.response | ||
}; // set header | ||
next() | ||
} | ||
handler.response.headers = handler.response?.headers ?? {}; | ||
handler.response.headers = { ...handler.response.headers, | ||
'Content-Type': type | ||
}; // run serializer | ||
module.exports = opts => { | ||
const result = s.serializer(handler.response); | ||
if (typeof result === 'object') { | ||
// replace response object if result is object | ||
handler.response = result; | ||
} else { | ||
// otherwise only replace the body attribute | ||
handler.response.body = result; | ||
} | ||
return true; | ||
})); | ||
}; | ||
const httpResponseSerializerMiddlewareOnError = httpResponseSerializerMiddlewareAfter; | ||
return { | ||
after: (handler, next) => middleware(opts, handler, next), | ||
onError: (handler, next) => middleware(opts, handler, next) | ||
} | ||
} | ||
after: httpResponseSerializerMiddlewareAfter, | ||
onError: httpResponseSerializerMiddlewareOnError | ||
}; | ||
}; | ||
const getNormalisedHeaders = source => Object.keys(source).reduce((destination, key) => { | ||
destination[key.toLowerCase()] = source[key]; | ||
return destination; | ||
}, {}); |
{ | ||
"name": "@middy/http-response-serializer", | ||
"version": "1.5.2", | ||
"version": "2.0.0-alpha.0", | ||
"description": "The Http Serializer middleware lets you define serialization mechanisms based on the current content negotiation.", | ||
"type": "commonjs", | ||
"engines": { | ||
"node": ">=10" | ||
"node": ">=14" | ||
}, | ||
@@ -12,5 +13,7 @@ "engineStrict": true, | ||
}, | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"scripts": { | ||
"test": "npm run test:typings && npm run test:unit", | ||
"test:unit": "jest", | ||
"test:unit": "ava", | ||
"test:typings": "typings-tester --config tsconfig.json index.d.ts" | ||
@@ -40,3 +43,4 @@ }, | ||
"type": "git", | ||
"url": "git+https://github.com/middyjs/middy.git" | ||
"url": "github:middyjs/middy", | ||
"directory": "packages/http-reponse-serializer" | ||
}, | ||
@@ -48,13 +52,6 @@ "bugs": { | ||
"dependencies": { | ||
"@hapi/accept": "^5.0.1", | ||
"http-errors": "^1.7.3" | ||
"@hapi/accept": "5.0.1", | ||
"http-errors": "1.8.0" | ||
}, | ||
"peerDependencies": { | ||
"@middy/core": ">=1.0.0-alpha" | ||
}, | ||
"devDependencies": { | ||
"@middy/core": "^1.5.2", | ||
"es6-promisify": "^6.0.2" | ||
}, | ||
"gitHead": "f77cb5e1fc2c529f4fe74efa8a9fb105373b0723" | ||
"gitHead": "e047c0d3db00aa11b39f2d3e193458ea021a58a0" | ||
} |
# Middy http-response-serializer middleware | ||
<div align="center"> | ||
<img alt="Middy logo" src="https://raw.githubusercontent.com/middyjs/middy/master/img/middy-logo.png"/> | ||
<img alt="Middy logo" src="https://raw.githubusercontent.com/middyjs/middy/master/docs/img/middy-logo.png"/> | ||
</div> | ||
@@ -22,5 +22,2 @@ | ||
</a> | ||
<a href="https://greenkeeper.io/"> | ||
<img src="https://badges.greenkeeper.io/middyjs/middy.svg" alt="Greenkeeper badge" style="max-width:100%;"> | ||
</a> | ||
<a href="https://gitter.im/middyjs/Lobby"> | ||
@@ -109,6 +106,6 @@ <img src="https://badges.gitter.im/gitterHQ/gitter.svg" alt="Chat on Gitter" style="max-width:100%;"> | ||
```javascript | ||
const middy = require('@middy/core') | ||
const httpResponseSerializer = require('@middy/http-response-serializer') | ||
import middy from '@middy/core' | ||
import httpResponseSerializer from '@middy/http-response-serializer' | ||
const handler = middy((event, context, cb) => { | ||
const handler = middy((event, context) => { | ||
const body = 'Hello World' | ||
@@ -148,3 +145,3 @@ | ||
handler(event, {}, (_, response) => { | ||
expect(response.body).toEqual('<message>Hello World</message>') | ||
t.is(response.body,'<message>Hello World</message>') | ||
}) | ||
@@ -166,3 +163,3 @@ ``` | ||
Licensed under [MIT License](LICENSE). Copyright (c) 2017-2018 Luciano Mammino and the [Middy team](https://github.com/middyjs/middy/graphs/contributors). | ||
Licensed under [MIT License](LICENSE). Copyright (c) 2017-2021 Luciano Mammino, will Farrell, and the [Middy team](https://github.com/middyjs/middy/graphs/contributors). | ||
@@ -169,0 +166,0 @@ <a href="https://app.fossa.io/projects/git%2Bgithub.com%2Fmiddyjs%2Fmiddy?ref=badge_large"> |
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
2
0
17294
6
305
1
165
+ Added@hapi/accept@5.0.1(transitive)
+ Addedhttp-errors@1.8.0(transitive)
+ Addedtoidentifier@1.0.0(transitive)
- Removed@hapi/accept@5.0.2(transitive)
- Removed@middy/core@6.0.0(transitive)
- Removedhttp-errors@1.8.1(transitive)
- Removedtoidentifier@1.0.1(transitive)
Updated@hapi/accept@5.0.1
Updatedhttp-errors@1.8.0