Serverless Framework eventの内容をローカルとデプロイ時で入力を出来るだけ同じにする

 handlerの中身を変えずに　eventの内容をデプロイ時とローカル時で同じにしたい。

##今回使う環境
* serverless (v1.26)

##まずは関数を作成

```sh
$ serverless create --template aws-nodejs --name my-service
```

##handlerを少し変更

```js:handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: {
        id: event.id,
        name: event.name
      }
    })
  };

  callback(null, response);
};

```

##テストデータの作成
```sh
$ echo \
  '{
     "id": 1,
     "name": "kuro"
  }' >> event.json
```
## 実行
```sh
$ serverless invoke local -f --path event.json

{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":1,\"name\":\"kuro\"}}"
}
```
そうですね、invoke localでテスト用のjsonを使用した時はevent.keyでそのままアクセスできますね？
ではデプロイした時のeventがどうなっているのかを見てみます。

## デプロイ時のevent
### ローカルで結果を見るために
ローカルで手軽に結果を見るためにserverlessのプラグイン[serverless-offline](https://github.com/dherault/serverless-offline)をインストールします。

```sh
$ npm init
$ npm install -D serverless-offline
```
```diff:serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

+ plugins
+   - serverless-offline 

functions:
  hello:
    handler: handler.hello
```
## handler.jsを変更
```diff:handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
+ console.log("[LOG]", event);
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: {
        id: event.id,
        name: event.name
      }
    })
  };

  callback(null, response);
};

```

## ルーティングの設定
```diff:serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

functions:
  hello:
    handler: handler.hello
+    events:
+      - http:
+          path: /hello
+          method: get

```
## 実行
```sh:window1
$ serverless offline

Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000
```
別ターミナルで

```sh:window2
$ curl "localhost:3000/hello?id=1&name=tarp"
{"message":"Go Serverless v1.0! Your function executed successfully!","input":{}}
```

元のターミナルを見てみると

```sh:window1

Serverless: GET /hello (λ: hello)
[LOG] { headers:
   { Host: 'localhost:3000',
     'User-Agent': 'curl/7.54.0',
     Accept: '*/*' },
  path: '/hello',
  pathParameters: null,
  requestContext:
   { accountId: 'offlineContext_accountId',
     resourceId: 'offlineContext_resourceId',
     apiId: 'offlineContext_apiId',
     stage: 'dev',
     requestId: 'offlineContext_requestId_',
     identity:
      { cognitoIdentityPoolId: 'offlineContext_cognitoIdentityPoolId',
        accountId: 'offlineContext_accountId',
        cognitoIdentityId: 'offlineContext_cognitoIdentityId',
        caller: 'offlineContext_caller',
        apiKey: 'offlineContext_apiKey',
        sourceIp: '127.0.0.1',
        cognitoAuthenticationType: 'offlineContext_cognitoAuthenticationType',
        cognitoAuthenticationProvider: 'offlineContext_cognitoAuthenticationProvider',
        userArn: 'offlineContext_userArn',
        userAgent: 'curl/7.54.0',
        user: 'offlineContext_user' },
     authorizer:
      { principalId: 'offlineContext_authorizer_principalId',
        claims: undefined },
     resourcePath: '/hello',
     httpMethod: 'GET' },
  resource: '/hello',
  httpMethod: 'GET',
  queryStringParameters: { id: '1', name: 'kuro' },
  stageVariables: null,
  body: null,
  isOffline: true }
Serverless: [200] {"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"}
```
このように実際には必要ないものがeventの中に入っており。 パラメータにアクセスするには`event.queryStringParameters`でアクセスしなくてはいけません。
HTTPmethodがpostの場合は JSON.parse(event.body)のようにしてあげなくてはいけません。
デプロイ環境かローカル環境かの場合分けとかなんかあんまりしたくありません。。
httpMethodもヘッダーの情報もリソースの情報も小さいapiを作るのに必要ありませんよね。

なので、これを解決するために serverless0.5系お馴染みの[mapping-template](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html)を使うことにします。

## mapping-templateを有効にするために
serverless 1系でmapping-templateを使用するにはserverlessのカスタムリクエストテンプレートというものを使います。使うためには serverlessのlambdaとhttpエンドポイントの統合の設定を変えなければなりません。
function句を変えていきます。
### serverless.ymlを変更
```yml:serverless.yml
  hello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda　#デフォルトではlambda-proxy
```

##設定が終わったので実際にマッピングテンプレートを使用して試して見る
### serverless.ymlを変更
```yml:serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

custom:
  # query string
  idFromParams: '"id": "$input.params("id")"'　 
  nameFromParams: '"name": "$input.params("name")"'
  # json body
  idFromJson: '"id": $input.json("$.id")'
  nameFromJson: '"name": $input.json("$.name")'

functions:
  getHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda
          request:
            template:
              application/json: '{
                ${self:custom.idFromParams},
                ${self:custom.nameFromParams}
              }'
  postHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: post
          integration: lambda
          request:
            template:
              application/json: '{
                ${self:custom.idFromJson},
                ${self:custom.nameFromJson}
              }'

```

```sh:window1
$ serverless offline
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for getHello:
Serverless: GET /hello

Serverless: Routes for postHello:
Serverless: POST /hello

Serverless: Offline listening on http://localhost:3000
```

```sh:window2
$ curl "localhost:3000/hello?id=1&name=taro"
{"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":\"1\",\"name\":\"taro\"}}"}⏎
```

```sh:window1
serverless offline
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for getHello:
Serverless: GET /hello

Serverless: Routes for postHello:
Serverless: POST /hello

Serverless: Offline listening on http://localhost:3000

Serverless: GET /hello (λ: getHello)
Serverless: The first request might take a few extra seconds
[LOG] { id: '1', name: 'taro', isOffline: true, stageVariables: {} }
Serverless: [200] {"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":\"1\",\"name\":\"taro\"}}"}
```

postでjsonをうけとった時も同じようにeventの値にアクセスできるはずです。
このようにserverless-offlineによって設定される値はあるものの使わない値がeventの中に入らず、handlerの中身を変えずにeventの内容にデプロイ時もローカルと同じようにアクセスできるようになりました。

今回使用したファイルたち

* [serverless-mapping-template](https://github.com/kurouw/serverless-mapping-template)

## おまけ
### url encodeされたリクエストも同じようにマッピングする
```yml:config/serverless/urlDecodeTmpl.yml
|
    #if ($context.httpMethod == "POST")
      #set($rawAPIData = $input.path('$'))
      #set($rawAPIData = $rawAPIData.replace('"', '\"').replace('%40', '@'))
    #else
      #set($rawAPIData = "")
    #end

    #set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())
    #if ($countAmpersands == 0)
      #set($rawPostData = $rawAPIData + "&")
    #end

    #set($tokenisedAmpersand = $rawAPIData.split("&"))
    #set($tokenisedEquals = [])

    #foreach( $kvPair in $tokenisedAmpersand )
      #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
      #if ($countEquals == 1)
        #set($kvTokenised = $kvPair.split("="))
      #if ($kvTokenised[0].length() > 0)
        #set($devNull = $tokenisedEquals.add($kvPair))
      #end
      #end
    #end
    {
    #foreach( $kvPair in $tokenisedEquals )
      #set($kvTokenised = $kvPair.split("="))
      #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
        #set($kvValue = $kvTokenised[1])
      #else
        #set($kvValue = "")
      #end
      #if( $foreach.hasNext )
        #set($itemDelimiter = ",")
      #else
        #set($itemDelimiter = "")
      #end
    "$kvTokenised[0]" : "$kvValue"$itemDelimiter
    #end
    }
```
```diff:serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

custom:
+  urlDecode: ${file(./config/serverless/urlDecodeTmpl.yml)
  # query string
  idFromParams: '"id": "$input.params("id")"'
  nameFromParams: '"name": "$input.params("name")"'
  # json body
  idFromJson: '"id": $input.json("$.id")'
  nameFromJson: '"name": $input.json("$.name")'

functions:
  getHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda
          request:
            template:
+              application/x-www-form-urlencoded: ${self:custom.urlDecode}
              application/json: '{
                ${self:custom.idFromParams},
                ${self:custom.nameFromParams}
              }'
  postHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: post
          integration: lambda
          request:
            template:
+              application/x-www-form-urlencoded: ${self:custom.urlDecode}
              application/json: '{
                ${self:custom.idFromJson},
                ${self:custom.nameFromJson}
              }'

```