├── .babelrc ├── .codeclimate.yml ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── build ├── app.js ├── router.js ├── runner.js └── util.js ├── docs ├── ast │ └── source │ │ └── src │ │ ├── app.js.json │ │ ├── lambdas │ │ ├── apples │ │ │ └── index.js.json │ │ ├── oranges │ │ │ └── index.js.json │ │ └── runner.js.json │ │ ├── lib │ │ ├── app.js.json │ │ └── runner.js.json │ │ ├── router.js.json │ │ ├── runner.js.json │ │ ├── server.js.json │ │ └── util.js.json ├── class │ └── src │ │ ├── app.js~app.html │ │ └── lib │ │ └── app.js~App.html ├── coverage.json ├── css │ ├── prettify-tomorrow.css │ └── style.css ├── dump.json ├── file │ └── src │ │ ├── app.js.html │ │ ├── lambdas │ │ ├── apples │ │ │ └── index.js.html │ │ ├── oranges │ │ │ └── index.js.html │ │ └── runner.js.html │ │ ├── lib │ │ ├── app.js.html │ │ └── runner.js.html │ │ ├── router.js.html │ │ ├── runner.js.html │ │ ├── server.js.html │ │ └── util.js.html ├── identifiers.html ├── image │ ├── github.png │ └── search.png ├── index.html ├── script │ ├── inherited-summary.js │ ├── inner-link.js │ ├── patch-for-local.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── pretty-print.js │ ├── search.js │ ├── search_index.js │ └── test-summary.js ├── source.html └── variable │ └── index.html ├── esdoc.json ├── package.json ├── src ├── app.js ├── router.js ├── runner.js └── util.js └── test ├── gateway.yml ├── index.js ├── lambdas └── foo │ └── index.js ├── setup.js └── src ├── app.spec.js ├── router.spec.js └── util.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 1, 3 | "optional": [ "es7.asyncFunctions" ] 4 | } -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | exclude_paths: 4 | - "docs/**/*" 5 | - "build/**/*" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "blockBindings": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "destructuring": true, 13 | "forOf": true, 14 | "generators": false, 15 | "modules": true, 16 | "objectLiteralComputedProperties": true, 17 | "objectLiteralDuplicateProperties": false, 18 | "objectLiteralShorthandMethods": true, 19 | "objectLiteralShorthandProperties": true, 20 | "spread": true, 21 | "superInFunctions": true, 22 | "templateStrings": true, 23 | "jsx": true 24 | }, 25 | "rules": { 26 | /** 27 | * Strict mode 28 | */ 29 | // babel inserts "use strict"; for us 30 | // http://eslint.org/docs/rules/strict 31 | "strict": [2, "never"], 32 | 33 | /** 34 | * ES6 35 | */ 36 | "no-var": 2, // http://eslint.org/docs/rules/no-var 37 | 38 | /** 39 | * Variables 40 | */ 41 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 42 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 43 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 44 | "vars": "local", 45 | "args": "after-used" 46 | }], 47 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define 48 | 49 | /** 50 | * Possible errors 51 | */ 52 | "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle 53 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 54 | "no-console": 1, // http://eslint.org/docs/rules/no-console 55 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 56 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 57 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 58 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 59 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 60 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 61 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 62 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 63 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 64 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 65 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 66 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 67 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 68 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 69 | "quote-props": 2, // http://eslint.org/docs/rules/no-reserved-keys 70 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 71 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 72 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 73 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var 74 | 75 | /** 76 | * Best practices 77 | */ 78 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 79 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 80 | "default-case": 2, // http://eslint.org/docs/rules/default-case 81 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 82 | "allowKeywords": true 83 | }], 84 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 85 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in 86 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 87 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 88 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 89 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 90 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 91 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 92 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 93 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 94 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 95 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 96 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 97 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 98 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 99 | "no-new": 2, // http://eslint.org/docs/rules/no-new 100 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 101 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 102 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 103 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 104 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 105 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 106 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 107 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 108 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 109 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 110 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 111 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 112 | "no-with": 2, // http://eslint.org/docs/rules/no-with 113 | "radix": 2, // http://eslint.org/docs/rules/radix 114 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 115 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 116 | "yoda": 2, // http://eslint.org/docs/rules/yoda 117 | 118 | /** 119 | * Style 120 | */ 121 | "indent": [2, 2], // http://eslint.org/docs/rules/ 122 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 123 | "1tbs", { 124 | "allowSingleLine": true 125 | }], 126 | "quotes": [ 127 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 128 | ], 129 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 130 | "properties": "never" 131 | }], 132 | "quote-props": 0, 133 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 134 | "before": false, 135 | "after": true 136 | }], 137 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 138 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 139 | "func-names": 1, // http://eslint.org/docs/rules/func-names 140 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 141 | "beforeColon": false, 142 | "afterColon": true 143 | }], 144 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 145 | "newIsCap": true 146 | }], 147 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 148 | "max": 2 149 | }], 150 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 151 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 152 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 153 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 154 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 155 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 156 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 157 | "semi": [ 2, "never" ], // http://eslint.org/docs/rules/semi 158 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 159 | "before": false, 160 | "after": true 161 | }], 162 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 163 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 164 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 165 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 166 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 167 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment 168 | } 169 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | logs/ 3 | **/*.log 4 | report 5 | coverage 6 | 7 | # Wercker 8 | _builds 9 | _projects 10 | _steps -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "asi": true 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | - "0.10" 6 | script: 7 | - npm run cover 8 | after_success: 9 | - npm install -g codeclimate-test-reporter 10 | - codeclimate-test-reporter < coverage/lcov.info -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 TechnologyAdvice 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | # Paths 4 | DEPS = ./node_modules 5 | BIN = $(DEPS)/.bin 6 | SRC = ./src 7 | BUILD = ./build 8 | DOCS = ./docs 9 | PKG = ./package.json 10 | 11 | # Tests 12 | TESTS = ./test/src/ 13 | SPACE := 14 | SPACE += 15 | # Default to recursive, can override on run 16 | FILE = $(SPACE)--recursive 17 | # ARGS 18 | T_ARGS = --compilers js:babel/register $(TESTS)$(FILE) 19 | 20 | # Deploy 21 | TAG = 0 22 | TAG_CMD = npm version $(TAG) && \ 23 | git push origin master && \ 24 | git push --tags 25 | 26 | # Make things more readable 27 | define colorecho 28 | @tput setaf 2 29 | @tput bold 30 | @printf "\n" 31 | @echo $1 32 | @echo ========================================== 33 | @tput sgr0 34 | endef 35 | 36 | # Tasks 37 | 38 | clean: 39 | $(call colorecho, "Cleaning $(BUILD) and $(DEPS)") 40 | rm -rf $(DEPS) 41 | 42 | install: $(PKG) 43 | $(call colorecho, "Installing") 44 | npm i . 45 | 46 | lint: 47 | $(call colorecho, "Linting $(SRC)") 48 | $(BIN)/eslint $(SRC) 49 | 50 | test: 51 | # Need to prebuild runner for integration tests 52 | $(call colorecho, "Building runner for integration tests") 53 | $(BIN)/babel $(SRC)/runner.js --out-file $(BUILD)/runner.js 54 | $(call colorecho, "Testing $(TESTS)$(FILE)") 55 | $(BIN)/mocha $(T_ARGS) 56 | 57 | cover: 58 | $(call colorecho, "Running coverage report") 59 | $(BIN)/istanbul cover $(BIN)/_mocha -- $(T_ARGS) 60 | 61 | 62 | build: 63 | $(call colorecho, "Building $(SRC) to $(BUILD)") 64 | $(BIN)/babel $(SRC) --out-dir $(BUILD) 65 | 66 | start: 67 | $(call colorecho, "Starting...") 68 | node test/index.js 69 | 70 | doc: 71 | $(call colorecho, "Building Docs") 72 | $(BIN)/esdoc -c esdoc.json 73 | 74 | report: 75 | $(call colorecho, "Running Static Analysis") 76 | $(BIN)/plato -r -d report $(BUILD) 77 | 78 | tag: 79 | $(call colorecho, "Deploying to Git") 80 | $(TAG_CMD) 81 | 82 | deploy: lint test build doc tag 83 | 84 | dev: lint test build start 85 | 86 | watch: 87 | $(call colorecho, "Starting watch") 88 | $(BIN)/nodemon --exec "make dev" --watch $(SRC) 89 | 90 | all: clean install lint test build doc report 91 | 92 | 93 | # Phonies 94 | .PHONY: lint test doc build start report deploy -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/TechnologyAdvice/glambda.svg?branch=master)](https://travis-ci.org/TechnologyAdvice/glambda) 2 | [![Code Climate](https://codeclimate.com/github/TechnologyAdvice/glambda/badges/gpa.svg)](https://codeclimate.com/github/TechnologyAdvice/glambda) 3 | [![Test Coverage](https://codeclimate.com/github/TechnologyAdvice/glambda/badges/coverage.svg)](https://codeclimate.com/github/TechnologyAdvice/glambda/coverage) 4 | [![Dependency Status](https://www.versioneye.com/user/projects/55bd0f716537620017001fa2/badge.svg?style=flat)](https://www.versioneye.com/user/projects/55bd0f716537620017001fa2) 5 | 6 | [![NPM](https://nodei.co/npm/glambda.png)](https://www.npmjs.com/package/glambda) 7 | 8 | # GLambda 9 | 10 | ### AWS Gateway + Lambda Testing Module 11 | 12 | A module for mocking and testing AWS [API Gateway](http://aws.amazon.com/api-gateway/) 13 | in conjunction with [Lambda](http://aws.amazon.com/lambda/) functions. 14 | 15 | ## Introduction 16 | 17 | ## Setup 18 | 19 | Install the module: 20 | 21 | ``` 22 | npm install glambda --save-dev 23 | ``` 24 | 25 | To see a fully functional demo, see the [`/test`](/test) directory. The `index.js` 26 | file is setup to run using the [`lambdas`](/test/lambdas) and the [`gateway.yml`](/test/gateway.yml) 27 | file. The tests run against this configuration as well. 28 | 29 | After installing the npm module simply include it in a file where it will run and 30 | set any config options on `init`: 31 | 32 | ```javascript 33 | // Include the module 34 | var glambda = require('glambda') 35 | // Set options and init 36 | glambda.init({ 37 | lambdas: './lambdas', 38 | schema: './gateway.yml', 39 | port: 8181, 40 | apiPath: '/api', 41 | log: true, 42 | cors: { 43 | origin: '*', 44 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 45 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 46 | } 47 | }) 48 | ``` 49 | 50 | The above shows a standard set of config options: 51 | 52 | * `lambdas`: Path to the directory containing lambdas 53 | * `schema`: Path to the API gateway YAML config 54 | * `port`: Port on which the HTTP server will run 55 | * `apiPath`: Any path (proceeding root) to include in HTTP requests mapping 56 | * `log`: Wether or not to log to console 57 | 58 | Simply running the file created above will spin up the service, then accessing 59 | the endpoints via the corresponding lambda name will spawn the Lambda function 60 | and return its results. 61 | 62 | **Environment Variables** 63 | 64 | The system runs a configuration load process which uses the default values, 65 | overrides with any initialized (passed) config properties and (lastly) checks 66 | for environment variables following the convention `GL_{PROPERTY}`. 67 | 68 | *Note: CORS settings don't currently support environment variables* 69 | 70 | ## The Gateway YAML Configuration 71 | 72 | The [`gateway.yml`](/test/gateway.yml) format was designed to closely match the 73 | AWS [API Gateway](http://aws.amazon.com/api-gateway/). The structure is intended 74 | to appear similar to the Resource (left-hand) pane when editing an API in the 75 | web interface. 76 | 77 | ```YAML 78 | --- 79 | /: 80 | /foo: 81 | GET: 82 | lambda: "foo" 83 | templates: 84 | application/json: 85 | method: "get" 86 | POST: 87 | lambda: "foo" 88 | templates: 89 | application/json: 90 | method: "post" 91 | body: "$input.json('$')" 92 | /foo/{fooId}: 93 | GET: 94 | lambda: "foo" 95 | templates: 96 | application/json: 97 | id: "$input.params('fooId')" 98 | method: "get" 99 | PUT: 100 | lambda: "foo" 101 | templates: 102 | application/json: 103 | id: "$input.params('fooId')" 104 | baz: "quz" 105 | body: "$input.json('$')" 106 | ``` 107 | 108 | It's simple to identify the core nodes of the tree, i.e. the paths of the requests 109 | and their associated methods. To explain, the following shows results of a number 110 | of requests made against the above configuration: 111 | 112 | | PATH | METHOD | BODY | RESPONSE/EVENT | 113 | | -------- | ------ | -------------------- | --------------------------------------------------------- | 114 | | / | ANY | N/A | `METHOD NOT ALLOWED` | 115 | | /foo | GET | N/A | `{ method: 'get' }` | 116 | | /foo | POST | `{ fizz: 'buzz' }` | `{ method: 'post', body: { fizz: 'buzz' }` | 117 | | /foo/123 | GET | N/A | `{ method: 'get', fooId: 123 }` | 118 | | /foo/123 | PUT | `{ baz: 'quz' }` | `{ method: 'put', fooId: 123, body: { baz: 'quz' } }` | 119 | 120 | ## Logging 121 | 122 | GLambda will output information from both the service and the Lambdas. An example 123 | of the Lambda-specific log output is below: 124 | 125 | ``` 126 | [2015-08-02 14:26:46] INFO: Lambda Processed (...) 127 | lambda: foo 128 | event: {"method":"get"} 129 | pid: 30945 130 | memory: { rss: 20062208, heapTotal: 9751808, heapUsed: 3989464 } 131 | time: 0.124 132 | ``` 133 | 134 | ## Notes 135 | 136 | ### Gateway Templates 137 | 138 | Currently Glambda only supports a single template which must be `application/json`. 139 | The plan is to expand on this, see [Support Multiple Templates](https://github.com/TechnologyAdvice/glambda/issues/2) 140 | issue for more information. 141 | 142 | ## Makefile and Scripts 143 | 144 | A `Makefile` is included for managing build and install tasks. The commands are 145 | then referenced in the `package.json` `scripts` if that is the preferred 146 | task method: 147 | 148 | * `all` (default) will run all build tasks 149 | * `start` will run the main script 150 | * `clean` will remove the `/node_modules` directories 151 | * `build` will transpile ES2015 code in `/src` to `/build` 152 | * `test` will run all spec files in `/test/src` 153 | * `cover` will run code coverage on all tests 154 | * `lint` will lint all files in `/src` 155 | * `doc` will run ESDoc on all files in `/src` and output to `/docs` 156 | * `report` will run Plato static analysis on `/build` and output to `/report` 157 | * `dev` will run... 158 | * linting, then... 159 | * tests, then... 160 | * build/transpile, then... 161 | * the main script. 162 | * `watch` will run the `dev` task and rerun on change of `/src` files 163 | 164 | **Test Inidividual File** 165 | 166 | An individual spec can be run by specifying the `FILE`: 167 | 168 | ``` 169 | make test FILE=some.spec.js 170 | ``` 171 | 172 | The `FILE` is relative to the `test/src/` directory. 173 | 174 | **Deploys** 175 | 176 | For deploying releases, the `deploy TAG={VERSION}` can be used where `VERSION` can be: 177 | 178 | ``` 179 | | major | minor | patch | premajor 180 | ``` 181 | 182 | Both `make {COMMAND}` and `npm run {COMMAND}` work for any of the above commands. 183 | 184 | ## License 185 | 186 | Glambda is licensed under the MIT license. Please see `LICENSE.txt` for full details. 187 | 188 | ## Credits 189 | 190 | Glambda was designed and created at [TechnologyAdvice](http://www.technologyadvice.com). -------------------------------------------------------------------------------- /build/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | // Deps 6 | 'use strict'; 7 | 8 | Object.defineProperty(exports, '__esModule', { 9 | value: true 10 | }); 11 | 12 | // Import router 13 | 14 | var _router = require('./router'); 15 | 16 | var _util = require('./util'); 17 | 18 | /** 19 | * Allows overriding default runner script 20 | * @param {String} runnerPath Path to the runner module 21 | */ 22 | var express = require('express'); 23 | var bodyParser = require('body-parser'); 24 | var fork = require('child_process').fork; 25 | var path = require('path'); 26 | var _ = require('lodash'); 27 | 28 | // Setup logs 29 | var log = require('bristol'); 30 | exports.log = log; 31 | log.addTarget('console').withFormatter('commonInfoModel'); 32 | log.addTransform(function (elem) { 33 | delete elem.file; 34 | delete elem.line; 35 | return elem; 36 | }); 37 | 38 | // Default path to lambda runner 39 | var runner = path.resolve(__dirname, './runner');var setRunner = function setRunner(runnerPath) { 40 | return runner = path.resolve(runnerPath); 41 | }; 42 | 43 | exports.setRunner = setRunner; 44 | /** 45 | * Default config object 46 | * @property config 47 | * @attribute {String} lambdas The path to the lambdas directory 48 | * @attribute {String} schema The path to the Gateway YAML config 49 | * @attribute {Number} port The port for the HTTP service 50 | * @attribute {String} apiPath The request path for the api 51 | * @attribute {Boolean} log Show or repress console output 52 | */ 53 | var config = { 54 | lambdas: './lambdas', 55 | schema: './gateway.yml', 56 | port: 8181, 57 | apiPath: '/api', 58 | log: true, 59 | cors: { 60 | origin: '*', 61 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 62 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 63 | } 64 | }; 65 | 66 | exports.config = config; 67 | // Express setup 68 | var service = express(); 69 | 70 | exports.service = service; 71 | // CORS 72 | var setCORS = function setCORS() { 73 | service.use(function (req, res, next) { 74 | res.header('Access-Control-Allow-Origin', config.cors.origin); 75 | res.header('Access-Control-Allow-Methods', config.cors.methods); 76 | res.header('Access-Control-Allow-Headers', config.cors.headers); 77 | if (req.method === 'OPTIONS') { 78 | res.send(200); 79 | } else { 80 | next(); 81 | } 82 | }); 83 | }; 84 | 85 | exports.setCORS = setCORS; 86 | // Body parser 87 | service.use(bodyParser.json()); 88 | 89 | /** 90 | * Calls log output method 91 | * @param {String} type Type of log message to write 92 | * @param {String|Object} msg Message body of log 93 | */ 94 | var procLog = function procLog(type) { 95 | /* istanbul ignore if */ 96 | if (config.log) log[type](arguments[1], arguments[2]); 97 | }; 98 | 99 | exports.procLog = procLog; 100 | /** 101 | * Parses response body for error code 102 | * @param {String} output Output from context.fail 103 | */ 104 | var parseErrorCode = function parseErrorCode(output) { 105 | var code = output.toString().replace(/^Error: ([1-5]\d\d).+$/, function (i, match) { 106 | return match; 107 | }); 108 | if (code > 100 && code < 600) { 109 | // Return specific code with stripped message 110 | return { 111 | code: parseInt(code, 10), 112 | output: output.replace('Error: ' + code, '').trim() 113 | }; 114 | } 115 | // Return generic 500 with original message 116 | return { 117 | code: 500, 118 | output: code 119 | }; 120 | }; 121 | 122 | exports.parseErrorCode = parseErrorCode; 123 | /** 124 | * Handles response from forked lambda runner procs 125 | * @param {Object} msg Message object from child proc 126 | * @param {Object} [res] Express response object 127 | */ 128 | var procResponse = function procResponse(msg, res) { 129 | switch (msg.type) { 130 | case 'metric': 131 | procLog('info', 'Lambda Processed', msg.output);break; 132 | case 'debug': 133 | procLog('info', 'Lambda Debug', msg.output);break; 134 | case 'success': 135 | res.status(200).send(msg.output);break; 136 | case 'error': 137 | var err = parseErrorCode(msg.output); 138 | res.status(err.code).send(err.output); 139 | break; 140 | default: 141 | procLog('error', 'Missing response type'); 142 | } 143 | }; 144 | 145 | exports.procResponse = procResponse; 146 | /** 147 | * Parses the properties from the template and then calls `parseRequestParams` 148 | * to align variable properties with their template keys 149 | * @param {Object} req The request object 150 | * @param {Object} template The gateway template 151 | * @returns {Object} the full event to be passed to the Lambda 152 | */ 153 | var parseRequest = function parseRequest(req, template) { 154 | var tmpBody = {}; 155 | for (var prop in template) { 156 | /* istanbul ignore else */ 157 | if (({}).hasOwnProperty.call(template, prop)) { 158 | tmpBody[prop] = (0, _util.parseRequestParams)(template[prop], req); 159 | } 160 | } 161 | return tmpBody; 162 | }; 163 | 164 | exports.parseRequest = parseRequest; 165 | /** 166 | * Builds the `event` payload with the request body and then forks a new 167 | * runner process to the requested lambda. Awaits messaging from the lambda 168 | * to return response payload and display log information 169 | * @param {String} lambda The lambda to run 170 | * @param {Object} template The gateway template 171 | * @param {Object} req Express req object 172 | * @param {Object} res Express res object 173 | */ 174 | var runLambda = function runLambda(lambda, template, req, res) { 175 | // Parse body against template 176 | var body = parseRequest(req, template); 177 | // Build event by extending body with params 178 | var event = JSON.stringify(_.extend(body, req.params)); 179 | // Build env to match current envirnment, add lambdas and event 180 | var env = _.extend({ lambdas: config.lambdas, event: event }, process.env); 181 | // Execute lambda 182 | fork(runner, [lambda], { env: env }).on('message', function (msg) { 183 | return procResponse(msg, res); 184 | }); 185 | }; 186 | 187 | exports.runLambda = runLambda; 188 | /** 189 | * Combines the default config with any passed to init and overrides (lastly) 190 | * if there are any environment variables set 191 | * @param {Object} [cfg] The config passed through init 192 | */ 193 | var buildConfig = function buildConfig(cfg) { 194 | // Against defaults 195 | _.extend(config, cfg); 196 | // Against env vars 197 | for (var prop in config) { 198 | /* istanbul ignore else */ 199 | if (({}).hasOwnProperty.call(config, prop)) { 200 | var envVar = process.env['GL_' + prop.toUpperCase()]; 201 | if (envVar) config[prop] = envVar; 202 | } 203 | } 204 | // Apply config to CORS 205 | setCORS(); 206 | }; 207 | 208 | exports.buildConfig = buildConfig; 209 | /** 210 | * Initialize the service by building the config, loading the (YAML) Gateway 211 | * API configuration and then initializing routes on Express and finally 212 | * starting the service. 213 | * @param {Object} [config] The main service configuration 214 | */ 215 | var init = function init(cfg) { 216 | // Setup config 217 | buildConfig(cfg); 218 | // Load schema into router 219 | (0, _router.loadSchema)(config.schema); 220 | // Initialize all routes from gateway schema 221 | (0, _router.initRoutes)(); 222 | // Starts service 223 | service.listen(config.port, function () { 224 | procLog('info', 'Service running', { port: config.port }); 225 | }); 226 | }; 227 | exports.init = init; -------------------------------------------------------------------------------- /build/router.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | /* eslint no-param-reassign: 0 */ 6 | 'use strict'; 7 | 8 | Object.defineProperty(exports, '__esModule', { 9 | value: true 10 | }); 11 | 12 | var _app = require('./app'); 13 | 14 | var _util = require('./util'); 15 | 16 | /** 17 | * Placeholder for schema object 18 | * @parameter {Object} schema 19 | */ 20 | var path = require('path'); 21 | var yaml = require('yamljs'); 22 | 23 | var schema = null; 24 | 25 | exports.schema = schema; 26 | /** 27 | * Placeholder for routes array 28 | * @parameter {Array} routes 29 | */ 30 | var routes = []; 31 | 32 | exports.routes = routes; 33 | /** 34 | * Loads the schema from specified file 35 | * @param {String} file The file path of the Gateway schema 36 | */ 37 | var loadSchema = function loadSchema(file) { 38 | // No checks, YAML error automagically 39 | exports.schema = schema = yaml.load(path.resolve(file)); 40 | }; 41 | 42 | exports.loadSchema = loadSchema; 43 | /** 44 | * Walks schema to look for request methods (verbs), when a method is found it 45 | * creates a route with the parent node key (the path), the current method, 46 | * and the properties of that method (the template) 47 | * @param {Object} node The node to traverse 48 | * @param {String} prevKey The key of the previous traversal for accessing parent/path 49 | */ 50 | var walkSchema = function walkSchema() { 51 | var node = arguments.length <= 0 || arguments[0] === undefined ? schema : arguments[0]; 52 | var prevKey = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; 53 | 54 | // Methods indicate traversal stops 55 | var methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH']; 56 | for (var prop in node) { 57 | // Ensure prop 58 | if (({}).hasOwnProperty.call(node, prop)) { 59 | if (methods.indexOf(prop) >= 0) { 60 | // Node is a method, push to router 61 | routes.push({ route: prevKey, method: prop, config: node[prop] }); 62 | } else { 63 | // Route node, traverse 64 | walkSchema(node[prop], prop); 65 | } 66 | } 67 | } 68 | }; 69 | 70 | exports.walkSchema = walkSchema; 71 | /** 72 | * Iterates over the properties of the template and calls `parseRouteParams` to 73 | * convert the bracket-delimited params with colon-lead (Express-style) route 74 | * params with the template-designated key/property name 75 | * @param {String} route The route to modify 76 | * @param {Object} template The template object to match against 77 | * @returns {String} The formatted route 78 | */ 79 | var mapTemplateParams = function mapTemplateParams(route, template) { 80 | for (var prop in template) { 81 | if (({}).hasOwnProperty.call(template, prop)) { 82 | var param = (0, _util.parseRouteParams)(template[prop], prop, route); 83 | if (param) { 84 | route = param; 85 | delete template[prop]; 86 | } 87 | } 88 | } 89 | // Return modified route and template 90 | return { route: route, template: template }; 91 | }; 92 | 93 | exports.mapTemplateParams = mapTemplateParams; 94 | /** 95 | * Ensures that the lambda exists (on init/load) then creates and Express 96 | * verb+route object for the specific request 97 | * @param {Object} route The route to add 98 | */ 99 | var addRoute = function addRoute(route) { 100 | // Build ensure specified Lambda exists 101 | (0, _util.fileExists)(_app.config.lambdas + '/' + route.config.lambda + '/index.js').then(function () { 102 | // Add method route 103 | _app.service[route.method.toLowerCase()](_app.config.apiPath + route.route, function (req, res) { 104 | (0, _app.runLambda)(route.config.lambda, route.config.templates['application/json'], req, res); 105 | }); 106 | })['catch'](function () { 107 | _app.log.error('Missing Lambda', { name: route.config.lambda }); 108 | }); 109 | }; 110 | 111 | exports.addRoute = addRoute; 112 | /** 113 | * Itterates over the routes array to map template parameters, set the route 114 | * property, config > templates and call `addRoute` 115 | */ 116 | var buildRoutes = function buildRoutes() { 117 | // Itterate over routes 118 | routes.forEach(function (rte) { 119 | // Map template params 120 | var mappedRoutes = mapTemplateParams(rte.route, rte.config.templates['application/json']); 121 | rte.route = mappedRoutes.route; 122 | rte.config.templates['application/json'] = mappedRoutes.template; 123 | addRoute(rte); 124 | }); 125 | }; 126 | 127 | /** 128 | * Initializes the routes by walking the Gateway schema then running `buildRoutes` 129 | * to load into Express object 130 | */ 131 | var initRoutes = function initRoutes() { 132 | // Walk the schema to build routes 133 | walkSchema(schema); 134 | // Map params and build express routes 135 | buildRoutes(); 136 | }; 137 | exports.initRoutes = initRoutes; -------------------------------------------------------------------------------- /build/runner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | /* eslint no-process-exit: 0, no-console: 0 */ 6 | 'use strict'; 7 | 8 | var util = require('util'); 9 | var path = require('path'); 10 | 11 | // Override console.log to send messages back through proc emit 12 | console.log = console.info = console.warn = console.error = console.debug = function () { 13 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 14 | args[_key] = arguments[_key]; 15 | } 16 | 17 | process.send({ 18 | type: 'debug', 19 | output: { 20 | lambda: process.argv[2], 21 | data: args 22 | } 23 | }); 24 | }; 25 | 26 | // Sets the lambda from its path 27 | var lambda = require(path.resolve(process.env.lambdas + '/' + process.argv[2] + '/index')); 28 | 29 | /** 30 | * Creates the context object passed to lambdas 31 | * @property context 32 | */ 33 | var context = { 34 | /** 35 | * Emit a success message with output 36 | * @param {Object|String} result The contents of the result 37 | */ 38 | succeed: function succeed(result) { 39 | process.send({ type: 'success', output: result }); 40 | context.done(); 41 | }, 42 | /** 43 | * Emit an error message with output 44 | * @param {Object|String} error The error object or message 45 | */ 46 | fail: function fail(error) { 47 | process.send({ type: 'error', output: error }); 48 | context.done(); 49 | }, 50 | /** 51 | * Emit closing metrics and end the lambda process 52 | */ 53 | done: function done() { 54 | process.send({ type: 'metric', output: { 55 | lambda: process.argv[2], 56 | event: process.env.event, 57 | pid: process.pid, 58 | memory: util.inspect(process.memoryUsage()), 59 | time: process.uptime() 60 | } }); 61 | process.exit(); 62 | } 63 | }; 64 | 65 | // Call lambda's handler 66 | lambda.handler(JSON.parse(process.env.event), context); -------------------------------------------------------------------------------- /build/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | 'use strict'; 6 | 7 | Object.defineProperty(exports, '__esModule', { 8 | value: true 9 | }); 10 | var Promise = require('bluebird'); 11 | var path = require('path'); 12 | var fs = Promise.promisifyAll(require('fs')); 13 | 14 | /** 15 | * Checks if file exists 16 | * @param {String} file Path to file 17 | * @returns {Object} Promise 18 | */ 19 | var fileExists = function fileExists(file) { 20 | return fs.openAsync(path.resolve(file), 'r'); 21 | }; 22 | 23 | exports.fileExists = fileExists; 24 | /** 25 | * Abstracts parsing of routes against template values 26 | * @param {String} value The value of the template element 27 | * @param {String} key The property name of the template element 28 | * @param {String} route The route to check/modify 29 | */ 30 | var parseRouteParams = function parseRouteParams(value, key, route) { 31 | if (value.indexOf('$input.params(\'') >= 0) { 32 | // Remove wrapper 33 | var param = value.replace('$input.params(\'', '').replace('\')', ''); 34 | // Ensure route contains match, replace 35 | if (route.indexOf('{' + param + '}') >= 0) return route.replace('{' + param + '}', ':' + key); 36 | // Not matched 37 | return false; 38 | } 39 | return false; 40 | }; 41 | 42 | exports.parseRouteParams = parseRouteParams; 43 | /** 44 | * Abstracts parsing of body against template values 45 | * @param {String} value The value of the template element 46 | * @param {Object} req The request object 47 | * @returns {String} The value of the body property requested by the template 48 | */ 49 | var parseRequestParams = function parseRequestParams(value, req) { 50 | // Body 51 | if (value.indexOf('querystring') >= 0) { 52 | var returnArray = []; 53 | for (var obj in req.query) { 54 | if (req.query.hasOwnProperty(obj)) { 55 | var str = obj + '=' + req.query[obj]; 56 | returnArray.push(str); 57 | } 58 | } 59 | var returnObject = '{' + returnArray.join(',') + '}'; 60 | return returnObject; 61 | } 62 | if (value.indexOf('$input.json(\'$') >= 0) { 63 | // Get the name to check 64 | var _name = value.replace('$input.json(\'$', '').replace('\')', ''); 65 | // Return the entire body 66 | if (!_name.length) return req.body; 67 | // Return the specific property of the body (or null if DNE) 68 | _name = _name.replace(/^\./, ''); // Remove leading dot 69 | return req.body && req.body[_name] ? req.body[_name] : null; 70 | } 71 | // Param (querystring or header) 72 | if (value.indexOf('$input.params(\'') >= 0) { 73 | // Remove wrapper 74 | var param = value.replace('$input.params(\'', '').replace('\')', ''); 75 | // Return if matching querysting 76 | if (req.query && req.query[param]) return req.query[param]; 77 | // Retrun if matching header (or undefined) 78 | return req.get(param); 79 | } 80 | // Custom value passed through 81 | return value; 82 | }; 83 | exports.parseRequestParams = parseRequestParams; -------------------------------------------------------------------------------- /docs/ast/source/src/server.js.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Program", 3 | "body": [ 4 | { 5 | "type": "ImportDeclaration", 6 | "specifiers": [ 7 | { 8 | "type": "ImportSpecifier", 9 | "local": { 10 | "type": "Identifier", 11 | "name": "app", 12 | "range": [ 13 | 38, 14 | 41 15 | ], 16 | "loc": { 17 | "start": { 18 | "line": 2, 19 | "column": 9 20 | }, 21 | "end": { 22 | "line": 2, 23 | "column": 12 24 | } 25 | } 26 | }, 27 | "imported": { 28 | "type": "Identifier", 29 | "name": "app", 30 | "range": [ 31 | 38, 32 | 41 33 | ], 34 | "loc": { 35 | "start": { 36 | "line": 2, 37 | "column": 9 38 | }, 39 | "end": { 40 | "line": 2, 41 | "column": 12 42 | } 43 | } 44 | }, 45 | "range": [ 46 | 38, 47 | 41 48 | ], 49 | "loc": { 50 | "start": { 51 | "line": 2, 52 | "column": 9 53 | }, 54 | "end": { 55 | "line": 2, 56 | "column": 12 57 | } 58 | } 59 | } 60 | ], 61 | "source": { 62 | "type": "Literal", 63 | "value": "./lib/app", 64 | "raw": "'./lib/app'", 65 | "range": [ 66 | 49, 67 | 60 68 | ], 69 | "loc": { 70 | "start": { 71 | "line": 2, 72 | "column": 20 73 | }, 74 | "end": { 75 | "line": 2, 76 | "column": 31 77 | } 78 | } 79 | }, 80 | "range": [ 81 | 29, 82 | 61 83 | ], 84 | "loc": { 85 | "start": { 86 | "line": 2, 87 | "column": 0 88 | }, 89 | "end": { 90 | "line": 3, 91 | "column": 0 92 | } 93 | }, 94 | "leadingComments": [ 95 | { 96 | "type": "Block", 97 | "value": "eslint no-unused-vars:0 ", 98 | "range": [ 99 | 0, 100 | 28 101 | ], 102 | "loc": { 103 | "start": { 104 | "line": 1, 105 | "column": 0 106 | }, 107 | "end": { 108 | "line": 1, 109 | "column": 28 110 | } 111 | } 112 | } 113 | ] 114 | }, 115 | { 116 | "type": "ExpressionStatement", 117 | "expression": { 118 | "type": "CallExpression", 119 | "callee": { 120 | "type": "Identifier", 121 | "name": "app", 122 | "range": [ 123 | 61, 124 | 64 125 | ], 126 | "loc": { 127 | "start": { 128 | "line": 3, 129 | "column": 0 130 | }, 131 | "end": { 132 | "line": 3, 133 | "column": 3 134 | } 135 | } 136 | }, 137 | "arguments": [ 138 | { 139 | "type": "ObjectExpression", 140 | "properties": [ 141 | { 142 | "type": "Property", 143 | "key": { 144 | "type": "Identifier", 145 | "name": "port", 146 | "range": [ 147 | 69, 148 | 73 149 | ], 150 | "loc": { 151 | "start": { 152 | "line": 4, 153 | "column": 2 154 | }, 155 | "end": { 156 | "line": 4, 157 | "column": 6 158 | } 159 | } 160 | }, 161 | "value": { 162 | "type": "Literal", 163 | "value": 8181, 164 | "raw": "8181", 165 | "range": [ 166 | 75, 167 | 79 168 | ], 169 | "loc": { 170 | "start": { 171 | "line": 4, 172 | "column": 8 173 | }, 174 | "end": { 175 | "line": 4, 176 | "column": 12 177 | } 178 | } 179 | }, 180 | "kind": "init", 181 | "method": false, 182 | "shorthand": false, 183 | "computed": false, 184 | "range": [ 185 | 69, 186 | 79 187 | ], 188 | "loc": { 189 | "start": { 190 | "line": 4, 191 | "column": 2 192 | }, 193 | "end": { 194 | "line": 4, 195 | "column": 12 196 | } 197 | } 198 | }, 199 | { 200 | "type": "Property", 201 | "key": { 202 | "type": "Identifier", 203 | "name": "lambdas", 204 | "range": [ 205 | 83, 206 | 90 207 | ], 208 | "loc": { 209 | "start": { 210 | "line": 5, 211 | "column": 2 212 | }, 213 | "end": { 214 | "line": 5, 215 | "column": 9 216 | } 217 | } 218 | }, 219 | "value": { 220 | "type": "Literal", 221 | "value": "./build/lambdas/", 222 | "raw": "'./build/lambdas/'", 223 | "range": [ 224 | 92, 225 | 110 226 | ], 227 | "loc": { 228 | "start": { 229 | "line": 5, 230 | "column": 11 231 | }, 232 | "end": { 233 | "line": 5, 234 | "column": 29 235 | } 236 | } 237 | }, 238 | "kind": "init", 239 | "method": false, 240 | "shorthand": false, 241 | "computed": false, 242 | "range": [ 243 | 83, 244 | 110 245 | ], 246 | "loc": { 247 | "start": { 248 | "line": 5, 249 | "column": 2 250 | }, 251 | "end": { 252 | "line": 5, 253 | "column": 29 254 | } 255 | } 256 | } 257 | ], 258 | "range": [ 259 | 65, 260 | 112 261 | ], 262 | "loc": { 263 | "start": { 264 | "line": 3, 265 | "column": 4 266 | }, 267 | "end": { 268 | "line": 6, 269 | "column": 1 270 | } 271 | } 272 | } 273 | ], 274 | "range": [ 275 | 61, 276 | 113 277 | ], 278 | "loc": { 279 | "start": { 280 | "line": 3, 281 | "column": 0 282 | }, 283 | "end": { 284 | "line": 6, 285 | "column": 2 286 | } 287 | } 288 | }, 289 | "range": [ 290 | 61, 291 | 114 292 | ], 293 | "loc": { 294 | "start": { 295 | "line": 3, 296 | "column": 0 297 | }, 298 | "end": { 299 | "line": 7, 300 | "column": 0 301 | } 302 | } 303 | } 304 | ], 305 | "sourceType": "module", 306 | "range": [ 307 | 29, 308 | 114 309 | ], 310 | "loc": { 311 | "start": { 312 | "line": 2, 313 | "column": 0 314 | }, 315 | "end": { 316 | "line": 7, 317 | "column": 0 318 | } 319 | }, 320 | "comments": [ 321 | { 322 | "type": "Block", 323 | "value": "eslint no-unused-vars:0 ", 324 | "range": [ 325 | 0, 326 | 28 327 | ], 328 | "loc": { 329 | "start": { 330 | "line": 1, 331 | "column": 0 332 | }, 333 | "end": { 334 | "line": 1, 335 | "column": 28 336 | } 337 | } 338 | } 339 | ] 340 | } -------------------------------------------------------------------------------- /docs/class/src/app.js~app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | app | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 48 | 49 |
50 |
import app from 'template/src/app.js'
51 | public 52 | class 53 | 54 | 55 | 56 | | source 57 |
58 | 59 |
60 |

app

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |

App class

75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 | 86 | 87 | 88 |

Constructor Summary

89 | 90 | 91 | 92 | 93 | 100 | 112 | 116 | 117 | 118 |
Public Constructor
94 | public 95 | 96 | 97 | 98 | 99 | 101 |
102 |

103 | constructor 104 |

105 |
106 |
107 | 108 | 109 | 110 |
111 |
113 | 114 | 115 |
119 |
120 | 121 |

Method Summary

122 | 123 | 124 | 125 | 126 | 133 | 145 | 149 | 150 | 151 |
Public Methods
127 | public 128 | 129 | 130 | 131 | 132 | 134 |
135 |

136 | test(): string 137 |

138 |
139 |
140 | 141 | 142 | 143 |
144 |
146 | 147 | 148 |
152 |
153 | 154 | 155 | 156 | 157 | 158 |

Public Constructors

159 | 160 |
161 |

162 | public 163 | 164 | 165 | 166 | 167 | constructor 168 | 169 | 170 | 171 | source 172 | 173 |

174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |
183 |
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
200 |
201 | 202 |

Public Methods

203 | 204 |
205 |

206 | public 207 | 208 | 209 | 210 | 211 | test(): string 212 | 213 | 214 | 215 | source 216 | 217 |

218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 |
227 |
228 | 229 |
230 |

Return:

231 | 232 | 233 | 234 | 236 | 237 |
string

just returns foo

235 |
238 |
239 |
240 |
241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 |
255 |
256 |
257 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /docs/class/src/lib/app.js~App.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 55 | 56 |
57 |
import {App} from 'template/src/lib/app.js'
58 | public 59 | class 60 | 61 | 62 | 63 | | source 64 |
65 | 66 |
67 |

App

68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |

Creates a new app instance

84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 | 95 | 96 | 97 |

Constructor Summary

98 | 99 | 100 | 101 | 102 | 109 | 121 | 125 | 126 | 127 |
Public Constructor
103 | public 104 | 105 | 106 | 107 | 108 | 110 |
111 |

112 | constructor 113 |

114 |
115 |
116 | 117 | 118 | 119 |
120 |
122 | 123 | 124 |
128 |
129 | 130 |

Method Summary

131 | 132 | 133 | 134 | 135 | 142 | 155 | 159 | 160 | 161 |
Public Methods
136 | public 137 | 138 | 139 | 140 | 141 | 143 |
144 |

145 | runLambda(req: Object, res: Object) 146 |

147 |
148 |
149 | 150 | 151 |

Builds payload and execs lambda

152 |
153 |
154 |
156 | 157 | 158 |
162 |
163 | 164 | 165 | 166 | 167 | 168 |

Public Constructors

169 | 170 |
171 |

172 | public 173 | 174 | 175 | 176 | 177 | constructor 178 | 179 | 180 | 181 | source 182 | 183 |

184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
193 |
194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
210 |
211 | 212 |

Public Methods

213 | 214 |
215 |

216 | public 217 | 218 | 219 | 220 | 221 | runLambda(req: Object, res: Object) 222 | 223 | 224 | 225 | source 226 | 227 |

228 | 229 | 230 | 231 | 232 |

Builds payload and execs lambda

233 |
234 | 235 | 236 | 237 |
238 |

Params:

239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 251 | 252 | 253 | 254 | 255 | 256 | 258 | 259 | 260 |
NameTypeAttributeDescription
reqObject

Express req object

250 |
resObject

Exptess res object

257 |
261 |
262 |
263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 |
279 |
280 |
281 | 282 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /docs/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "86.36%", 3 | "expectCount": 22, 4 | "actualCount": 19, 5 | "files": { 6 | "src/router.js": { 7 | "expectCount": 7, 8 | "actualCount": 7 9 | }, 10 | "src/app.js": { 11 | "expectCount": 12, 12 | "actualCount": 9 13 | }, 14 | "src/util.js": { 15 | "expectCount": 3, 16 | "actualCount": 3 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /docs/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,300,700); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | text-decoration: none; 7 | } 8 | 9 | html 10 | { 11 | font-family: 'Roboto', sans-serif; 12 | overflow: auto; 13 | font-size: 14px; 14 | color: #4d4e53; 15 | background-color: #F9F9F9; 16 | } 17 | 18 | a { 19 | /*color: #0095dd;*/ 20 | color:rgb(37, 138, 175); 21 | } 22 | 23 | code a:hover { 24 | text-decoration: underline; 25 | } 26 | 27 | ul, ol { 28 | padding-left: 20px; 29 | } 30 | 31 | ul li { 32 | list-style: disc; 33 | margin: 4px 0; 34 | } 35 | 36 | ol li { 37 | margin: 4px 0; 38 | } 39 | 40 | h1 { 41 | margin-bottom: 10px; 42 | font-size: 34px; 43 | font-weight: 300; 44 | border-bottom: solid 1px #ddd; 45 | } 46 | 47 | h2 { 48 | margin-top: 24px; 49 | margin-bottom: 10px; 50 | font-size: 20px; 51 | border-bottom: solid 1px #ddd; 52 | font-weight: 300; 53 | } 54 | 55 | h3 { 56 | position: relative; 57 | font-size: 16px; 58 | margin-bottom: 12px; 59 | background-color: #E2E2E2; 60 | padding: 4px; 61 | font-weight: 300; 62 | } 63 | 64 | p { 65 | margin-bottom: 15px; 66 | line-height: 19px; 67 | } 68 | 69 | pre.prettyprint { 70 | padding: 4px; 71 | border: solid 1px #ddd; 72 | margin: 4px 0; 73 | } 74 | 75 | p > code, 76 | li > code { 77 | padding: 0 4px; 78 | background: #ddd; 79 | } 80 | 81 | .import-path pre.prettyprint { 82 | border: none; 83 | padding: 0; 84 | } 85 | 86 | .layout-container { 87 | /*display: flex;*/ 88 | /*flex-direction: row;*/ 89 | /*justify-content: flex-start;*/ 90 | /*align-items: stretch;*/ 91 | } 92 | 93 | .layout-container > header { 94 | height: 40px; 95 | line-height: 40px; 96 | font-size: 16px; 97 | padding: 0 10px; 98 | margin: 0; 99 | position: fixed; 100 | width: 100%; 101 | z-index: 1; 102 | background-color: #F9F9F9; 103 | top: 0; 104 | border-bottom: solid 1px #E02130; 105 | } 106 | .layout-container > header > a{ 107 | margin: 0 5px; 108 | } 109 | 110 | .layout-container > header > a.repo-url-github { 111 | font-size: 0; 112 | display: inline-block; 113 | width: 20px; 114 | height: 38px; 115 | background: url("../image/github.png") no-repeat center; 116 | background-size: 20px; 117 | vertical-align: top; 118 | } 119 | 120 | .navigation { 121 | position: fixed; 122 | top: 0; 123 | left: 0; 124 | box-sizing: border-box; 125 | width: 200px; 126 | height: 100%; 127 | padding-top: 40px; 128 | padding-left: 15px; 129 | padding-bottom: 20px; 130 | overflow-x: scroll; 131 | box-shadow: rgba(255, 255, 255, 1) -1px 0 0 inset; 132 | border-right: 1px solid rgba(0, 0, 0, 0.1); 133 | } 134 | 135 | .navigation h1 { 136 | border: none; 137 | font-size: 18px; 138 | } 139 | 140 | .navigation h2 { 141 | margin: 15px 0 4px; 142 | font-size: 16px; 143 | border: none; 144 | } 145 | 146 | .navigation h2 a { 147 | color: inherit; 148 | } 149 | 150 | .navigation ul { 151 | padding: 0; 152 | } 153 | 154 | .navigation li { 155 | list-style: none; 156 | margin: 4px 0; 157 | } 158 | 159 | h1 .version, 160 | h1 .url a { 161 | font-size: 14px; 162 | color: #aaa; 163 | } 164 | 165 | .content { 166 | margin-top: 40px; 167 | margin-left: 200px; 168 | padding: 10px 50px 10px 20px; 169 | } 170 | 171 | .header-notice { 172 | font-size: 14px; 173 | color: #aaa; 174 | margin: 0; 175 | } 176 | 177 | .expression-extends .prettyprint { 178 | margin-left: 10px; 179 | background: white; 180 | } 181 | 182 | .extends-chain { 183 | border-bottom: 1px solid#ddd; 184 | padding-bottom: 10px; 185 | margin-bottom: 10px; 186 | } 187 | 188 | .extends-chain span:nth-of-type(1) { 189 | padding-left: 10px; 190 | } 191 | 192 | .extends-chain > div { 193 | margin: 5px 0; 194 | } 195 | 196 | .description table { 197 | font-size: 14px; 198 | border-spacing: 0; 199 | border: 0; 200 | border-collapse: collapse; 201 | } 202 | 203 | .description thead { 204 | background: #999; 205 | color: white; 206 | } 207 | 208 | .description table td, 209 | .description table th { 210 | border: solid 1px #ddd; 211 | padding: 4px; 212 | font-weight: normal; 213 | } 214 | 215 | .flat-list ul { 216 | padding-left: 0; 217 | } 218 | 219 | .flat-list li { 220 | display: inline; 221 | list-style: none; 222 | } 223 | 224 | table.summary { 225 | width: 100%; 226 | margin: 10px 0; 227 | border-spacing: 0; 228 | border: 0; 229 | border-collapse: collapse; 230 | } 231 | 232 | table.summary thead { 233 | background: #999; 234 | color: white; 235 | } 236 | 237 | table.summary td { 238 | border: solid 1px #ddd; 239 | padding: 4px 10px; 240 | } 241 | 242 | table.summary tbody td:nth-child(1) { 243 | text-align: right; 244 | white-space: nowrap; 245 | min-width: 64px; 246 | vertical-align: top; 247 | } 248 | 249 | table.summary tbody td:nth-child(2) { 250 | width: 100%; 251 | border-right: none; 252 | } 253 | 254 | table.summary tbody td:nth-child(3) { 255 | white-space: nowrap; 256 | border-left: none; 257 | vertical-align: top; 258 | } 259 | 260 | table.summary td > div:nth-of-type(2) { 261 | padding-top: 4px; 262 | padding-left: 15px; 263 | } 264 | 265 | table.summary td p { 266 | margin-bottom: 0; 267 | } 268 | 269 | .inherited-summary thead td { 270 | padding-left: 2px; 271 | } 272 | 273 | .inherited-summary thead a { 274 | color: white; 275 | } 276 | 277 | .inherited-summary .summary tbody { 278 | display: none; 279 | } 280 | 281 | .inherited-summary .summary .toggle { 282 | padding: 0 4px; 283 | font-size: 12px; 284 | cursor: pointer; 285 | } 286 | .inherited-summary .summary .toggle.closed:before { 287 | content: "▶"; 288 | } 289 | .inherited-summary .summary .toggle.opened:before { 290 | content: "▼"; 291 | } 292 | 293 | .member, .method { 294 | margin-bottom: 24px; 295 | } 296 | 297 | table.params { 298 | width: 100%; 299 | margin: 10px 0; 300 | border-spacing: 0; 301 | border: 0; 302 | border-collapse: collapse; 303 | } 304 | 305 | table.params thead { 306 | background: #eee; 307 | color: #aaa; 308 | } 309 | 310 | table.params td { 311 | padding: 4px; 312 | border: solid 1px #ddd; 313 | } 314 | 315 | table.params td p { 316 | margin: 0; 317 | } 318 | 319 | .content .detail > * { 320 | margin: 15px 0; 321 | } 322 | 323 | .content .detail > h3 { 324 | color: black; 325 | } 326 | 327 | .content .detail > div { 328 | margin-left: 10px; 329 | } 330 | 331 | .content .detail > .import-path { 332 | margin-top: -8px; 333 | } 334 | 335 | .content .detail + .detail { 336 | margin-top: 30px; 337 | } 338 | 339 | .content .detail .throw td:first-child { 340 | padding-right: 10px; 341 | } 342 | 343 | .content .detail h4 + :not(pre) { 344 | padding-left: 0; 345 | margin-left: 10px; 346 | } 347 | 348 | .content .detail h4 + ul li { 349 | list-style: none; 350 | } 351 | 352 | .return-param * { 353 | display: inline; 354 | } 355 | 356 | .argument-params { 357 | margin-bottom: 20px; 358 | } 359 | 360 | .return-type { 361 | padding-right: 10px; 362 | font-weight: normal; 363 | } 364 | 365 | .return-desc { 366 | margin-left: 10px; 367 | margin-top: 4px; 368 | } 369 | 370 | .return-desc p { 371 | margin: 0; 372 | } 373 | 374 | .deprecated, .experimental, .instance-docs { 375 | border-left: solid 5px orange; 376 | padding-left: 4px; 377 | margin: 4px 0; 378 | } 379 | 380 | tr.listen p, 381 | tr.throw p, 382 | tr.emit p{ 383 | margin-bottom: 10px; 384 | } 385 | 386 | .version, .since { 387 | color: #aaa; 388 | } 389 | 390 | h3 .right-info { 391 | position: absolute; 392 | right: 4px; 393 | font-size: 14px; 394 | } 395 | 396 | .version + .since:before { 397 | content: '| '; 398 | } 399 | 400 | .see { 401 | margin-top: 10px; 402 | } 403 | 404 | .see h4 { 405 | margin: 4px 0; 406 | } 407 | 408 | .content .detail h4 + .example-doc { 409 | margin: 6px 0; 410 | } 411 | 412 | .example-caption { 413 | position: relative; 414 | bottom: -1px; 415 | display: inline-block; 416 | padding: 4px; 417 | border: solid 1px #ddd; 418 | border-bottom: none; 419 | font-style: italic; 420 | background-color: #eee; 421 | font-weight: bold; 422 | } 423 | 424 | .example-caption + pre.source-code { 425 | margin-top: 0; 426 | } 427 | 428 | footer, .file-footer { 429 | text-align: right; 430 | font-style: italic; 431 | font-weight: 100; 432 | font-size: 13px; 433 | margin-right: 50px; 434 | margin-left: 220px; 435 | border-top: 1px solid #ddd; 436 | padding-top: 30px; 437 | margin-top: 20px; 438 | padding-bottom: 10px; 439 | } 440 | 441 | pre.source-code { 442 | background: white; 443 | border: solid 1px #ddd; 444 | padding: 4px; 445 | } 446 | 447 | pre.source-code.line-number { 448 | padding: 0; 449 | } 450 | 451 | pre.source-code ol { 452 | background: #eee; 453 | padding-left: 40px; 454 | } 455 | 456 | pre.source-code li { 457 | background: white; 458 | padding-left: 4px; 459 | list-style: decimal; 460 | margin: 0; 461 | } 462 | 463 | pre.source-code.line-number li.active { 464 | background: rgb(255, 255, 150); 465 | } 466 | 467 | table.files-summary { 468 | width: 100%; 469 | margin: 10px 0; 470 | border-spacing: 0; 471 | border: 0; 472 | border-collapse: collapse; 473 | text-align: right; 474 | } 475 | 476 | table.files-summary tbody tr:hover { 477 | background: #eee; 478 | } 479 | 480 | table.files-summary td:first-child, 481 | table.files-summary td:nth-of-type(2) { 482 | text-align: left; 483 | } 484 | 485 | table.files-summary[data-use-coverage="false"] td.coverage { 486 | display: none; 487 | } 488 | 489 | table.files-summary thead { 490 | background: #999; 491 | color: white; 492 | } 493 | 494 | table.files-summary td { 495 | border: solid 1px #ddd; 496 | padding: 4px 10px; 497 | vertical-align: top; 498 | } 499 | 500 | table.files-summary td.identifiers > span { 501 | display: block; 502 | margin-top: 4px; 503 | } 504 | table.files-summary td.identifiers > span:first-child { 505 | margin-top: 0; 506 | } 507 | 508 | table.files-summary .coverage-count { 509 | font-size: 12px; 510 | color: #aaa; 511 | display: inline-block; 512 | min-width: 40px; 513 | } 514 | 515 | .total-coverage-count { 516 | font-size: 12px; 517 | color: #666; 518 | font-weight: 500; 519 | padding-left: 5px; 520 | } 521 | 522 | table.test-summary thead { 523 | background: #999; 524 | color: white; 525 | } 526 | 527 | table.test-summary thead .test-description { 528 | width: 50%; 529 | } 530 | 531 | table.test-summary { 532 | width: 100%; 533 | margin: 10px 0; 534 | border-spacing: 0; 535 | border: 0; 536 | border-collapse: collapse; 537 | } 538 | 539 | table.test-summary thead .test-count { 540 | width: 3em; 541 | } 542 | 543 | table.test-summary tbody tr:hover { 544 | background-color: #eee; 545 | } 546 | 547 | table.test-summary td { 548 | border: solid 1px #ddd; 549 | padding: 4px 10px; 550 | vertical-align: top; 551 | } 552 | 553 | table.test-summary td p { 554 | margin: 0; 555 | } 556 | 557 | table.test-summary tr.test-describe .toggle { 558 | display: inline-block; 559 | float: left; 560 | margin-right: 4px; 561 | cursor: pointer; 562 | } 563 | 564 | table.test-summary tr.test-describe .toggle.opened:before { 565 | content: '▼'; 566 | } 567 | 568 | table.test-summary tr.test-describe .toggle.closed:before { 569 | content: '▶'; 570 | } 571 | 572 | table.test-summary .test-target > span { 573 | display: block; 574 | margin-top: 4px; 575 | } 576 | table.test-summary .test-target > span:first-child { 577 | margin-top: 0; 578 | } 579 | 580 | .inner-link-active { 581 | background: rgb(255, 255, 150); 582 | } 583 | 584 | /* search box */ 585 | .search-box { 586 | position: absolute; 587 | top: 10px; 588 | right: 50px; 589 | padding-right: 8px; 590 | padding-bottom: 10px; 591 | line-height: normal; 592 | font-size: 12px; 593 | } 594 | 595 | .search-box img { 596 | width: 20px; 597 | vertical-align: top; 598 | } 599 | 600 | .search-input { 601 | display: inline; 602 | visibility: hidden; 603 | width: 0; 604 | padding: 2px; 605 | height: 1.5em; 606 | outline: none; 607 | background: transparent; 608 | border: 1px #0af; 609 | border-style: none none solid none; 610 | vertical-align: bottom; 611 | } 612 | 613 | .search-input-edge { 614 | display: none; 615 | width: 1px; 616 | height: 5px; 617 | background-color: #0af; 618 | vertical-align: bottom; 619 | } 620 | 621 | .search-result { 622 | position: absolute; 623 | display: none; 624 | height: 600px; 625 | width: 100%; 626 | padding: 0; 627 | margin-top: 5px; 628 | margin-left: 24px; 629 | background: white; 630 | box-shadow: 1px 1px 4px rgb(0,0,0); 631 | white-space: nowrap; 632 | overflow-y: scroll; 633 | } 634 | 635 | .search-result-import-path { 636 | color: #aaa; 637 | font-size: 12px; 638 | } 639 | 640 | .search-result li { 641 | list-style: none; 642 | padding: 2px 4px; 643 | } 644 | 645 | .search-result li a { 646 | display: block; 647 | } 648 | 649 | .search-result li.selected { 650 | background: #ddd; 651 | } 652 | 653 | .search-result li.search-separator { 654 | background: rgb(37, 138, 175); 655 | color: white; 656 | } 657 | 658 | .search-box.active .search-input { 659 | visibility: visible; 660 | transition: width 0.2s ease-out; 661 | width: 300px; 662 | } 663 | 664 | .search-box.active .search-input-edge { 665 | display: inline-block; 666 | } 667 | 668 | /* coverage badge */ 669 | .esdoc-coverage { 670 | display: inline-block; 671 | height: 20px; 672 | vertical-align: top; 673 | } 674 | 675 | h1 .esdoc-coverage { 676 | position: relative; 677 | top: -4px; 678 | } 679 | 680 | .esdoc-coverage-wrap { 681 | color: white; 682 | font-size: 12px; 683 | font-weight: 500; 684 | } 685 | 686 | .esdoc-coverage-label { 687 | padding: 3px 4px 3px 6px; 688 | background: linear-gradient(to bottom, #5e5e5e 0%,#4c4c4c 100%); 689 | border-radius: 4px 0 0 4px; 690 | display: inline-block; 691 | height: 20px; 692 | box-sizing: border-box; 693 | line-height: 14px; 694 | } 695 | 696 | .esdoc-coverage-ratio { 697 | padding: 3px 6px 3px 4px; 698 | border-radius: 0 4px 4px 0; 699 | display: inline-block; 700 | height: 20px; 701 | box-sizing: border-box; 702 | line-height: 14px; 703 | } 704 | 705 | .esdoc-coverage-low { 706 | background: linear-gradient(to bottom, #db654f 0%,#c9533d 100%); 707 | } 708 | 709 | .esdoc-coverage-middle { 710 | background: linear-gradient(to bottom, #dab226 0%,#c9a179 100%); 711 | } 712 | 713 | .esdoc-coverage-high { 714 | background: linear-gradient(to bottom, #4fc921 0%,#3eb810 100%); 715 | } 716 | -------------------------------------------------------------------------------- /docs/file/src/app.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/app.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
29 | 30 | 69 | 70 |

src/app.js

71 |
/*
 72 |  * Copyright (c) 2015 TechnologyAdvice
 73 |  */
 74 | 
 75 | // Deps
 76 | const express = require('express')
 77 | const bodyParser = require('body-parser')
 78 | const fork = require('child_process').fork
 79 | const path = require('path')
 80 | const _ = require('lodash')
 81 | 
 82 | // Setup logs
 83 | export const log = require('bristol')
 84 | log.addTarget('console').withFormatter('commonInfoModel')
 85 | log.addTransform((elem) => {
 86 |   delete elem.file
 87 |   delete elem.line
 88 |   return elem
 89 | })
 90 | 
 91 | // Default path to lambda runner
 92 | let runner = path.resolve(__dirname, './runner')
 93 | 
 94 | // Import router
 95 | import { loadSchema, initRoutes } from './router'
 96 | import { parseRequestParams } from './util'
 97 | 
 98 | /**
 99 |  * Allows overriding default runner script
100 |  * @param {String} runnerPath Path to the runner module
101 |  */
102 | export const setRunner = (runnerPath) => runner = path.resolve(runnerPath)
103 | 
104 | /**
105 |  * Default config object
106 |  * @property config
107 |  * @attribute {String} lambdas The path to the lambdas directory
108 |  * @attribute {String} schema The path to the Gateway YAML config
109 |  * @attribute {Number} port The port for the HTTP service
110 |  * @attribute {String} apiPath The request path for the api
111 |  * @attribute {Boolean} log Show or repress console output
112 |  */
113 | export let config = {
114 |   lambdas: './lambdas',
115 |   schema: './gateway.yml',
116 |   port: 8181,
117 |   apiPath: '/api',
118 |   log: true,
119 |   cors: {
120 |     origin: '*',
121 |     methods: 'GET,PUT,POST,DELETE,OPTIONS',
122 |     headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
123 |   }
124 | }
125 | 
126 | // Express setup
127 | export const service = express()
128 | 
129 | // CORS
130 | export const setCORS = () => {
131 |   service.use((req, res, next) => {
132 |     res.header('Access-Control-Allow-Origin', config.cors.origin)
133 |     res.header('Access-Control-Allow-Methods', config.cors.methods)
134 |     res.header('Access-Control-Allow-Headers', config.cors.headers)
135 |     if (req.method === 'OPTIONS') {
136 |       res.send(200)
137 |     } else {
138 |       next()
139 |     }
140 |   })
141 | }
142 | 
143 | // Body parser
144 | service.use(bodyParser.json())
145 | 
146 | /**
147 |  * Calls log output method
148 |  * @param {String} type Type of log message to write
149 |  * @param {String|Object} msg Message body of log
150 |  */
151 | export const procLog = (type, ...msg) => {
152 |   /* istanbul ignore if  */
153 |   if (config.log) log[type](msg[0], msg[1])
154 | }
155 | 
156 | /**
157 |  * Parses response body for error code
158 |  * @param {String} output Output from context.fail
159 |  */
160 | export const parseErrorCode = (output) => {
161 |   const code = output.toString().replace(/^Error: ([1-5]\d\d).+$/, (i, match) => match)
162 |   if (code > 100 && code < 600) {
163 |     // Return specific code with stripped message
164 |     return {
165 |       code: parseInt(code, 10),
166 |       output: output.replace(`Error: ${code}`, '').trim()
167 |     }
168 |   }
169 |   // Return generic 500 with original message
170 |   return {
171 |     code: 500,
172 |     output: code
173 |   }
174 | }
175 | 
176 | /**
177 |  * Handles response from forked lambda runner procs
178 |  * @param {Object} msg Message object from child proc
179 |  * @param {Object} [res] Express response object
180 |  */
181 | export const procResponse = (msg, res) => {
182 |   switch (msg.type) {
183 |   case 'metric': procLog('info', 'Lambda Processed', msg.output); break
184 |   case 'debug': procLog('info', 'Lambda Debug', msg.output); break
185 |   case 'success': res.status(200).send(msg.output); break
186 |   case 'error':
187 |     const err = parseErrorCode(msg.output)
188 |     res.status(err.code).send(err.output)
189 |     break
190 |   default: procLog('error', 'Missing response type')
191 |   }
192 | }
193 | 
194 | /**
195 |  * Parses the properties from the template and then calls `parseRequestParams`
196 |  * to align variable properties with their template keys
197 |  * @param {Object} req The request object
198 |  * @param {Object} template The gateway template
199 |  * @returns {Object} the full event to be passed to the Lambda
200 |  */
201 | export const parseRequest = (req, template) => {
202 |   let tmpBody = {}
203 |   for (let prop in template) {
204 |     /* istanbul ignore else  */
205 |     if ({}.hasOwnProperty.call(template, prop)) {
206 |       tmpBody[prop] = parseRequestParams(template[prop], req)
207 |     }
208 |   }
209 |   return tmpBody
210 | }
211 | 
212 | /**
213 |  * Builds the `event` payload with the request body and then forks a new
214 |  * runner process to the requested lambda. Awaits messaging from the lambda
215 |  * to return response payload and display log information
216 |  * @param {String} lambda The lambda to run
217 |  * @param {Object} template The gateway template
218 |  * @param {Object} req Express req object
219 |  * @param {Object} res Express res object
220 |  */
221 | export const runLambda = (lambda, template, req, res) => {
222 |   // Parse body against template
223 |   const body = parseRequest(req, template)
224 |   // Build event by extending body with params
225 |   const event = JSON.stringify(_.extend(body, req.params))
226 |   // Build env to match current envirnment, add lambdas and event
227 |   const env = _.extend({ lambdas: config.lambdas, event }, process.env)
228 |   // Execute lambda
229 |   fork(runner, [ lambda ], { env }).on('message', (msg) => procResponse(msg, res))
230 | }
231 | 
232 | /**
233 |  * Combines the default config with any passed to init and overrides (lastly)
234 |  * if there are any environment variables set
235 |  * @param {Object} [cfg] The config passed through init
236 |  */
237 | export const buildConfig = (cfg) => {
238 |   // Against defaults
239 |   _.extend(config, cfg)
240 |   // Against env vars
241 |   for (let prop in config) {
242 |     /* istanbul ignore else  */
243 |     if ({}.hasOwnProperty.call(config, prop)) {
244 |       let envVar = process.env['GL_' + prop.toUpperCase()]
245 |       if (envVar) config[prop] = envVar
246 |     }
247 |   }
248 |   // Apply config to CORS
249 |   setCORS()
250 | }
251 | 
252 | /**
253 |  * Initialize the service by building the config, loading the (YAML) Gateway
254 |  * API configuration and then initializing routes on Express and finally
255 |  * starting the service.
256 |  * @param {Object} [config] The main service configuration
257 |  */
258 | export const init = (cfg) => {
259 |   // Setup config
260 |   buildConfig(cfg)
261 |   // Load schema into router
262 |   loadSchema(config.schema)
263 |   // Initialize all routes from gateway schema
264 |   initRoutes()
265 |   // Starts service
266 |   service.listen(config.port, () => {
267 |     procLog('info', 'Service running', { port: config.port })
268 |   })
269 | }
270 | 
271 |
272 | 273 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /docs/file/src/lambdas/apples/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/lambdas/apples/index.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 53 | 54 |

src/lambdas/apples/index.js

55 |
export const handler = (event, context) => {
56 |   context.succeed(`Apples ${JSON.stringify(event)}`)
57 | }
58 | 
59 |
60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/file/src/lambdas/oranges/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/lambdas/oranges/index.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 53 | 54 |

src/lambdas/oranges/index.js

55 |
export const handler = (event, context) => {
56 |   context.succeed(`Oranges ${JSON.stringify(event)}`)
57 | }
58 | 
59 |
60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/file/src/lambdas/runner.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/lambdas/runner.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 55 | 56 |

src/lambdas/runner.js

57 |
const path = require('path')
58 | const lambda = require(path.resolve(`./build/lambdas/${process.argv[2]}/index`))
59 | 
60 | // Build context object for Lambda
61 | const context = {
62 |   succeed: (result) => console.log(result),
63 |   fail: (error) => console.error(error)
64 | }
65 | 
66 | // Call lambda's handler
67 | lambda.handler(JSON.parse(process.env.event), context)
68 |
69 | 70 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /docs/file/src/lib/app.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/lib/app.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 51 | 52 |

src/lib/app.js

53 |
// Deps
 54 | const express = require('express')
 55 | const bodyParser = require('body-parser')
 56 | const fork = require('child_process').fork
 57 | const path = require('path')
 58 | const log = require('bristol')
 59 | const _ = require('lodash')
 60 | 
 61 | // Setup logs
 62 | log.addTarget('console').withFormatter('human')
 63 | 
 64 | // Path to lambda runner
 65 | const runner = path.resolve('build/lib/runner')
 66 | 
 67 | // Express setup
 68 | const service = express()
 69 | service.use(bodyParser.json())
 70 | 
 71 | /**
 72 |  * Default config object
 73 |  * @property config
 74 |  * @attribute {String} lambdas The path to the lambdas directory
 75 |  * @arrtibute {Number} port The port for the HTTP service
 76 |  * @attribute {String} apiPath The request path for the api
 77 |  * @attribute {Boolean} log Show or repress console output
 78 |  */
 79 | export let config = {
 80 |   lambdas: './lambdas',
 81 |   port: 8181,
 82 |   apiPath: '/api',
 83 |   log: true
 84 | }
 85 | 
 86 | /**
 87 |  * Handles response from forked lambda runner procs
 88 |  * @param {Object} msg Message object from child proc
 89 |  * @param {Object} [res] Express response object
 90 |  */
 91 | export const procResponse = (msg, res) => {
 92 |   switch (msg.type) {
 93 |     case 'metric':
 94 |       if (config.log) log.info(msg.output)
 95 |       break
 96 |     case 'success':
 97 |       res.status(200).send(msg.output)
 98 |       break
 99 |     case 'error':
100 |       res.status(500).send(msg.output)
101 |       break
102 |     default:
103 |       log.error('Missing response type')
104 |   }
105 | }
106 | 
107 | /**
108 |  * Builds the `event` payload with the request body and the method of the
109 |  * call (`operation`). Forks a new runner process to the requested lambda
110 |  * then awaits messaging from the lambda
111 |  * @param {Object} req Express req object
112 |  * @param {Object} res Express res object
113 |  * @param {String} lambdas Path to the lambdas directory
114 |  */
115 | const runLambda = (req, res) => {
116 |   const evt = req.body
117 |   const lambda = req.params.endpoint
118 |   // Map method to operation param
119 |   evt.operation = req.method
120 |   // Set lambdas
121 |   const lambdas = config.lambdas
122 |   // Set event
123 |   const event = JSON.stringify(evt)
124 |   // Execute lambda
125 |   const proc = fork(runner, [ lambda ], { env: { lambdas, event } })
126 |   // Print pid
127 |   procResponse({ type: 'metric', output: `PID ${proc.pid} running ${lambda}` })
128 |   // Await proc messaging
129 |   proc.on('message', (msg) => procResponse(msg, res))
130 | }
131 | 
132 | /**
133 |  * Combines the default config with any passed to init and overrides (lastly)
134 |  * if there are any environment variables set
135 |  * @param {Object} [cfg] The config passed through init
136 |  */
137 | export const buildConfig = (cfg) => {
138 |   // Against defaults
139 |   _.extend(config, cfg)
140 |   // Against env vars
141 |   for (let prop in config) {
142 |     if ({}.hasOwnProperty.call(config, prop)) {
143 |       let envVar = process.env['GL_' + prop.toUpperCase()]
144 |       if (envVar) config[prop] = envVar
145 |     }
146 |   }
147 | }
148 | 
149 | /**
150 |  * Initialize testing service, binds endpoints to apiPath, handles the action
151 |  * (runLambda) on calls and starts listener
152 |  * @param {Object} [config] Path to the lambdas directory
153 |  */
154 | export const init = (cfg) => {
155 |   // Setup config
156 |   buildConfig(cfg)
157 |   // Binds to endpoint
158 |   service.all(`${config.apiPath}/:endpoint`, (req, res) => runLambda(req, res))
159 |   // Starts service
160 |   service.listen(config.port, () => {
161 |     if (config.log) log.info(`Service running on ${config.port}`)
162 |   })
163 | }
164 | 
165 |
166 | 167 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /docs/file/src/lib/runner.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/lib/runner.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 51 | 52 |

src/lib/runner.js

53 |
/* eslint no-process-exit: 0 */
 54 | const util = require('util')
 55 | const path = require('path')
 56 | 
 57 | // Sets the lambda from its path
 58 | const lambda = require(path.resolve(`${process.env.lambdas}/${process.argv[2]}/index`))
 59 | 
 60 | /**
 61 |  * Creates the context object passed to lambdas
 62 |  * @property context
 63 |  */
 64 | const context = {
 65 |   /**
 66 |    * Emit a success message with output
 67 |    * @param {Object|String} result The contents of the result
 68 |    */
 69 |   succeed: (result) => {
 70 |     process.send({ type: 'success', output: result })
 71 |     context.done()
 72 |   },
 73 |   /**
 74 |    * Emit an error message with output
 75 |    * @param {Object|String} error The error object or message
 76 |    */
 77 |   fail: (error) => {
 78 |     process.send({ type: 'error', output: error })
 79 |     context.done()
 80 |   },
 81 |   /**
 82 |    * Emit closing metrics and end the lambda process
 83 |    */
 84 |   done: () => {
 85 |     process.send({ type: 'metric', output: { memory: util.inspect(process.memoryUsage()) }})
 86 |     process.send({ type: 'metric', output: { time: process.uptime() }})
 87 |     process.exit()
 88 |   }
 89 | }
 90 | 
 91 | // Call lambda's handler
 92 | lambda.handler(JSON.parse(process.env.event), context)
 93 | 
94 |
95 | 96 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/file/src/router.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/router.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
29 | 30 | 69 | 70 |

src/router.js

71 |
/*
 72 |  * Copyright (c) 2015 TechnologyAdvice
 73 |  */
 74 | 
 75 | /* eslint no-param-reassign: 0 */
 76 | const path = require('path')
 77 | const yaml = require('yamljs')
 78 | 
 79 | import { config, log, service, runLambda } from './app'
 80 | import { fileExists, parseRouteParams } from './util'
 81 | 
 82 | /**
 83 |  * Placeholder for schema object
 84 |  * @parameter {Object} schema
 85 |  */
 86 | export let schema = null
 87 | 
 88 | /**
 89 |  * Placeholder for routes array
 90 |  * @parameter {Array} routes
 91 |  */
 92 | export let routes = []
 93 | 
 94 | 
 95 | /**
 96 |  * Loads the schema from specified file
 97 |  * @param {String} file The file path of the Gateway schema
 98 |  */
 99 | export const loadSchema = (file) => {
100 |   // No checks, YAML error automagically
101 |   schema = yaml.load(path.resolve(file))
102 | }
103 | 
104 | /**
105 |  * Walks schema to look for request methods (verbs), when a method is found it
106 |  * creates a route with the parent node key (the path), the current method,
107 |  * and the properties of that method (the template)
108 |  * @param {Object} node The node to traverse
109 |  * @param {String} prevKey The key of the previous traversal for accessing parent/path
110 |  */
111 | export const walkSchema = (node = schema, prevKey = null) => {
112 |   // Methods indicate traversal stops
113 |   const methods = [ 'GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH' ]
114 |   for (let prop in node) {
115 |     // Ensure prop
116 |     if ({}.hasOwnProperty.call(node, prop)) {
117 |       if (methods.indexOf(prop) >= 0) {
118 |         // Node is a method, push to router
119 |         routes.push({ route: prevKey, method: prop, config: node[prop] })
120 |       } else {
121 |         // Route node, traverse
122 |         walkSchema(node[prop], prop)
123 |       }
124 |     }
125 |   }
126 | }
127 | 
128 | /**
129 |  * Iterates over the properties of the template and calls `parseRouteParams` to
130 |  * convert the bracket-delimited params with colon-lead (Express-style) route
131 |  * params with the template-designated key/property name
132 |  * @param {String} route The route to modify
133 |  * @param {Object} template The template object to match against
134 |  * @returns {String} The formatted route
135 |  */
136 | export const mapTemplateParams = (route, template) => {
137 |   for (let prop in template) {
138 |     if ({}.hasOwnProperty.call(template, prop)) {
139 |       let param = parseRouteParams(template[prop], prop, route)
140 |       if (param) {
141 |         route = param
142 |         delete template[prop]
143 |       }
144 |     }
145 |   }
146 |   // Return modified route and template
147 |   return { route, template }
148 | }
149 | 
150 | /**
151 |  * Ensures that the lambda exists (on init/load) then creates and Express
152 |  * verb+route object for the specific request
153 |  * @param {Object} route The route to add
154 |  */
155 | export const addRoute = (route) => {
156 |   // Build ensure specified Lambda exists
157 |   fileExists(`${config.lambdas}/${route.config.lambda}/index.js`).then(() => {
158 |     // Add method route
159 |     service[route.method.toLowerCase()](config.apiPath + route.route, (req, res) => {
160 |       runLambda(route.config.lambda, route.config.templates['application/json'], req, res)
161 |     })
162 |   })
163 |   .catch(() => {
164 |     log.error('Missing Lambda', { name: route.config.lambda })
165 |   })
166 | }
167 | 
168 | /**
169 |  * Itterates over the routes array to map template parameters, set the route
170 |  * property, config > templates and call `addRoute`
171 |  */
172 | const buildRoutes = () => {
173 |   // Itterate over routes
174 |   routes.forEach((rte) => {
175 |     // Map template params
176 |     let mappedRoutes = mapTemplateParams(rte.route, rte.config.templates['application/json'])
177 |     rte.route = mappedRoutes.route
178 |     rte.config.templates['application/json'] = mappedRoutes.template
179 |     addRoute(rte)
180 |   })
181 | }
182 | 
183 | /**
184 |  * Initializes the routes by walking the Gateway schema then running `buildRoutes`
185 |  * to load into Express object
186 |  */
187 | export const initRoutes = () => {
188 |   // Walk the schema to build routes
189 |   walkSchema(schema)
190 |   // Map params and build express routes
191 |   buildRoutes()
192 | }
193 | 
194 |
195 | 196 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /docs/file/src/runner.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/runner.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
29 | 30 | 69 | 70 |

src/runner.js

71 |
/*
 72 |  * Copyright (c) 2015 TechnologyAdvice
 73 |  */
 74 | 
 75 | /* eslint no-process-exit: 0, no-console: 0 */
 76 | const util = require('util')
 77 | const path = require('path')
 78 | 
 79 | // Override console.log to send messages back through proc emit
 80 | console.log = console.info = console.warn = console.error = console.debug = (...args) => {
 81 |   process.send({
 82 |     type: 'debug',
 83 |     output: {
 84 |       lambda: process.argv[2],
 85 |       data: args
 86 |     }
 87 |   })
 88 | }
 89 | 
 90 | // Sets the lambda from its path
 91 | const lambda = require(path.resolve(`${process.env.lambdas}/${process.argv[2]}/index`))
 92 | 
 93 | /**
 94 |  * Creates the context object passed to lambdas
 95 |  * @property context
 96 |  */
 97 | const context = {
 98 |   /**
 99 |    * Emit a success message with output
100 |    * @param {Object|String} result The contents of the result
101 |    */
102 |   succeed: (result) => {
103 |     process.send({ type: 'success', output: result })
104 |     context.done()
105 |   },
106 |   /**
107 |    * Emit an error message with output
108 |    * @param {Object|String} error The error object or message
109 |    */
110 |   fail: (error) => {
111 |     process.send({ type: 'error', output: error })
112 |     context.done()
113 |   },
114 |   /**
115 |    * Emit closing metrics and end the lambda process
116 |    */
117 |   done: () => {
118 |     process.send({ type: 'metric', output: {
119 |       lambda: process.argv[2],
120 |       event: process.env.event,
121 |       pid: process.pid,
122 |       memory: util.inspect(process.memoryUsage()),
123 |       time: process.uptime()
124 |     }})
125 |     process.exit()
126 |   }
127 | }
128 | 
129 | // Call lambda's handler
130 | lambda.handler(JSON.parse(process.env.event), context)
131 | 
132 |
133 | 134 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/file/src/server.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/server.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 53 | 54 |

src/server.js

55 |
/*eslint no-unused-vars:0 */
56 | import { app } from './lib/app'
57 | app({
58 |   port: 8181,
59 |   lambdas: './build/lambdas/'
60 | })
61 | 
62 |
63 | 64 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/file/src/util.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/util.js | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
29 | 30 | 69 | 70 |

src/util.js

71 |
/*
 72 |  * Copyright (c) 2015 TechnologyAdvice
 73 |  */
 74 | 
 75 | const Promise = require('bluebird')
 76 | const path = require('path')
 77 | const fs = Promise.promisifyAll(require('fs'))
 78 | 
 79 | /**
 80 |  * Checks if file exists
 81 |  * @param {String} file Path to file
 82 |  * @returns {Object} Promise
 83 |  */
 84 | export const fileExists = (file) => {
 85 |   return fs.openAsync(path.resolve(file), 'r')
 86 | }
 87 | 
 88 | /**
 89 |  * Abstracts parsing of routes against template values
 90 |  * @param {String} value The value of the template element
 91 |  * @param {String} key The property name of the template element
 92 |  * @param {String} route The route to check/modify
 93 |  */
 94 | export const parseRouteParams = (value, key, route) => {
 95 |   if (value.indexOf(`$input.params('`) >= 0) {
 96 |     // Remove wrapper
 97 |     let param = value.replace(`$input.params('`, '').replace(`')`, '')
 98 |     // Ensure route contains match, replace
 99 |     if (route.indexOf(`{${param}}`) >= 0) return route.replace(`{${param}}`, `:${key}`)
100 |     // Not matched
101 |     return false
102 |   }
103 |   return false
104 | }
105 | 
106 | /**
107 |  * Abstracts parsing of body against template values
108 |  * @param {String} value The value of the template element
109 |  * @param {Object} req The request object
110 |  * @returns {String} The value of the body property requested by the template
111 |  */
112 | export const parseRequestParams = (value, req) => {
113 |   // Body
114 |   if (value.indexOf('querystring') >= 0) {
115 |     let returnArray = []
116 |     for (let obj in req.query) {
117 |       if (req.query.hasOwnProperty(obj)) {
118 |         const str = obj + '=' + req.query[obj]
119 |         returnArray.push(str)
120 |       }
121 |     }
122 |     const returnObject = '{' + returnArray.join(',') + '}'
123 |     return returnObject
124 |   }
125 |   if (value.indexOf(`$input.json('$`) >= 0) {
126 |     // Get the name to check
127 |     let name = value.replace(`$input.json('$`, '').replace(`')`, '')
128 |     // Return the entire body
129 |     if (!name.length) return req.body
130 |     // Return the specific property of the body (or null if DNE)
131 |     name = name.replace(/^\./, '') // Remove leading dot
132 |     return (req.body && req.body[name]) ? req.body[name] : null
133 |   }
134 |   // Param (querystring or header)
135 |   if (value.indexOf(`$input.params('`) >= 0) {
136 |     // Remove wrapper
137 |     let param = value.replace(`$input.params('`, '').replace(`')`, '')
138 |     // Return if matching querysting
139 |     if (req.query && req.query[param]) return req.query[param]
140 |     // Retrun if matching header (or undefined)
141 |     return req.get(param)
142 |   }
143 |   // Custom value passed through
144 |   return value
145 | }
146 | 
147 |
148 | 149 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechnologyAdvice/glambda/63799f74a4b662094a24fcba0a5fe06398f1d436/docs/image/github.png -------------------------------------------------------------------------------- /docs/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechnologyAdvice/glambda/63799f74a4b662094a24fcba0a5fe06398f1d436/docs/image/search.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
29 | 30 | 69 | 70 |

Build Status 71 | Code Climate 72 | Test Coverage 73 | Dependency Status

74 |

NPM

75 |

GLambda

76 |

AWS Gateway + Lambda Testing Module

77 |

A module for mocking and testing AWS API Gateway 78 | in conjunction with Lambda functions.

79 |

Introduction

80 |

Setup

81 |

Install the module:

82 |
npm install glambda --save-dev
83 |

To see a fully functional demo, see the /test directory. The index.js 84 | file is setup to run using the lambdas and the gateway.yml 85 | file. The tests run against this configuration as well.

86 |

After installing the npm module simply include it in a file where it will run and 87 | set any config options on init:

88 |
// Include the module
 89 | var glambda = require('glambda')
 90 | // Set options and init
 91 | glambda.init({
 92 |   lambdas: './lambdas',
 93 |   schema: './gateway.yml',
 94 |   port: 8181,
 95 |   apiPath: '/api',
 96 |   log: true,
 97 |   cors: {
 98 |     origin: '*',
 99 |     methods: 'GET,PUT,POST,DELETE,OPTIONS',
100 |     headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
101 |   }
102 | })
103 |
104 |

The above shows a standard set of config options:

105 |
    106 |
  • lambdas: Path to the directory containing lambdas
  • 107 |
  • schema: Path to the API gateway YAML config
  • 108 |
  • port: Port on which the HTTP server will run
  • 109 |
  • apiPath: Any path (proceeding root) to include in HTTP requests mapping
  • 110 |
  • log: Wether or not to log to console
  • 111 |
112 |

Simply running the file created above will spin up the service, then accessing 113 | the endpoints via the corresponding lambda name will spawn the Lambda function 114 | and return its results.

115 |

Environment Variables

116 |

The system runs a configuration load process which uses the default values, 117 | overrides with any initialized (passed) config properties and (lastly) checks 118 | for environment variables following the convention GL_{PROPERTY}.

119 |

Note: CORS settings don't currently support environment variables

120 |

The Gateway YAML Configuration

121 |

The gateway.yml format was designed to closely match the 122 | AWS API Gateway. The structure is intended 123 | to appear similar to the Resource (left-hand) pane when editing an API in the 124 | web interface.

125 |
---
126 |   /:
127 |     /foo:
128 |       GET:
129 |           lambda: "foo"
130 |           templates:
131 |             application/json:
132 |               method: "get"
133 |       POST:
134 |           lambda: "foo"
135 |           templates:
136 |             application/json:
137 |               method: "post"
138 |               body: "$input.json('$')"
139 |       /foo/{fooId}:
140 |         GET:
141 |           lambda: "foo"
142 |           templates:
143 |             application/json:
144 |               id: "$input.params('fooId')"
145 |               method: "get"
146 |         PUT:
147 |           lambda: "foo"
148 |           templates:
149 |             application/json:
150 |               id: "$input.params('fooId')"
151 |               baz: "quz"
152 |               body: "$input.json('$')"
153 |
154 |

It's simple to identify the core nodes of the tree, i.e. the paths of the requests 155 | and their associated methods. To explain, the following shows results of a number 156 | of requests made against the above configuration:

157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
PATHMETHODBODYRESPONSE/EVENT
/ANYN/AMETHOD NOT ALLOWED
/fooGETN/A{ method: 'get' }
/fooPOST{ fizz: 'buzz' }{ method: 'post', body: { fizz: 'buzz' }
/foo/123GETN/A{ method: 'get', fooId: 123 }
/foo/123PUT{ baz: 'quz' }{ method: 'put', fooId: 123, body: { baz: 'quz' } }
199 |

Logging

200 |

GLambda will output information from both the service and the Lambdas. An example 201 | of the Lambda-specific log output is below:

202 |
[2015-08-02 14:26:46] INFO: Lambda Processed (...)
203 |         lambda: foo
204 |         event: {"method":"get"}
205 |         pid: 30945
206 |         memory: { rss: 20062208, heapTotal: 9751808, heapUsed: 3989464 }
207 |         time: 0.124
208 |

Notes

209 |

Gateway Templates

210 |

Currently Glambda only supports a single template which must be application/json. 211 | The plan is to expand on this, see Support Multiple Templates 212 | issue for more information.

213 |

Makefile and Scripts

214 |

A Makefile is included for managing build and install tasks. The commands are 215 | then referenced in the package.json scripts if that is the preferred 216 | task method:

217 |
    218 |
  • all (default) will run all build tasks
  • 219 |
  • start will run the main script
  • 220 |
  • clean will remove the /node_modules directories
  • 221 |
  • build will transpile ES2015 code in /src to /build
  • 222 |
  • test will run all spec files in /test/src
  • 223 |
  • cover will run code coverage on all tests
  • 224 |
  • lint will lint all files in /src
  • 225 |
  • doc will run ESDoc on all files in /src and output to /docs
  • 226 |
  • report will run Plato static analysis on /build and output to /report
  • 227 |
  • dev will run...
      228 |
    • linting, then...
    • 229 |
    • tests, then...
    • 230 |
    • build/transpile, then...
    • 231 |
    • the main script.
    • 232 |
    233 |
  • 234 |
  • watch will run the dev task and rerun on change of /src files
  • 235 |
236 |

Test Inidividual File

237 |

An individual spec can be run by specifying the FILE:

238 |
make test FILE=some.spec.js
239 |

The FILE is relative to the test/src/ directory.

240 |

Deploys

241 |

For deploying releases, the deploy TAG={VERSION} can be used where VERSION can be:

242 |
<newversion> | major | minor | patch | premajor
243 |

Both make {COMMAND} and npm run {COMMAND} work for any of the above commands.

244 |

License

245 |

Glambda is licensed under the MIT license. Please see LICENSE.txt for full details.

246 |

Credits

247 |

Glambda was designed and created at TechnologyAdvice.

248 |
249 |
250 | 251 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /docs/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | function adjust() { 5 | window.scrollBy(0, -55); 6 | var el = document.querySelector('.inner-link-active'); 7 | if (el) el.classList.remove('inner-link-active'); 8 | 9 | var el = document.querySelector(location.hash); 10 | if (el) el.classList.add('inner-link-active'); 11 | } 12 | 13 | window.addEventListener('hashchange', adjust); 14 | 15 | if (location.hash) { 16 | setTimeout(adjust, 0); 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /docs/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docs/script/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/script/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p' + pair[2] + ''); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /docs/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-describe')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-describe .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /docs/source.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Source | API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | Home 17 | Identifier 18 | Source 19 | 20 | Repository 21 | 28 |
    29 | 30 | 69 | 70 |

    Source 71 | document86% 72 | 19/22

    73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
    FileIdentifierDocumentSizeLinesUpdated
    src/app.jsbuildConfig 90 | config 91 | init 92 | log 93 | parseErrorCode 94 | parseRequest 95 | procLog 96 | procResponse 97 | runLambda 98 | service 99 | setCORS 100 | setRunner75 %9/125845 byte1992015-09-05 21:41:12 (UTC)
    src/router.jsaddRoute 109 | initRoutes 110 | loadSchema 111 | mapTemplateParams 112 | routes 113 | schema 114 | walkSchema100 %7/73667 byte1222015-09-05 20:39:18 (UTC)
    src/runner.js--1470 byte602015-09-05 20:39:18 (UTC)
    src/util.jsfileExists 131 | parseRequestParams 132 | parseRouteParams100 %3/32401 byte752015-09-06 20:14:26 (UTC)
    140 |
    141 | 142 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs" 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glambda", 3 | "version": "1.3.2", 4 | "description": "A module for mocking and testing AWS API Gateway in conjunction with Lambda functions.", 5 | "main": "build/app.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/TechnologyAdvice/gateway-lambda" 9 | }, 10 | "keywords": [ 11 | "amazon", 12 | "aws", 13 | "api", 14 | "gateway", 15 | "lambda", 16 | "node", 17 | "mock", 18 | "staging", 19 | "testing" 20 | ], 21 | "author": "TechnologyAdvice ", 22 | "scripts": { 23 | "all": "make", 24 | "start": "make start", 25 | "clean": "make clean", 26 | "build": "make build", 27 | "test": "make test", 28 | "cover": "make cover", 29 | "lint": "make lint", 30 | "doc": "make doc", 31 | "report": "make report", 32 | "dev": "make dev" 33 | }, 34 | "license": "MIT", 35 | "devDependencies": { 36 | "assert": "^1.3.0", 37 | "babel": "^5.8.23", 38 | "babel-eslint": "^4.1.3", 39 | "babel-istanbul": "^0.3.20", 40 | "chai": "^3.3.0", 41 | "esdoc": "^0.1.3", 42 | "eslint": "^1.5.1", 43 | "istanbul-harmony": "^0.3.16", 44 | "mocha": "^2.3.3", 45 | "nodemon": "^1.7.0", 46 | "plato": "^1.5.0", 47 | "should": "^7.1.0", 48 | "sinon": "^1.17.0", 49 | "sinon-chai": "^2.8.0", 50 | "supertest": "^1.1.0" 51 | }, 52 | "dependencies": { 53 | "bluebird": "^2.10.1", 54 | "body-parser": "^1.14.0", 55 | "bristol": "^0.3.2", 56 | "express": "^4.13.1", 57 | "lodash": "^3.10.1", 58 | "yamljs": "^0.2.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | // Deps 6 | const express = require('express') 7 | const bodyParser = require('body-parser') 8 | const fork = require('child_process').fork 9 | const path = require('path') 10 | const _ = require('lodash') 11 | 12 | // Setup logs 13 | export const log = require('bristol') 14 | log.addTarget('console').withFormatter('commonInfoModel') 15 | log.addTransform((elem) => { 16 | delete elem.file 17 | delete elem.line 18 | return elem 19 | }) 20 | 21 | // Default path to lambda runner 22 | let runner = path.resolve(__dirname, './runner') 23 | 24 | // Import router 25 | import { loadSchema, initRoutes } from './router' 26 | import { parseRequestParams } from './util' 27 | 28 | /** 29 | * Allows overriding default runner script 30 | * @param {String} runnerPath Path to the runner module 31 | */ 32 | export const setRunner = (runnerPath) => runner = path.resolve(runnerPath) 33 | 34 | /** 35 | * Default config object 36 | * @property config 37 | * @attribute {String} lambdas The path to the lambdas directory 38 | * @attribute {String} schema The path to the Gateway YAML config 39 | * @attribute {Number} port The port for the HTTP service 40 | * @attribute {String} apiPath The request path for the api 41 | * @attribute {Boolean} log Show or repress console output 42 | */ 43 | export let config = { 44 | lambdas: './lambdas', 45 | schema: './gateway.yml', 46 | port: 8181, 47 | apiPath: '/api', 48 | log: true, 49 | cors: { 50 | origin: '*', 51 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 52 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 53 | } 54 | } 55 | 56 | // Express setup 57 | export const service = express() 58 | 59 | // CORS 60 | export const setCORS = () => { 61 | service.use((req, res, next) => { 62 | res.header('Access-Control-Allow-Origin', config.cors.origin) 63 | res.header('Access-Control-Allow-Methods', config.cors.methods) 64 | res.header('Access-Control-Allow-Headers', config.cors.headers) 65 | if (req.method === 'OPTIONS') { 66 | res.send(200) 67 | } else { 68 | next() 69 | } 70 | }) 71 | } 72 | 73 | // Body parser 74 | service.use(bodyParser.json()) 75 | 76 | /** 77 | * Calls log output method 78 | * @param {String} type Type of log message to write 79 | * @param {String|Object} msg Message body of log 80 | */ 81 | export const procLog = (type, ...msg) => { 82 | /* istanbul ignore if */ 83 | if (config.log) log[type](msg[0], msg[1]) 84 | } 85 | 86 | /** 87 | * Parses response body for error code 88 | * @param {String} output Output from context.fail 89 | */ 90 | export const parseErrorCode = (output) => { 91 | const code = output.toString().replace(/^Error: ([1-5]\d\d).+$/, (i, match) => match) 92 | if (code > 100 && code < 600) { 93 | // Return specific code with stripped message 94 | return { 95 | code: parseInt(code, 10), 96 | output: output.replace(`Error: ${code}`, '').trim() 97 | } 98 | } 99 | // Return generic 500 with original message 100 | return { 101 | code: 500, 102 | output: code 103 | } 104 | } 105 | 106 | /** 107 | * Handles response from forked lambda runner procs 108 | * @param {Object} msg Message object from child proc 109 | * @param {Object} [res] Express response object 110 | */ 111 | export const procResponse = (msg, res) => { 112 | switch (msg.type) { 113 | case 'metric': procLog('info', 'Lambda Processed', msg.output); break 114 | case 'debug': procLog('info', 'Lambda Debug', msg.output); break 115 | case 'success': res.status(200).send(msg.output); break 116 | case 'error': 117 | const err = parseErrorCode(msg.output) 118 | res.status(err.code).send(err.output) 119 | break 120 | default: procLog('error', 'Missing response type') 121 | } 122 | } 123 | 124 | /** 125 | * Parses the properties from the template and then calls `parseRequestParams` 126 | * to align variable properties with their template keys 127 | * @param {Object} req The request object 128 | * @param {Object} template The gateway template 129 | * @returns {Object} the full event to be passed to the Lambda 130 | */ 131 | export const parseRequest = (req, template) => { 132 | let tmpBody = {} 133 | for (let prop in template) { 134 | /* istanbul ignore else */ 135 | if ({}.hasOwnProperty.call(template, prop)) { 136 | tmpBody[prop] = parseRequestParams(template[prop], req) 137 | } 138 | } 139 | return tmpBody 140 | } 141 | 142 | /** 143 | * Builds the `event` payload with the request body and then forks a new 144 | * runner process to the requested lambda. Awaits messaging from the lambda 145 | * to return response payload and display log information 146 | * @param {String} lambda The lambda to run 147 | * @param {Object} template The gateway template 148 | * @param {Object} req Express req object 149 | * @param {Object} res Express res object 150 | */ 151 | export const runLambda = (lambda, template, req, res) => { 152 | // Parse body against template 153 | const body = parseRequest(req, template) 154 | // Build event by extending body with params 155 | const event = JSON.stringify(_.extend(body, req.params)) 156 | // Build env to match current envirnment, add lambdas and event 157 | const env = _.extend({ lambdas: config.lambdas, event }, process.env) 158 | // Execute lambda 159 | fork(runner, [ lambda ], { env }).on('message', (msg) => procResponse(msg, res)) 160 | } 161 | 162 | /** 163 | * Combines the default config with any passed to init and overrides (lastly) 164 | * if there are any environment variables set 165 | * @param {Object} [cfg] The config passed through init 166 | */ 167 | export const buildConfig = (cfg) => { 168 | // Against defaults 169 | _.extend(config, cfg) 170 | // Against env vars 171 | for (let prop in config) { 172 | /* istanbul ignore else */ 173 | if ({}.hasOwnProperty.call(config, prop)) { 174 | let envVar = process.env['GL_' + prop.toUpperCase()] 175 | if (envVar) config[prop] = envVar 176 | } 177 | } 178 | // Apply config to CORS 179 | setCORS() 180 | } 181 | 182 | /** 183 | * Initialize the service by building the config, loading the (YAML) Gateway 184 | * API configuration and then initializing routes on Express and finally 185 | * starting the service. 186 | * @param {Object} [config] The main service configuration 187 | */ 188 | export const init = (cfg) => { 189 | // Setup config 190 | buildConfig(cfg) 191 | // Load schema into router 192 | loadSchema(config.schema) 193 | // Initialize all routes from gateway schema 194 | initRoutes() 195 | // Starts service 196 | service.listen(config.port, () => { 197 | procLog('info', 'Service running', { port: config.port }) 198 | }) 199 | } 200 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | /* eslint no-param-reassign: 0 */ 6 | const path = require('path') 7 | const yaml = require('yamljs') 8 | 9 | import { config, log, service, runLambda } from './app' 10 | import { fileExists, parseRouteParams } from './util' 11 | 12 | /** 13 | * Placeholder for schema object 14 | * @parameter {Object} schema 15 | */ 16 | export let schema = null 17 | 18 | /** 19 | * Placeholder for routes array 20 | * @parameter {Array} routes 21 | */ 22 | export let routes = [] 23 | 24 | 25 | /** 26 | * Loads the schema from specified file 27 | * @param {String} file The file path of the Gateway schema 28 | */ 29 | export const loadSchema = (file) => { 30 | // No checks, YAML error automagically 31 | schema = yaml.load(path.resolve(file)) 32 | } 33 | 34 | /** 35 | * Walks schema to look for request methods (verbs), when a method is found it 36 | * creates a route with the parent node key (the path), the current method, 37 | * and the properties of that method (the template) 38 | * @param {Object} node The node to traverse 39 | * @param {String} prevKey The key of the previous traversal for accessing parent/path 40 | */ 41 | export const walkSchema = (node = schema, prevKey = null) => { 42 | // Methods indicate traversal stops 43 | const methods = [ 'GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH' ] 44 | for (let prop in node) { 45 | // Ensure prop 46 | if ({}.hasOwnProperty.call(node, prop)) { 47 | if (methods.indexOf(prop) >= 0) { 48 | // Node is a method, push to router 49 | routes.push({ route: prevKey, method: prop, config: node[prop] }) 50 | } else { 51 | // Route node, traverse 52 | walkSchema(node[prop], prop) 53 | } 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Iterates over the properties of the template and calls `parseRouteParams` to 60 | * convert the bracket-delimited params with colon-lead (Express-style) route 61 | * params with the template-designated key/property name 62 | * @param {String} route The route to modify 63 | * @param {Object} template The template object to match against 64 | * @returns {String} The formatted route 65 | */ 66 | export const mapTemplateParams = (route, template) => { 67 | for (let prop in template) { 68 | if ({}.hasOwnProperty.call(template, prop)) { 69 | let param = parseRouteParams(template[prop], prop, route) 70 | if (param) { 71 | route = param 72 | delete template[prop] 73 | } 74 | } 75 | } 76 | // Return modified route and template 77 | return { route, template } 78 | } 79 | 80 | /** 81 | * Ensures that the lambda exists (on init/load) then creates and Express 82 | * verb+route object for the specific request 83 | * @param {Object} route The route to add 84 | */ 85 | export const addRoute = (route) => { 86 | // Build ensure specified Lambda exists 87 | fileExists(`${config.lambdas}/${route.config.lambda}/index.js`).then(() => { 88 | // Add method route 89 | service[route.method.toLowerCase()](config.apiPath + route.route, (req, res) => { 90 | runLambda(route.config.lambda, route.config.templates['application/json'], req, res) 91 | }) 92 | }) 93 | .catch(() => { 94 | log.error('Missing Lambda', { name: route.config.lambda }) 95 | }) 96 | } 97 | 98 | /** 99 | * Itterates over the routes array to map template parameters, set the route 100 | * property, config > templates and call `addRoute` 101 | */ 102 | const buildRoutes = () => { 103 | // Itterate over routes 104 | routes.forEach((rte) => { 105 | // Map template params 106 | let mappedRoutes = mapTemplateParams(rte.route, rte.config.templates['application/json']) 107 | rte.route = mappedRoutes.route 108 | rte.config.templates['application/json'] = mappedRoutes.template 109 | addRoute(rte) 110 | }) 111 | } 112 | 113 | /** 114 | * Initializes the routes by walking the Gateway schema then running `buildRoutes` 115 | * to load into Express object 116 | */ 117 | export const initRoutes = () => { 118 | // Walk the schema to build routes 119 | walkSchema(schema) 120 | // Map params and build express routes 121 | buildRoutes() 122 | } 123 | -------------------------------------------------------------------------------- /src/runner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | /* eslint no-process-exit: 0, no-console: 0 */ 6 | const util = require('util') 7 | const path = require('path') 8 | 9 | // Override console.log to send messages back through proc emit 10 | console.log = console.info = console.warn = console.error = console.debug = (...args) => { 11 | process.send({ 12 | type: 'debug', 13 | output: { 14 | lambda: process.argv[2], 15 | data: args 16 | } 17 | }) 18 | } 19 | 20 | // Sets the lambda from its path 21 | const lambda = require(path.resolve(`${process.env.lambdas}/${process.argv[2]}/index`)) 22 | 23 | /** 24 | * Creates the context object passed to lambdas 25 | * @property context 26 | */ 27 | const context = { 28 | /** 29 | * Emit a success message with output 30 | * @param {Object|String} result The contents of the result 31 | */ 32 | succeed: (result) => { 33 | process.send({ type: 'success', output: result }) 34 | context.done() 35 | }, 36 | /** 37 | * Emit an error message with output 38 | * @param {Object|String} error The error object or message 39 | */ 40 | fail: (error) => { 41 | process.send({ type: 'error', output: error }) 42 | context.done() 43 | }, 44 | /** 45 | * Emit closing metrics and end the lambda process 46 | */ 47 | done: () => { 48 | process.send({ type: 'metric', output: { 49 | lambda: process.argv[2], 50 | event: process.env.event, 51 | pid: process.pid, 52 | memory: util.inspect(process.memoryUsage()), 53 | time: process.uptime() 54 | }}) 55 | process.exit() 56 | } 57 | } 58 | 59 | // Call lambda's handler 60 | lambda.handler(JSON.parse(process.env.event), context) 61 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 TechnologyAdvice 3 | */ 4 | 5 | const Promise = require('bluebird') 6 | const path = require('path') 7 | const fs = Promise.promisifyAll(require('fs')) 8 | 9 | /** 10 | * Checks if file exists 11 | * @param {String} file Path to file 12 | * @returns {Object} Promise 13 | */ 14 | export const fileExists = (file) => { 15 | return fs.openAsync(path.resolve(file), 'r') 16 | } 17 | 18 | /** 19 | * Abstracts parsing of routes against template values 20 | * @param {String} value The value of the template element 21 | * @param {String} key The property name of the template element 22 | * @param {String} route The route to check/modify 23 | */ 24 | export const parseRouteParams = (value, key, route) => { 25 | if (value.indexOf(`$input.params('`) >= 0) { 26 | // Remove wrapper 27 | let param = value.replace(`$input.params('`, '').replace(`')`, '') 28 | // Ensure route contains match, replace 29 | if (route.indexOf(`{${param}}`) >= 0) return route.replace(`{${param}}`, `:${key}`) 30 | // Not matched 31 | return false 32 | } 33 | return false 34 | } 35 | 36 | /** 37 | * Abstracts parsing of body against template values 38 | * @param {String} value The value of the template element 39 | * @param {Object} req The request object 40 | * @returns {String} The value of the body property requested by the template 41 | */ 42 | export const parseRequestParams = (value, req) => { 43 | // Body 44 | if (value.indexOf('querystring') >= 0) { 45 | let returnArray = [] 46 | for (let obj in req.query) { 47 | if (req.query.hasOwnProperty(obj)) { 48 | const str = obj + '=' + req.query[obj] 49 | returnArray.push(str) 50 | } 51 | } 52 | const returnObject = '{' + returnArray.join(',') + '}' 53 | return returnObject 54 | } 55 | if (value.indexOf(`$input.json('$`) >= 0) { 56 | // Get the name to check 57 | let name = value.replace(`$input.json('$`, '').replace(`')`, '') 58 | // Return the entire body 59 | if (!name.length) return req.body 60 | // Return the specific property of the body (or null if DNE) 61 | name = name.replace(/^\./, '') // Remove leading dot 62 | return (req.body && req.body[name]) ? req.body[name] : null 63 | } 64 | // Param (querystring or header) 65 | if (value.indexOf(`$input.params('`) >= 0) { 66 | // Remove wrapper 67 | let param = value.replace(`$input.params('`, '').replace(`')`, '') 68 | // Return if matching querysting 69 | if (req.query && req.query[param]) return req.query[param] 70 | // Retrun if matching header (or undefined) 71 | return req.get(param) 72 | } 73 | // Custom value passed through 74 | return value 75 | } 76 | -------------------------------------------------------------------------------- /test/gateway.yml: -------------------------------------------------------------------------------- 1 | --- 2 | /: 3 | /foo: 4 | GET: 5 | lambda: "foo" 6 | templates: 7 | application/json: 8 | method: "get" 9 | contentType: "$input.params('Content-Type')" 10 | queryTest: "$input.params('querytest')" 11 | POST: 12 | lambda: "foo" 13 | templates: 14 | application/json: 15 | method: "post" 16 | body: "$input.json('$')" 17 | /foo/{fooId}: 18 | GET: 19 | lambda: "foo" 20 | templates: 21 | application/json: 22 | id: "$input.params('fooId')" 23 | method: "get" 24 | PUT: 25 | lambda: "foo" 26 | templates: 27 | application/json: 28 | id: "$input.params('fooId')" 29 | baz: "quz" 30 | body: "$input.json('$')" -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var glambda = require('../build/app') 2 | 3 | // Run 4 | glambda.init({ 5 | lambdas: './test/lambdas', 6 | schema: './test/gateway.yml', 7 | port: 8181, 8 | apiPath: '/api', 9 | log: true, 10 | cors: { 11 | origin: '*', 12 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 13 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 14 | } 15 | }) -------------------------------------------------------------------------------- /test/lambdas/foo/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | if (event.failTest) { 3 | context.fail('Fail test') 4 | } else { 5 | context.succeed(event) 6 | } 7 | } -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import sinon from 'sinon' 3 | import schai from 'sinon-chai' 4 | global.request = require('supertest') 5 | global.assert = require('assert') 6 | global.should = require('should') 7 | global.sinon = sinon 8 | global.expect = chai.expect 9 | chai.use(schai) -------------------------------------------------------------------------------- /test/src/app.spec.js: -------------------------------------------------------------------------------- 1 | /* global sinon, expect, request, describe, it, before, after */ 2 | import '../setup' 3 | import { init, config, log, service, setCORS, parseErrorCode, procResponse, buildConfig, setRunner, parseRequest } from '../../src/app' 4 | 5 | const url = 'http://localhost:8181/api/' 6 | 7 | describe('app', () => { 8 | 9 | describe('config', () => { 10 | 11 | it('overrides defaults with passed object', () => { 12 | const testConfig = { 13 | lambdas: 'test-override', 14 | schema: 'gateway-override', 15 | port: 1111, 16 | apiPath: '/test-override', 17 | log: false, 18 | cors: { 19 | origin: '*', 20 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 21 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 22 | } 23 | } 24 | buildConfig(testConfig) 25 | expect(config).to.deep.equal(testConfig) 26 | }) 27 | 28 | it('overvides config with environment variables', () => { 29 | process.env.GL_LAMBDAS='test-env' 30 | process.env.GL_SCHEMA='gateway-env' 31 | process.env.GL_PORT='2222' 32 | process.env.GL_APIPATH='/test-env' 33 | buildConfig() 34 | expect(config).to.deep.equal({ 35 | lambdas: 'test-env', 36 | schema: 'gateway-env', 37 | port: '2222', 38 | apiPath: '/test-env', 39 | log: false, 40 | cors: { 41 | origin: '*', 42 | methods: 'GET,PUT,POST,DELETE,OPTIONS', 43 | headers: 'Content-Type, Authorization, Content-Length, X-Requested-With' 44 | } 45 | }) 46 | }) 47 | 48 | after(() => { 49 | delete process.env.GL_LAMBDAS 50 | delete process.env.GL_SCHEMA 51 | delete process.env.GL_PORT 52 | delete process.env.GL_APIPATH 53 | }) 54 | 55 | }) 56 | 57 | describe('setCORS', () => { 58 | it('sets CORS properties on the service object', () => { 59 | config.cors.methods = 'GET,PUT,POST,DELETE,OPTIONS,SCAN' 60 | setCORS(); 61 | }) 62 | }) 63 | 64 | describe('parseErrorCode', () => { 65 | it('returns a specific code when matched in context.fail response', () => { 66 | const response = parseErrorCode('Error: 300 This is broken'); 67 | expect(response.code).to.equal(300) 68 | }); 69 | it('returns a 500 when no match on context.fail response', () => { 70 | const response = parseErrorCode('Blah blah blah'); 71 | expect(response.code).to.equal(500); 72 | }) 73 | }); 74 | 75 | describe('procResponse', () => { 76 | 77 | let resStub = { 78 | status: function (code) { 79 | return { 80 | send: function (msg) { 81 | return { code: code, msg: msg } 82 | } 83 | } 84 | } 85 | } 86 | 87 | let responseSpy = sinon.spy(resStub, 'status') 88 | 89 | it('logs info on metrics case', () => { 90 | procResponse({ type: 'metric', output: 'test' }) 91 | sinon.mock(log).expects('info').once 92 | }) 93 | 94 | it('logs error on default case', () => { 95 | procResponse({ type: null, output: 'no type'}) 96 | sinon.mock(log).expects('error').once 97 | }) 98 | 99 | it('responds on success case', () => { 100 | procResponse({ type: 'success', output: 'response' }, resStub) 101 | expect(responseSpy).to.have.been.called 102 | }) 103 | 104 | it('responds on error case', () => { 105 | procResponse({ type: 'error', output: 'response' }, resStub) 106 | expect(responseSpy).to.have.been.called 107 | }) 108 | 109 | }) 110 | 111 | describe('parseBody', () => { 112 | 113 | it('combines custom params and req.body to create event object', () => { 114 | const req = { body: { foo: 'bar' } } 115 | const template = { baz: 'quz', body: `$input.json('$')` } 116 | const event = parseRequest(req, template) 117 | const shouldBe = { baz: 'quz', body: { foo: 'bar' } } 118 | expect(event).to.deep.equal(shouldBe) 119 | }) 120 | 121 | }) 122 | 123 | describe('requests', () => { 124 | 125 | before(() => { 126 | // Set runner 127 | setRunner('./build/runner') 128 | // Start app 129 | init({ 130 | port: 8181, 131 | lambdas: './test/lambdas', 132 | schema: './test/gateway.yml', 133 | apiPath: '/api', 134 | log: false 135 | }) 136 | }) 137 | 138 | it('responds with the correct method and request params', (done) => { 139 | request(url) 140 | .get('foo/someId') 141 | .expect(200) 142 | .end((err, res) => { 143 | if (err) { 144 | done(err) 145 | return 146 | } 147 | res.body.should.have.property('method') 148 | res.body.method.should.equal('get') 149 | res.body.should.have.property('id') 150 | res.body.id.should.equal('someId') 151 | done() 152 | }) 153 | }) 154 | 155 | it('responds with correct header values', (done) => { 156 | request(url) 157 | .get('foo') 158 | .set('Content-Type', 'application/json') 159 | .expect(200) 160 | .end((err, res) => { 161 | if (err) { 162 | done(err) 163 | return 164 | } 165 | res.body.should.have.property('contentType') 166 | res.body.contentType.should.equal('application/json') 167 | done() 168 | }) 169 | }) 170 | 171 | it('responds with the correct querystring values', (done) => { 172 | request(url) 173 | .get('foo?querytest=bar') 174 | .expect(200) 175 | .end((err, res) => { 176 | if (err) { 177 | done(err) 178 | return 179 | } 180 | res.body.should.have.property('queryTest') 181 | res.body.queryTest.should.equal('bar') 182 | done() 183 | }) 184 | }) 185 | 186 | it('responds with correct event property values', (done) => { 187 | request(url) 188 | .put('foo/someId') 189 | .send({ foo: 'bar' }) 190 | .end((err, res) => { 191 | if (err) { 192 | done(err) 193 | return 194 | } 195 | let responseText = res.body 196 | // Custom pass-through 197 | responseText.should.have.property('baz') 198 | responseText.baz.should.equal('quz') 199 | // Body pass-through 200 | responseText.should.have.property('body') 201 | responseText.body.should.have.property('foo') 202 | responseText.body.foo.should.equal('bar') 203 | done() 204 | }) 205 | }) 206 | 207 | it('responds with a 500 on fail', (done) => { 208 | request(url) 209 | .put('foo/someId') 210 | .send({ failTest: true }) 211 | .expect(500) 212 | .end(() => { 213 | done() 214 | }) 215 | }) 216 | 217 | }) 218 | 219 | }) -------------------------------------------------------------------------------- /test/src/router.spec.js: -------------------------------------------------------------------------------- 1 | /* global expect, sinon, request, describe, it, before, after */ 2 | import '../setup' 3 | import { schema, loadSchema, walkSchema, routes, addRoute, mapTemplateParams } from '../../src/router' 4 | import { service } from '../../src/app' 5 | 6 | const schemaPath = './test/gateway.yml' 7 | 8 | describe('router', () => { 9 | 10 | describe('setSchema', () => { 11 | 12 | it('sets the schema object based on the file passed', () => { 13 | loadSchema(schemaPath) 14 | expect(schema).to.be.an.object 15 | walkSchema() 16 | }) 17 | 18 | }) 19 | 20 | describe('walkSchema', () => { 21 | 22 | it('creates array of route objects from the schema', () => { 23 | walkSchema() 24 | expect(routes).to.be.an.array 25 | expect(routes[0]).to.be.an.object 26 | }) 27 | 28 | }) 29 | 30 | describe('mapTemplateParams', () => { 31 | 32 | it('identifies template params and replaces route param names', () => { 33 | let route = '/foo/{fooId}' 34 | let template = { id: '$input.params(\'fooId\')', test: 'foo' } 35 | let output = mapTemplateParams(route, template) 36 | expect(output).to.deep.equal({ route: '/foo/:id', template: { test: 'foo' } }) 37 | }) 38 | 39 | }) 40 | 41 | describe('addRoute', () => { 42 | 43 | it('fails if lambda file does not exist', (done) => { 44 | let testMissingLambda = { 45 | route: 'notExists', 46 | method: 'get', 47 | config: { 48 | lambda: 'notExists', 49 | templates: { 50 | 'application/json': {} 51 | } 52 | } 53 | } 54 | expect(addRoute(testMissingLambda)).to.throw 55 | done() 56 | }) 57 | }) 58 | 59 | }) -------------------------------------------------------------------------------- /test/src/util.spec.js: -------------------------------------------------------------------------------- 1 | /* global expect, request, describe, it, before, after */ 2 | import '../setup' 3 | import { fileExists, parseRouteParams, parseRequestParams } from '../../src/util' 4 | 5 | describe('util', () => { 6 | 7 | describe('fileExists', () => { 8 | it('rejects when the file does not exist', (done) => { 9 | fileExists('./test/dne.txt').catch(() => done()) 10 | }) 11 | 12 | it('resolves when the file exists', (done) => { 13 | fileExists('./test/gateway.yml').then(() => done()) 14 | }) 15 | }) 16 | 17 | describe('parseRouteParams', () => { 18 | it('returns a properly formatted route', () => { 19 | const testCase = parseRouteParams(`$input.params('fooId')`, 'id', '/foo/{fooId}') 20 | expect(testCase).to.equal('/foo/:id') 21 | }) 22 | 23 | it('returns false if param is not in route', () => { 24 | const testCase = parseRouteParams(`$input.params('notInRoute')`, 'id', '/foo/{fooId}') 25 | expect(testCase).to.be.false 26 | }) 27 | }) 28 | 29 | describe('parseRequestParams', () => { 30 | 31 | const testBody = { 32 | body: { 33 | foo: 'bar' 34 | } 35 | } 36 | 37 | const testQuery = { 38 | query: { 39 | baz: 'quz' 40 | } 41 | } 42 | 43 | const testHeader = { 44 | get: (name) => { 45 | return 'buzz' 46 | } 47 | } 48 | 49 | it('returns the full body when input.json($) is requested', () => { 50 | const testCase = parseRequestParams(`$input.json('$')`, testBody) 51 | expect(testCase).to.deep.equal(testBody.body) 52 | }) 53 | 54 | it('returns the specific body property when input.json($.PROP) is requested', () => { 55 | const testCase = parseRequestParams(`$input.json('$.foo')`, testBody) 56 | expect(testCase).to.equal(testBody.body.foo) 57 | }) 58 | 59 | it('returns the querystring value when input.params(PROP) is in the query', () => { 60 | const testCase = parseRequestParams(`$input.params('baz')`, testQuery) 61 | expect(testCase).to.equal('quz') 62 | }) 63 | 64 | it('returns the header value when input.params(PROP) is in the header', () => { 65 | const testCase = parseRequestParams(`$input.params('some-header')`, testHeader) 66 | expect(testCase).to.equal('buzz') 67 | }) 68 | 69 | it('returns the value if no parameters matched', () => { 70 | const testCase = parseRequestParams('fizz', testBody) 71 | expect(testCase).to.equal('fizz') 72 | }) 73 | }) 74 | 75 | }) --------------------------------------------------------------------------------