Skip to content

Commit

Permalink
feat: Response.format method implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
muratcorlu committed Sep 2, 2019
1 parent 49110f2 commit 3383545
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Methods:
| Method | Notes |
|-------------|-------|
| [get()](https://expressjs.com/en/4x/api.html#res.get) | - |
| [format()](https://expressjs.com/en/4x/api.html#res.format) | Doesn't support shorthand mime-types |
| [set()](https://expressjs.com/en/4x/api.html#res.set) | Only supports `key, value` parameters |
| [send()](https://expressjs.com/en/4x/api.html#res.send) | Only supports string values |
| [status()](https://expressjs.com/en/4x/api.html#res.status) | - |
Expand Down
6 changes: 5 additions & 1 deletion src/lambda-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ exports.use = (...handlers) => {
return (event, context, callback) => {
const request = new Request(event);
const response = new Response(request, callback);
request.res = response;

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

handlers.reduce((chain, handler) => chain.then(
() => new Promise((resolve) => handler(request, response, resolve))
() => new Promise((resolve) => {
request.next = resolve;
return handler(request, response, resolve);
})
).catch(callback), Promise.resolve());
}
}
62 changes: 62 additions & 0 deletions src/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,68 @@ class Response {
return this.responseObj.headers[key.toLowerCase()];
}

/**
* Performs content-negotiation on the Accept HTTP header
* on the request object, when present. It uses `req.accepts()`
* to select a handler for the request, based on the acceptable
* types ordered by their quality values. If the header is not
* specified, the first callback is invoked. When no match is
* found, the server responds with 406 “Not Acceptable”, or invokes
* the `default` callback.
*
* The `Content-Type` response header is set when a callback is
* selected. However, you may alter this within the callback using
* methods such as `res.set()` or `res.type()`.
*
* The following example would respond with `{ "message": "hey" }`
* when the `Accept` header field is set to “application/json”
* or “*\/json” (however if it is “*\/*”, then the response will
* be “hey”).
*
* res.format({
* 'text/plain': function(){
* res.send('hey');
* },
*
* 'text/html': function(){
* res.send('<p>hey</p>');
* },
*
* 'appliation/json': function(){
* res.send({ message: 'hey' });
* }
* });
*
* By default it passes an `Error`
* with a `.status` of 406 to `next(err)`
* if a match is not made. If you provide
* a `.default` callback it will be invoked
* instead.
*
* @param {Object} obj
* @return {ServerResponse} for chaining
* @public
*/
format(obj) {
const defaultFn = obj.default;
const types = Object.keys(obj);
const chosenType = this.req.accepts(types);

if (chosenType) {
this.type(chosenType);
obj[chosenType](this.req, this, this.req.next);
} else if (defaultFn) {
return defaultFn(this.req, this, this.req.next);
} else {
var err = new Error('Not Acceptable');
err.status = err.statusCode = 406;
err.types = types;
this.req.next(err);
}

return this;
}

/**
* 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().
*
Expand Down
128 changes: 128 additions & 0 deletions src/response.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { Response } = require('./response');
const { Request } = require('./request');

describe('Response object', () => {
it('set response status properly', () => {
Expand Down Expand Up @@ -84,5 +85,132 @@ describe('Response object', () => {

response.type('text/xml').end();
});

describe('should send correct response via accept header', () => {
it('with regular header', () => {

const event = {
headers: {
'Accept': 'text/xml',
'Content-Length': 0
},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/path',
pathParameters: { },
queryStringParameters: { },
multiValueQueryStringParameters: { },
stageVariables: { },
requestContext: {},
resource: ''
};

const req = new Request(event);
req.next = (error) => {

};

const cb = (err, response) => {
expect(response.statusCode).toBe(200);
expect(response.headers['content-type']).toBe('text/xml');
expect(response.body).toBe('<xml/>');
};

const response = new Response(req, cb);

response.format({
'application/json': (req, res, next) => {
res.json({a: 1});
},
'text/xml': (req, res, next) => {
res.send('<xml/>');
}
});
});

it('without regular header', () => {

const event = {
headers: {
'Accept': 'text/html',
'Content-Length': 0
},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/path',
pathParameters: { },
queryStringParameters: { },
multiValueQueryStringParameters: { },
stageVariables: { },
requestContext: {},
resource: ''
};

const req = new Request(event);
req.next = (error) => {

};

const cb = (err, response) => {
expect(response.statusCode).toBe(200);
expect(response.headers['content-type']).toBe('text/html');
expect(response.body).toBe('<p>hi</p>');
};

const response = new Response(req, cb);

response.format({
'application/json': (req, res, next) => {
res.json({a: 1});
},
'text/xml': (req, res, next) => {
res.send('<xml/>');
},
'default': (req, res, next) => {
res.type('text/html').send('<p>hi</p>');
}
});
});


it('with non acceptable accept header', () => {

const event = {
headers: {
'Accept': 'image/jpeg',
'Content-Length': 0
},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/path',
pathParameters: { },
queryStringParameters: { },
multiValueQueryStringParameters: { },
stageVariables: { },
requestContext: {},
resource: ''
};

const req = new Request(event);
req.next = (error) => {
expect(error.status).toBe(406);
expect(error).toEqual(Error('Not Acceptable'));
};

const cb = (err, response) => {
};

const response = new Response(req, cb);

response.format({
'application/json': (req, res, next) => {
res.json({a: 1});
}
});
});
})
});

0 comments on commit 3383545

Please sign in to comment.