[AWS SAM] Swaggerを使用したAPI定義

1. [[AWS SAM] 概要、Hello World](https://qiita.com/takmot/items/27f7145f953fc62bdf6b)
2. [[AWS SAM] Lambda関数からS3アクセス](https://qiita.com/takmot/items/6c8cefe94f80a214aeb0)
3. [AWS SAM] Swaggerを使用したAPI定義 （※本記事）

# Swaggerについて

RESTful APIを構築するためのオープンソースのフレームワーク
記述形式はJSONまたはYAML
バージョン2と3があり記述方法が異なるため注意
Swaggerで定義したAPIをSAMテンプレートから参照して使用する

# 【SAM】API定義とSwaggerファイル連携



## 必須項目のみでのAPI定義

以下のようにAPI Gatewayを表すTypeと、デプロイするステージ名が必須項目

```yaml
Resources:
  RestApi:
    Type: AWS::Serverless::Api # API Gateway
    Properties:
      StageName: Dev # デプロイするステージ名
```

このAPIをLambdaのトリガとする場合、Lambdaの定義は以下のようになる

```yaml
  GetDataFunction:  
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: get-data/
      Handler: function.lambda_handler
      Runtime: python3.8
      Events:
        getData:
          Type: Api
          Properties:
            Path: /api/devices/{SerialNumber}
            Method: get
            RestApiId: !Ref RestApi # RestApiの定義を参照
```



## SAMからSwaggerの参照

以下のようにDefinitionBodyでSwaggerファイルのパスを指定する

```yaml
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml # swaggerファイルのパス指定
```

# 【Swagger】API定義

パス`/api/devices/{SerialNumber}`のGET、POSTメソッドを以下で定義

```yaml
openapi: 3.0.1
info:
  description: 'REST API 定義'
  version: '1.0.0'
  title: 
    Fn::Sub: ${AWS::StackName}_restApi # API Gatewayに生成されるAPI名
paths:
  /api/devices/{SerialNumber}: # APIのパス
    get: # GETメソッド
      summary: 'デバイス情報取得API'
      parameters:
        - name: SerialNumber
          in: path # パラメータの場所：パスパラメータ
          description: 'デバイスシリアル番号'
          required: true # 必須パラメータ
          schema: # パラメータ構造
            type: string
      responses:
        200:
          description: '成功時のレスポンス'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device'
      x-amazon-apigateway-integration:
        uri: # APIからキックするLambda関数のARN
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetDataFunction.Arn}/invocations
        passthroughBehavior: when_no_templates
        httpMethod: POST # Lambda関数を呼び出す場合はPOST
        type: aws_proxy # Lambda関数を呼び出す場合はaws_proxy
    post: # POSTメソッド
      summary: 'デバイス情報設定API'
      parameters:
        - name: SerialNumber
          in: path
          description: 'デバイスシリアル番号'
          required: true
          schema:
            type: string
      requestBody: # bodyでデータを受け取る
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/device'
      responses:
        200:
          description: '成功時のレスポンス'
      x-amazon-apigateway-integration:
        uri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SetDataFunction.Arn}/invocations
        passthroughBehavior: when_no_templates
        httpMethod: POST
        type: aws_proxy

components:
  schemas:
    device: # {'SerialNumber': 'abc-001', 'type': 1, 'status': 'working', 'power': 50}を定義
      type: object
      properties:
        SerialNumber:
          type: string
        type:
          type: integer
        status:
          type: string
        power:
          type: integer
    deviceList: # deviceのListを定義
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/device'
```

`x-amazon-apigateway-integration:`はAPI Gateway, Lambda連携で必要な設定、詳細は[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html)参照

# 【SAM】その他API設定



## API Gatewayへのアクセスのコントロール設定

今回はIAMアクセス許可を設定したいため以下のようにAuthを追加

```yaml
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      Auth:
        DefaultAuthorizer: AWS_IAM # IAMアクセス許可
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml
```

その他のアクセスコントロールについては[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-controlling-access-to-apis.html)を参照



## CORS対応

```yaml
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      Auth:
        AddDefaultAuthorizerToCorsPreflight: false # OPTIONSメソッドにIAM認証をつけないための設定
        DefaultAuthorizer: AWS_IAM
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml
      Cors: # CORS追加
        # 以下マネージメントコンソールでCORS設定した場合の内容に合わせて設定
        AllowOrigin: "'*'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowMethods: "'GET, OPTIONS'"
        AllowCredentials: "'true'"
```
Lambdaプロキシ統合を使用している場合は、
上記と合わせてLambda側でレスポンスヘッダを設定する必要がある

```python
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        }
    }
```

## Stageにデプロイされる問題

勝手に`Stage`ステージにデプロイされてしまうため以下のようにOpenApiVersionを追加する

```yaml
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      Auth:
        AddDefaultAuthorizerToCorsPreflight: false
        DefaultAuthorizer: AWS_IAM
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml
      Cors:
        AllowOrigin: "'*'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowMethods: "'GET, OPTIONS'"
        AllowCredentials: "'true'"
      OpenApiVersion: 3.0.2 # 追加
```

# 参考記事

[【初心者向け】SwaggerとAWS SAMを使ってWebAPIを簡単に作ってみた](https://dev.classmethod.jp/articles/serverless-swagger-apigateway/)
[API仕様書書くならswagger v2.0からv3.0に変更する際のポイント](https://qiita.com/shunichi_com/items/e63114f8d67beba14bfd)