laravel-openapiライブラリ ドキュメントを出力してみる vol3 レスポンス

# 概要

- laravel-openapiにてレスポンスを定義する方法をまとめる。

# 前提

- 出力されたOpenAPIドキュメントは下記の方法で正しいか確認する事ができる。
    - [Swagger Editorをローカルに落として開く](https://qiita.com/miriwo/items/93a845f03435fbbce3bd)
- 下記の内容が完了していること。
    - [laravel-openapi ドキュメントを出力してみる vol2 リクエストボディー](https://qiita.com/miriwo/items/e02cf0833856117144ba)
    - 今回アノテーションを追記するコントローラーの関数は上記記事の中で定義している。

# 方法

1. レスポンスのスキーマはリクエストボディーのときと同じでスキーマクラスを用いて定義する。
1. 下記コマンドを実行してUserUpdateOkResponseSchema.phpを作成する。

    ```terminal
    $ php artisan openapi:make-schema UserUpdateOkResponseSchema
    ```

1. `アプリ名ディレクトリ/app/OpenApi/Schemas`に`UserUpdateOkResponseSchema.php`が作成される。
1. 下記のようなスキーマを定義したいとする。

    ```json
    {
      "id": 1,
      "name": "ユーザー名",
      "email": "test@example.com",
      "created_at": "YYYY-MM-DD hh:ii:ss"
    }
    ```

1. 下記のように記載することで上記のスキーマを表現する事ができる。（Reusableクラスのuseとimplementsも忘れずに）

    ```アプリ名ディレクトリ/app/OpenApi/Schemas/UserUpdateOkResponseSchema.php
    <?php
    
    namespace App\OpenApi\Schemas;
    
    use GoldSpecDigital\ObjectOrientedOAS\Contracts\SchemaContract;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AllOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AnyOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Not;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\OneOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
    use Vyuldashev\LaravelOpenApi\Contracts\Reusable;
    use Vyuldashev\LaravelOpenApi\Factories\SchemaFactory;
    
    class UserUpdateOkResponseSchema extends SchemaFactory implements Reusable
    {
        /**
         * @return AllOf|OneOf|AnyOf|Not|Schema
         */
        public function build(): SchemaContract
        {
            return Schema::object('UserUpdateOkResponse')
                ->properties(
                    Schema::integer('id')
                        ->description('ユーザーID')
                        ->example(1),
                    Schema::string('name')
                        ->description('ユーザー名')
                        ->example('XXX YYY'),
                    Schema::string('email')
                        ->description('ユーザーメールアドレス')
                        ->example('test@example.com'),
                    Schema::string('created_at')
                        ->description('初回登録日時')
                        ->example('YYYY-MM-DD hh:ii:ss'),
                );
        }
    }
    ```

1. レスポンスそのものはレスポンスクラスを用いて定義し今直前で定義したスキーマクラスを呼び出す。
1. 下記コマンドを実行してUserUpdateOkResponse.phpを作成する。

    ```terminal
    $ php artisan openapi:make-response UserUpdateOkResponse
    ```

1. `アプリ名ディレクトリ/app/OpenApi/Response`に`UserUpdateOkResponse.php`が作成される。
1. 下記のように記載して先に定義したUserUpdateOkResponseSchemaクラスを呼び出す。（MediaTypeとUserUpdateOkResponseSchemaのuseも忘れずに）

    ```アプリ名ディレクトリ/app/OpenApi/Response/UserUpdateOkResponse.php
    <?php
    
    namespace App\OpenApi\Responses;
    
    use App\OpenApi\Schemas\UserUpdateOkResponseSchema;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Response;
    use Vyuldashev\LaravelOpenApi\Factories\ResponseFactory;
    
    class UserUpdateOkResponse extends ResponseFactory
    {
        public function build(): Response
        {
            return Response::ok()
                ->description('成功レスポンス')
                ->content(
                    MediaType::json()
                        ->schema(UserUpdateOkResponseSchema::ref())
                );
        }
    }
    ```

11. 下記のようにUserController.phpを記載してUserUpdateOkResponseクラスを呼び出す。（UserUpdateResponseのuseを忘れずに）

    ```アプリ名ディレクトリ/app/Http/Controllers/UserController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\OpenApi\RequestBodies\UserUpdateRequestBody;
    use App\OpenApi\Responses\UserUpdateOkResponse;
    use Illuminate\Http\Request;
    use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;
    
    #[OpenApi\PathItem]
    class UserController extends Controller
    {
        /**
         * indexを返す関数
         *
         * @return void
         */
        #[OpenApi\Operation]
        public function index()
        {
        
        }
    
        /**
         * ユーザー情報更新
         *
         * @return void
         */
        #[OpenApi\Operation]
        #[OpenApi\RequestBody(UserUpdateRequestBody::class)]
        #[OpenApi\Response(UserUpdateOkResponse::class)]
        public function update()
        {
        
        }
    }
    ```

1. 下記コマンドを実行してOpenAPIを出力する。

    ```
    $ php artisan openapi:generate default > storage/openapi.json
    ```

1. 下記のようにJSONが出力される。レスポンスはschemasルートオブジェクト内に定義されてpaths内から参照されるように記載されている。

    ```json
    {
        "openapi": "3.0.2",
        "info": {
            "title": "Laravel",
            "version": "1.0.0"
        },
        "servers": [
            {
                "url": "http:\/\/localhost"
            }
        ],
        "paths": {
            "\/api\/index": {
                "get": {
                    "summary": "indexを返す関数"
                }
            },
            "\/api\/update": {
                "post": {
                    "summary": "ユーザー情報更新",
                    "requestBody": {
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/UserUpdateRequestBody"
                                }
                            }
                        }
                    },
                    "responses": {
                        "200": {
                            "description": "成功レスポンス",
                            "content": {
                                "application\/json": {
                                    "schema": {
                                        "$ref": "#\/components\/schemas\/UserUpdateOkResponse"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "components": {
            "schemas": {
                "UserUpdateBadRequestResponse": {
                    "type": "object",
                    "properties": {
                        "message": {
                            "description": "エラー内容",
                            "type": "string",
                            "example": "送信された値に誤りがあります。"
                        }
                    }
                },
                "UserUpdateOkResponse": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        },
                        "created_at": {
                            "description": "初回登録日時",
                            "type": "string",
                            "example": "YYYY-MM-DD hh:ii:ss"
                        }
                    }
                },
                "UserUpdateRequestBody": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        }
                    }
                }
            }
        }
    }
    ```

1. ここまでは200 OKのレスポンス定義である。400 BadRequestなどを返却する場合もある。下記を実行して400 BadRequest用のレスポンスを定義してみる。
1. 下記コマンドを実行してUserUpdateBadRequestResponseSchema.phpを作成する。

    ```terminal
    $ php artisan openapi:make-schema UserUpdateBadRequestResponseSchema
    ```

1. `アプリ名ディレクトリ/app/OpenApi/Schemas`に`UserUpdateBadRequestResponseSchema.php`が作成される。
1. 下記のようなスキーマを定義したいとする。

    ```json
    {
      "message": "エラー内容"
    }
    ```

1. 下記のように記載することで上記のスキーマを表現する事ができる。（Reusableクラスのuseとimplementsも忘れずに）

    ```アプリ名ディレクトリ/app/OpenApi/Schemas/UserUpdateBadRequestResponseSchema.php
    <?php
    
    namespace App\OpenApi\Schemas;
    
    use GoldSpecDigital\ObjectOrientedOAS\Contracts\SchemaContract;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AllOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AnyOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Not;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\OneOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
    use Vyuldashev\LaravelOpenApi\Contracts\Reusable;
    use Vyuldashev\LaravelOpenApi\Factories\SchemaFactory;
    
    class UserUpdateBadRequestResponseSchema extends SchemaFactory implements Reusable
    {
        /**
         * @return AllOf|OneOf|AnyOf|Not|Schema
         */
        public function build(): SchemaContract
        {
            return Schema::object('UserUpdateBadRequestResponse')
                ->properties(
                    Schema::string('message')
                        ->description('エラー内容')
                        ->example('送信された値に誤りがあります。'),
                );
        }
    }
    ```

1. Bad Request用レスポンスクラスを用いて定義し今直前で定義したスキーマクラスを呼び出す。
1. 下記コマンドを実行してUserUpdateBadRequestResponse.phpを作成する。

    ```terminal
    $ php artisan openapi:make-response UserUpdateBadRequestResponse
    ```

1. `アプリ名ディレクトリ/app/OpenApi/Response`に`UserUpdateBadRequestResponse.php`が作成される。
1. 下記のように記載して先に定義したUserUpdateBadRequestResponseSchemaクラスを呼び出す。ちなみに200 OKなのか　400 BadRequestなのかを分けているのは`Response::badRequest()`の部分である。`::ok()`なら200 OKで`::badRequest()`なら400である。その他のHTTPステータスを指定したいときは`アプリ名ディレクトリ/vendor/goldspecdigital/oooas/src/Objects/Response.php`クラスを見ていただければ良い。使いたいHTTPステータスがない場合、前述のクラスをオーバーライドして呼び出せば良い。（MediaTypeとUserUpdateBadRequestResponseSchemaのuseも忘れずに）

    ```アプリ名ディレクトリ/app/OpenApi/Response/UserUpdateBadRequestResponse.php
    <?php
    
    namespace App\OpenApi\Responses;
    
    use App\Models\User;
    use App\OpenApi\Schemas\UserUpdateBadRequestResponseSchema;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Response;
    use Vyuldashev\LaravelOpenApi\Factories\ResponseFactory;
    
    class UserUpdateBadRequestResponse extends ResponseFactory
    {
        public function build(): Response
        {
            return Response::badRequest()
                ->description('400 Bad Requestレスポンス')
                ->content(
                    MediaType::json()
                        ->schema(UserUpdateBadRequestResponseSchema::ref())
                );
        }
    }
    ```

11. 下記のようにUserController.phpを記載してUserUpdateBadRequestResponseクラスを呼び出す。（UserUpdateBadRequestResponseのuseを忘れずに）

    ```アプリ名ディレクトリ/app/Http/Controllers/UserController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\OpenApi\RequestBodies\UserUpdateRequestBody;
    use App\OpenApi\Responses\UserUpdateBadRequestResponse;
    use App\OpenApi\Responses\UserUpdateOkResponse;
    use Illuminate\Http\Request;
    use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;
    
    #[OpenApi\PathItem]
    class UserController extends Controller
    {
        /**
         * indexを返す関数
         *
         * @return void
         */
        #[OpenApi\Operation]
        public function index()
        {
        
        }
    
        /**
         * ユーザー情報更新
         *
         * @return void
         */
        #[OpenApi\Operation]
        #[OpenApi\RequestBody(UserUpdateRequestBody::class)]
        #[OpenApi\Response(UserUpdateOkResponse::class)]
        #[OpenApi\Response(UserUpdateBadRequestResponse::class)]
        public function update()
        {
        
        }
    }
    ```

!. 下記コマンドを実行してOpenAPIを出力する。

    ```
    $ php artisan openapi:generate default > storage/openapi.json
    ```

1. 下記のようにJSONが出力される。responsesに400のレスポンス情報が追加された。

    ```json
    {
        "openapi": "3.0.2",
        "info": {
            "title": "Laravel",
            "version": "1.0.0"
        },
        "servers": [
            {
                "url": "http:\/\/localhost"
            }
        ],
        "paths": {
            "\/api\/index": {
                "get": {
                    "summary": "indexを返す関数"
                }
            },
            "\/api\/update": {
                "post": {
                    "summary": "ユーザー情報更新",
                    "requestBody": {
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/UserUpdateRequestBody"
                                }
                            }
                        }
                    },
                    "responses": {
                        "200": {
                            "description": "成功レスポンス",
                            "content": {
                                "application\/json": {
                                    "schema": {
                                        "$ref": "#\/components\/schemas\/UserUpdateOkResponse"
                                    }
                                }
                            }
                        },
                        "400": {
                            "description": "400 Bad Requestレスポンス",
                            "content": {
                                "application\/json": {
                                    "schema": {
                                        "$ref": "#\/components\/schemas\/UserUpdateBadRequestResponse"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "components": {
            "schemas": {
                "UserUpdateBadRequestResponse": {
                    "type": "object",
                    "properties": {
                        "message": {
                            "description": "エラー内容",
                            "type": "string",
                            "example": "送信された値に誤りがあります。"
                        }
                    }
                },
                "UserUpdateOkResponse": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        },
                        "created_at": {
                            "description": "初回登録日時",
                            "type": "string",
                            "example": "YYYY-MM-DD hh:ii:ss"
                        }
                    }
                },
                "UserUpdateRequestBody": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        }
                    }
                }
            }
        }
    }
    ```

# 参考文献

- [https://vyuldashev.github.io/laravel-openapi/](https://vyuldashev.github.io/laravel-openapi/)