| /* | ||
| * @author Marcelo Gornstein <marcelog@gmail.com> | ||
| * @license Apache-2.0 | ||
| * @copyright 2017 Marcelo Gornstein | ||
| */ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var promise = require('promise'); | ||
| var chai = require('chai'); | ||
| var expect = chai.expect; | ||
| var chaiAsPromised = require('chai-as-promised'); | ||
| var m = require('../src/index'); | ||
| chai.use(chaiAsPromised); | ||
| chai.should(); | ||
| describe('generic handler with proxy', function () { | ||
| describe('basic features', function () { | ||
| it('lowercases header names', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, {}); | ||
| }; | ||
| return expect( | ||
| m.handleProxy( | ||
| {headers: {'HEADER': 'value'}}, | ||
| {context: true}, | ||
| callback, | ||
| function (event, context) { | ||
| assert.equal(event.headers['header'], 'value'); | ||
| assert.equal(event.headers['HEADER'], undefined); | ||
| return {}; | ||
| } | ||
| ) | ||
| ).to.be.eventually.fulfilled.and.equal(true); | ||
| }); | ||
| it('handles requests', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, {}); | ||
| }; | ||
| return assertHandler(handler0, callback); | ||
| }); | ||
| it('returns client errors', function () { | ||
| var callback = function (err, result) { | ||
| assertClientError(err, result, {}, 'some_client_error'); | ||
| }; | ||
| return assertHandler(handler2, callback); | ||
| }); | ||
| it('returns success with body', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, { | ||
| result: 'some_result' | ||
| }); | ||
| }; | ||
| return assertHandler(handler1, callback); | ||
| }); | ||
| it('returns custom status code, body, and headers', function () { | ||
| var callback = function (err, result) { | ||
| assertResult(err, result, 999, { | ||
| 'custom-header': 'custom-value' | ||
| }, { | ||
| result: {whatever: 'value'} | ||
| }); | ||
| }; | ||
| return assertHandler(handler3, callback); | ||
| }); | ||
| it('handles unexpected errors', function () { | ||
| var callback = function (err, result) { | ||
| assertInternalError(err, result); | ||
| }; | ||
| return assertHandler(handler4, callback); | ||
| }); | ||
| }); | ||
| }); | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Handler functions for tests. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function handler0 (event, context) { | ||
| assert.deepEqual(event, {event: true, headers: {}}); | ||
| assert.deepEqual(context, {context: true}); | ||
| return true; | ||
| }; | ||
| function handler1 (event, context) { | ||
| return { | ||
| body: { | ||
| result: 'some_result' | ||
| } | ||
| }; | ||
| }; | ||
| function handler2 (event, context) { | ||
| return promise.reject({ | ||
| clientError: true, | ||
| message: 'some_client_error' | ||
| }); | ||
| }; | ||
| function handler3 (event, context) { | ||
| return promise.resolve({ | ||
| statusCode: 999, | ||
| headers: { | ||
| 'custom-header': 'custom-value' | ||
| }, | ||
| body: { | ||
| whatever: 'value' | ||
| } | ||
| }); | ||
| }; | ||
| function handler4 (event, context) { | ||
| throw new Error('oops'); | ||
| }; | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Helper functions for assertions. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function assertHandler(fn, callback) { | ||
| return expect( | ||
| m.handleProxy({event: true}, {context: true}, callback, fn) | ||
| ).to.be.eventually.fulfilled.and.equal(true); | ||
| } | ||
| function assertInternalError(err, result) { | ||
| return assertResult(err, result, 500, {}, {message: 'server_error'}); | ||
| }; | ||
| function assertSuccess(err, result, headers, body) { | ||
| return assertResult(err, result, 200, headers, {result: body}); | ||
| }; | ||
| function assertClientError(err, result, headers, msg) { | ||
| return assertResult(err, result, 400, headers, {message: msg}); | ||
| }; | ||
| function assertResult(err, result, statusCode, headers, body) { | ||
| headers['Access-Control-Allow-Origin'] = '*'; | ||
| headers['Access-Control-Allow-Credentials'] = true; | ||
| assert.equal(err, undefined); | ||
| assert.deepEqual(result, { | ||
| statusCode: statusCode, | ||
| headers: headers, | ||
| body: JSON.stringify(body) | ||
| }); | ||
| return true; | ||
| } |
| /* | ||
| * @author Marcelo Gornstein <marcelog@gmail.com> | ||
| * @license Apache-2.0 | ||
| * @copyright 2017 Marcelo Gornstein | ||
| */ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var promise = require('promise'); | ||
| var chai = require('chai'); | ||
| var expect = chai.expect; | ||
| var chaiAsPromised = require('chai-as-promised'); | ||
| var m = require('../src/index'); | ||
| chai.use(chaiAsPromised); | ||
| chai.should(); | ||
| describe('generic handler without proxy', function () { | ||
| describe('basic features', function () { | ||
| it('handles requests', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, {}); | ||
| }; | ||
| return assertHandler(handler0, callback); | ||
| }); | ||
| it('returns client errors', function () { | ||
| var callback = function (err, result) { | ||
| assertClientError(err, result, {}, ['some_error']); | ||
| }; | ||
| return assertHandler(handler2, callback); | ||
| }); | ||
| it('returns success with body', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, { | ||
| result: 'some_result' | ||
| }); | ||
| }; | ||
| return assertHandler(handler1, callback); | ||
| }); | ||
| it('returns custom body, and headers', function () { | ||
| var callback = function (err, result) { | ||
| assertResult( | ||
| err, | ||
| result, | ||
| undefined, | ||
| { | ||
| headers: {'custom-header': 'custom-value'}, | ||
| body: {whatever: 'value'} | ||
| } | ||
| ); | ||
| }; | ||
| return assertHandler(handler3, callback); | ||
| }); | ||
| it('handles unexpected errors', function () { | ||
| var callback = function (err, result) { | ||
| assertInternalError(err, result); | ||
| }; | ||
| return assertHandler(handler4, callback); | ||
| }); | ||
| }); | ||
| }); | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Handler functions for tests. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function handler0 (event, context) { | ||
| assert.deepEqual(event, {event: true, body: {}, headers: {}}); | ||
| assert.deepEqual(context, {context: true}); | ||
| return {body: {}}; | ||
| }; | ||
| function handler1 (event, context) { | ||
| return { | ||
| body: { | ||
| result: 'some_result' | ||
| } | ||
| }; | ||
| }; | ||
| function handler2 (event, context) { | ||
| return promise.reject({ | ||
| message: 'client_error', | ||
| errors: ['some_error'] | ||
| }); | ||
| }; | ||
| function handler3 (event, context) { | ||
| return promise.resolve({ | ||
| headers: { | ||
| 'custom-header': 'custom-value' | ||
| }, | ||
| body: { | ||
| whatever: 'value' | ||
| } | ||
| }); | ||
| }; | ||
| function handler4 (event, context) { | ||
| throw new Error('oops'); | ||
| }; | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Helper functions for assertions. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function assertHandler(fn, callback) { | ||
| return expect( | ||
| m.handle( | ||
| {event: true, body: JSON.stringify({}), headers: {}}, | ||
| {context: true}, | ||
| callback, | ||
| fn | ||
| ) | ||
| ).to.be.eventually.fulfilled.and.equal(true); | ||
| } | ||
| function assertInternalError(err, result) { | ||
| return assertResult( | ||
| err, | ||
| result, | ||
| JSON.stringify({ | ||
| body: {message: 'server_error', errors: []}, | ||
| headers: {} | ||
| }), | ||
| undefined | ||
| ); | ||
| }; | ||
| function assertSuccess(err, result, headers, body) { | ||
| return assertResult( | ||
| err, result, undefined, {body: body, headers: headers} | ||
| ); | ||
| }; | ||
| function assertClientError(err, result, headers, errors) { | ||
| return assertResult( | ||
| err, | ||
| result, | ||
| JSON.stringify({ | ||
| body: {message: 'client_error', errors: errors}, | ||
| headers: headers | ||
| }), | ||
| undefined | ||
| ); | ||
| }; | ||
| function assertResult(err, result, expectedErr, expectedResult) { | ||
| assert.equal(err, expectedErr); | ||
| assert.deepEqual(result, expectedResult); | ||
| return true; | ||
| } |
+1
-1
| { | ||
| "name": "uservit", | ||
| "description": "Small, thin layer, that serves your serverless microservices generically", | ||
| "version": "0.0.3", | ||
| "version": "0.0.4", | ||
| "keywords": ["serverless", "promise", "microservice"], | ||
@@ -6,0 +6,0 @@ "homepage": "https://github.com/switchpaas/uservit", |
+156
-2
@@ -39,2 +39,5 @@ [](http://img.shields.io/badge/license-APACHE2-blue.svg) | ||
| ---- | ||
| ### Lambda Integration (With Proxy) | ||
| Let's say you want to handle a request with the function `users.get` (that would | ||
@@ -50,3 +53,3 @@ be the function `get` inside the module `users`), you would write your | ||
| exports.hello = function (event, context, callback) { | ||
| return uservit.handle(event, context, callback, users.get); | ||
| return uservit.handleProxy(event, context, callback, users.get); | ||
| }; | ||
@@ -124,6 +127,157 @@ ``` | ||
| { | ||
| "message": "internal_error" | ||
| "message": "server_error" | ||
| } | ||
| ``` | ||
| ---- | ||
| ### Lambda (No Proxy integration) | ||
| To leverage the available features when using the proxy feature, you have to | ||
| add a [mapping template for the request](docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html). | ||
| This is based on the one available at [https://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/](https://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/). | ||
| ## Request template sample | ||
| ``` | ||
| { | ||
| "body" : "$util.escapeJavaScript($input.json('$'))", | ||
| "headers": { | ||
| #foreach($header in $input.params().header.keySet()) | ||
| "$header.toLowerCase()": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end | ||
| #end | ||
| }, | ||
| "method": "$context.httpMethod", | ||
| "params": { | ||
| #foreach($param in $input.params().path.keySet()) | ||
| "$param.toLowerCase()": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end | ||
| #end | ||
| }, | ||
| "query": { | ||
| #foreach($queryParam in $input.params().querystring.keySet()) | ||
| "$queryParam.toLowerCase()": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end | ||
| #end | ||
| } | ||
| } | ||
| ``` | ||
| ## Response template sample | ||
| For the 200 status code we need to return the contents of the `body` field as | ||
| JSON, so this template mapping is required: | ||
| ``` | ||
| $input.json('$.body') | ||
| ``` | ||
| For errors, this generic template will be used: | ||
| ``` | ||
| #set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage'))) | ||
| {"message": "$errorMessageObj.body.message","errors": [#foreach( $e in $errorMessageObj.body.errors )"$e"#if($foreach.hasNext),#end#end]} | ||
| ``` | ||
| And to return custom status codes the following mappings should be added too: | ||
| ``` | ||
| 400: '.*"message":"client_error".*' | ||
| 401: '.*"message":"unauthorized".*' | ||
| 402: '.*"message":"payment_required".*' | ||
| 403: '.*"message":"forbidden".*' | ||
| 404: '.*"message":"not_found".*' | ||
| 429: '.*"message":"throttled".*' | ||
| 500: '((.*Process exited before completing request.*)|(.*server_error.*))' | ||
| ``` | ||
| Let's say you want to handle a request with the function `users.get` (that would | ||
| be the function `get` inside the module `users`), you would write your | ||
| ServerLess service handler like this: | ||
| ```js | ||
| 'use strict'; | ||
| var uservit = require('uservit'); | ||
| var users = require('users'); | ||
| exports.hello = function (event, context, callback) { | ||
| return uservit.handle(event, context, callback, users.get); | ||
| }; | ||
| ``` | ||
| That's it! In your service (the function `users.get`) you can then do: | ||
| ```js | ||
| 'use strict'; | ||
| var promise = require('promise'); | ||
| exports.get = function (event, context) { | ||
| return promise.resolve(). | ||
| then(function () { | ||
| // | ||
| }). | ||
| then(function () { | ||
| // | ||
| }). | ||
| catch(function (err) { | ||
| // ... | ||
| }); | ||
| }; | ||
| ``` | ||
| ### Returning successful responses | ||
| Just return a `body` field with what you want, like: | ||
| ```js | ||
| { | ||
| body: { | ||
| success: true | ||
| } | ||
| } | ||
| ``` | ||
| And the HTTP client will see a payload like: | ||
| ```json | ||
| { | ||
| "result": { | ||
| "success": true | ||
| } | ||
| } | ||
| ``` | ||
| ### Returning custom HTTP status codes and Headers | ||
| ```js | ||
| { | ||
| message: 'payment_required', | ||
| headers: { | ||
| 'custom-header': 'a value' | ||
| } | ||
| } | ||
| ``` | ||
| The pattern for `402` will match and the right HTTP status code will be sent. To | ||
| return the custom header, you will need to map the header in the response like | ||
| `integration.response.body.headers.custom-header`. | ||
| ### Returning client errors | ||
| You can fail your promise and return something like this: | ||
| ```js | ||
| return promise.reject({ | ||
| message: 'client_error', | ||
| errors: ['missing_parameter'] | ||
| }); | ||
| ``` | ||
| And the HTTP client will see an HTTP status code of 400 with a payload like: | ||
| ```json | ||
| { | ||
| "message": "client_error", | ||
| errors: ['missing_parameter'] | ||
| } | ||
| ``` | ||
| ### Unexpected errors | ||
| When something in your code fails, your promises will also be rejected and your | ||
| client will see an HTTP status code 500 with a payload like: | ||
| ```json | ||
| { | ||
| "message": "server_error" | ||
| } | ||
| ``` | ||
| ---- | ||
| # Developers | ||
@@ -130,0 +284,0 @@ This project uses standard [npm scripts](https://docs.npmjs.com/cli/run-script). Current tasks include: |
+37
-2
| 'use strict'; | ||
| var promise = require('promise'); | ||
| exports.handle = function (event, context, callback, fn) { | ||
| exports.handleProxy = function (event, context, callback, fn) { | ||
| var withError = false; | ||
@@ -32,3 +32,3 @@ var ret = { | ||
| ret.statusCode = 500; | ||
| ret.body = {message: 'internal_error'}; | ||
| ret.body = {message: 'server_error'}; | ||
| if (result.clientError) { | ||
@@ -52,1 +52,36 @@ ret.statusCode = 400; | ||
| }; | ||
| exports.handle = function (event, context, callback, fn) { | ||
| event.body = JSON.parse(event.body); | ||
| return promise.resolve().then(function () { | ||
| return fn(event, context); | ||
| }).catch(function (error) { | ||
| var ret = {body: {}, headers: {}}; | ||
| switch (error.message) { | ||
| case 'client_error': | ||
| ret.body.message = 'client_error'; | ||
| ret.body.errors = error.errors; | ||
| break; | ||
| default: | ||
| ret.body.message = 'server_error'; | ||
| ret.body.errors = []; // This is not needed, but without it, the mapping | ||
| // reponse for 500 wont match :\ | ||
| break; | ||
| } | ||
| return ret; | ||
| }).then(function (result) { | ||
| var ret = { | ||
| body: result.body, | ||
| headers: result.headers | ||
| }; | ||
| if (!ret.headers) { | ||
| ret.headers = {}; | ||
| } | ||
| if (Array.isArray(result.body.errors)) { | ||
| callback(JSON.stringify(ret), undefined); | ||
| return true; | ||
| } | ||
| callback(undefined, ret); | ||
| return true; | ||
| }); | ||
| }; |
-151
| /* | ||
| * @author Marcelo Gornstein <marcelog@gmail.com> | ||
| * @license Apache-2.0 | ||
| * @copyright 2017 Marcelo Gornstein | ||
| */ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var promise = require('promise'); | ||
| var chai = require('chai'); | ||
| var expect = chai.expect; | ||
| var chaiAsPromised = require('chai-as-promised'); | ||
| var m = require('../src/index'); | ||
| chai.use(chaiAsPromised); | ||
| chai.should(); | ||
| describe('generic handler', function () { | ||
| describe('basic features', function () { | ||
| it('lowercases header names', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, {}); | ||
| }; | ||
| return expect( | ||
| m.handle( | ||
| {headers: {'HEADER': 'value'}}, | ||
| {context: true}, | ||
| callback, | ||
| function (event, context) { | ||
| assert.equal(event.headers['header'], 'value'); | ||
| assert.equal(event.headers['HEADER'], undefined); | ||
| return {}; | ||
| } | ||
| ) | ||
| ).to.be.eventually.fulfilled.and.equal(true); | ||
| }); | ||
| it('handles requests', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, {}); | ||
| }; | ||
| return assertHandler(handler0, callback); | ||
| }); | ||
| it('returns client errors', function () { | ||
| var callback = function (err, result) { | ||
| assertClientError(err, result, {}, 'some_client_error'); | ||
| }; | ||
| return assertHandler(handler2, callback); | ||
| }); | ||
| it('returns success with body', function () { | ||
| var callback = function (err, result) { | ||
| assertSuccess(err, result, {}, { | ||
| result: 'some_result' | ||
| }); | ||
| }; | ||
| return assertHandler(handler1, callback); | ||
| }); | ||
| it('returns custom status code, body, and headers', function () { | ||
| var callback = function (err, result) { | ||
| assertResult(err, result, 999, { | ||
| 'custom-header': 'custom-value' | ||
| }, { | ||
| result: {whatever: 'value'} | ||
| }); | ||
| }; | ||
| return assertHandler(handler3, callback); | ||
| }); | ||
| it('handles unexpected errors', function () { | ||
| var callback = function (err, result) { | ||
| assertInternalError(err, result); | ||
| }; | ||
| return assertHandler(handler4, callback); | ||
| }); | ||
| }); | ||
| }); | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Handler functions for tests. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function handler0 (event, context) { | ||
| assert.deepEqual(event, {event: true, headers: {}}); | ||
| assert.deepEqual(context, {context: true}); | ||
| return true; | ||
| }; | ||
| function handler1 (event, context) { | ||
| return { | ||
| body: { | ||
| result: 'some_result' | ||
| } | ||
| }; | ||
| }; | ||
| function handler2 (event, context) { | ||
| return promise.reject({ | ||
| clientError: true, | ||
| message: 'some_client_error' | ||
| }); | ||
| }; | ||
| function handler3 (event, context) { | ||
| return promise.resolve({ | ||
| statusCode: 999, | ||
| headers: { | ||
| 'custom-header': 'custom-value' | ||
| }, | ||
| body: { | ||
| whatever: 'value' | ||
| } | ||
| }); | ||
| }; | ||
| function handler4 (event, context) { | ||
| throw new Error('oops'); | ||
| }; | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // Helper functions for assertions. | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| function assertHandler(fn, callback) { | ||
| return expect( | ||
| m.handle({event: true}, {context: true}, callback, fn) | ||
| ).to.be.eventually.fulfilled.and.equal(true); | ||
| } | ||
| function assertInternalError(err, result) { | ||
| return assertResult(err, result, 500, {}, {message: 'internal_error'}); | ||
| }; | ||
| function assertSuccess(err, result, headers, body) { | ||
| return assertResult(err, result, 200, headers, {result: body}); | ||
| }; | ||
| function assertClientError(err, result, headers, msg) { | ||
| return assertResult(err, result, 400, headers, {message: msg}); | ||
| }; | ||
| function assertResult(err, result, statusCode, headers, body) { | ||
| headers['Access-Control-Allow-Origin'] = '*'; | ||
| headers['Access-Control-Allow-Credentials'] = true; | ||
| assert.equal(err, undefined); | ||
| assert.deepEqual(result, { | ||
| statusCode: statusCode, | ||
| headers: headers, | ||
| body: JSON.stringify(body) | ||
| }); | ||
| return true; | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
20961
72.28%9
12.5%362
89.53%309
99.35%1
Infinity%