Skip to content

Commit 3383545

Browse files
committed
feat: Response.format method implemented
1 parent 49110f2 commit 3383545

File tree

4 files changed

+196
-1
lines changed

4 files changed

+196
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Methods:
112112
| Method | Notes |
113113
|-------------|-------|
114114
| [get()](https://expressjs.com/en/4x/api.html#res.get) | - |
115+
| [format()](https://expressjs.com/en/4x/api.html#res.format) | Doesn't support shorthand mime-types |
115116
| [set()](https://expressjs.com/en/4x/api.html#res.set) | Only supports `key, value` parameters |
116117
| [send()](https://expressjs.com/en/4x/api.html#res.send) | Only supports string values |
117118
| [status()](https://expressjs.com/en/4x/api.html#res.status) | - |

src/lambda-wrapper.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ exports.use = (...handlers) => {
55
return (event, context, callback) => {
66
const request = new Request(event);
77
const response = new Response(request, callback);
8+
request.res = response;
89

910
handlers = [].concat(...handlers);
1011

1112
handlers.reduce((chain, handler) => chain.then(
12-
() => new Promise((resolve) => handler(request, response, resolve))
13+
() => new Promise((resolve) => {
14+
request.next = resolve;
15+
return handler(request, response, resolve);
16+
})
1317
).catch(callback), Promise.resolve());
1418
}
1519
}

src/response.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,68 @@ class Response {
3434
return this.responseObj.headers[key.toLowerCase()];
3535
}
3636

37+
/**
38+
* Performs content-negotiation on the Accept HTTP header
39+
* on the request object, when present. It uses `req.accepts()`
40+
* to select a handler for the request, based on the acceptable
41+
* types ordered by their quality values. If the header is not
42+
* specified, the first callback is invoked. When no match is
43+
* found, the server responds with 406 “Not Acceptable”, or invokes
44+
* the `default` callback.
45+
*
46+
* The `Content-Type` response header is set when a callback is
47+
* selected. However, you may alter this within the callback using
48+
* methods such as `res.set()` or `res.type()`.
49+
*
50+
* The following example would respond with `{ "message": "hey" }`
51+
* when the `Accept` header field is set to “application/json”
52+
* or “*\/json” (however if it is “*\/*”, then the response will
53+
* be “hey”).
54+
*
55+
* res.format({
56+
* 'text/plain': function(){
57+
* res.send('hey');
58+
* },
59+
*
60+
* 'text/html': function(){
61+
* res.send('<p>hey</p>');
62+
* },
63+
*
64+
* 'appliation/json': function(){
65+
* res.send({ message: 'hey' });
66+
* }
67+
* });
68+
*
69+
* By default it passes an `Error`
70+
* with a `.status` of 406 to `next(err)`
71+
* if a match is not made. If you provide
72+
* a `.default` callback it will be invoked
73+
* instead.
74+
*
75+
* @param {Object} obj
76+
* @return {ServerResponse} for chaining
77+
* @public
78+
*/
79+
format(obj) {
80+
const defaultFn = obj.default;
81+
const types = Object.keys(obj);
82+
const chosenType = this.req.accepts(types);
83+
84+
if (chosenType) {
85+
this.type(chosenType);
86+
obj[chosenType](this.req, this, this.req.next);
87+
} else if (defaultFn) {
88+
return defaultFn(this.req, this, this.req.next);
89+
} else {
90+
var err = new Error('Not Acceptable');
91+
err.status = err.statusCode = 406;
92+
err.types = types;
93+
this.req.next(err);
94+
}
95+
96+
return this;
97+
}
98+
3799
/**
38100
* Sends a JSON response. This method sends a response (with the correct content-type) that is the parameter converted to a JSON string using JSON.stringify().
39101
*

src/response.spec.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { Response } = require('./response');
2+
const { Request } = require('./request');
23

34
describe('Response object', () => {
45
it('set response status properly', () => {
@@ -84,5 +85,132 @@ describe('Response object', () => {
8485

8586
response.type('text/xml').end();
8687
});
88+
89+
describe('should send correct response via accept header', () => {
90+
it('with regular header', () => {
91+
92+
const event = {
93+
headers: {
94+
'Accept': 'text/xml',
95+
'Content-Length': 0
96+
},
97+
multiValueHeaders: {},
98+
httpMethod: 'POST',
99+
isBase64Encoded: false,
100+
path: '/path',
101+
pathParameters: { },
102+
queryStringParameters: { },
103+
multiValueQueryStringParameters: { },
104+
stageVariables: { },
105+
requestContext: {},
106+
resource: ''
107+
};
108+
109+
const req = new Request(event);
110+
req.next = (error) => {
111+
112+
};
113+
114+
const cb = (err, response) => {
115+
expect(response.statusCode).toBe(200);
116+
expect(response.headers['content-type']).toBe('text/xml');
117+
expect(response.body).toBe('<xml/>');
118+
};
119+
120+
const response = new Response(req, cb);
121+
122+
response.format({
123+
'application/json': (req, res, next) => {
124+
res.json({a: 1});
125+
},
126+
'text/xml': (req, res, next) => {
127+
res.send('<xml/>');
128+
}
129+
});
130+
});
131+
132+
it('without regular header', () => {
133+
134+
const event = {
135+
headers: {
136+
'Accept': 'text/html',
137+
'Content-Length': 0
138+
},
139+
multiValueHeaders: {},
140+
httpMethod: 'POST',
141+
isBase64Encoded: false,
142+
path: '/path',
143+
pathParameters: { },
144+
queryStringParameters: { },
145+
multiValueQueryStringParameters: { },
146+
stageVariables: { },
147+
requestContext: {},
148+
resource: ''
149+
};
150+
151+
const req = new Request(event);
152+
req.next = (error) => {
153+
154+
};
155+
156+
const cb = (err, response) => {
157+
expect(response.statusCode).toBe(200);
158+
expect(response.headers['content-type']).toBe('text/html');
159+
expect(response.body).toBe('<p>hi</p>');
160+
};
161+
162+
const response = new Response(req, cb);
163+
164+
response.format({
165+
'application/json': (req, res, next) => {
166+
res.json({a: 1});
167+
},
168+
'text/xml': (req, res, next) => {
169+
res.send('<xml/>');
170+
},
171+
'default': (req, res, next) => {
172+
res.type('text/html').send('<p>hi</p>');
173+
}
174+
});
175+
});
176+
177+
178+
it('with non acceptable accept header', () => {
179+
180+
const event = {
181+
headers: {
182+
'Accept': 'image/jpeg',
183+
'Content-Length': 0
184+
},
185+
multiValueHeaders: {},
186+
httpMethod: 'POST',
187+
isBase64Encoded: false,
188+
path: '/path',
189+
pathParameters: { },
190+
queryStringParameters: { },
191+
multiValueQueryStringParameters: { },
192+
stageVariables: { },
193+
requestContext: {},
194+
resource: ''
195+
};
196+
197+
const req = new Request(event);
198+
req.next = (error) => {
199+
expect(error.status).toBe(406);
200+
expect(error).toEqual(Error('Not Acceptable'));
201+
};
202+
203+
const cb = (err, response) => {
204+
};
205+
206+
const response = new Response(req, cb);
207+
208+
response.format({
209+
'application/json': (req, res, next) => {
210+
res.json({a: 1});
211+
}
212+
});
213+
});
214+
})
87215
});
88216

0 commit comments

Comments
 (0)