OpenAPI3：プロパティのoptionalとnullableの書き方

ちょうど、[こないだ書いた](https://qiita.com/tashinoso/items/7231e558c0429b2dab5d)OpenAPI genarator の typescript-fetch について、他所で言及されていたのですが、その内容が軽く引っかかったので書いておきます。

https://zenn.dev/erukiti/articles/frontend-team-building#api%E9%80%A3%E6%90%BA

## レスポンスデータのプロパティがオプショナル(?)になる？

> レスポンスデータのプロパティ全部に ? がついている（オプショナルな）ため、とても扱いづらい。API的には絶対あるはずの項目なのに、いちいちチェックを入れなければならない



> ? を外すの、一番最悪の場合は生成されたコードをさらに加工するという頭の悪い手段はありますが、それは本当の最終手段としたい。

すくなくとも私の手元ではそんなことにはならないので、ひょっとして `type:object` の時、`required`を指定してないんじゃないのかな？ と思うのですが、どうなんでしょうか。

## オプショナルなプロパティ
Specで下記のようなオブジェクトが定義されていた場合、オブジェクトのプロパティはすべてオプショナルになります。

```yaml
components:
  schemas:
    ApiResponse:
      type: object
      description: APIレスポンス
      properties:
        code:
          type: integer
          description: レスポンスコード
          minimum: 0
        type:
          type: string
          description: レスポンスタイプ
        message:
          type: string
          description: メッセージ
```

↓ Codegen して出力されてくるtypescriptの定義。

```ts:api.ts
/**
 * 
 * @export
 * @interface ApiResponse
 */
export interface ApiResponse {
    /**
     * 
     * @type {number}
     * @memberof ApiResponse
     */
    code?: number;
    /**
     * 
     * @type {string}
     * @memberof ApiResponse
     */
    type?: string;
    /**
     * 
     * @type {string}
     * @memberof ApiResponse
     */
    message?: string;
}
```

`code` `type` `message` すべてのプロパティがオプショナルになっており、ひょっとして元記事ではこの事を言っているのかなと思ったのですが、だとすると、これはSpec的に正しい解釈です。

オプショナルではないプロパティは、`required` に記述されている必要があります。
下記のSpecから Codegen されたTSでは、プロパティはオプショナルにはなりません。

```yaml
components:
  schemas:
    ApiResponse:
      type: object
      description: APIレスポンス
      properties:
        code:
          type: integer
          description: レスポンスコード
          minimum: 0
        type:
          type: string
          description: レスポンスタイプ
        message:
          type: string
          description: メッセージ
      required:
          - code
          - type
          - message
```



```ts:api.ts
/**
 * APIレスポンス
 * @export
 * @interface ApiResponse
 */
export interface ApiResponse {
    /**
     * レスポンスコード
     * @type {number}
     * @memberof ApiResponse
     */
    code: number;
    /**
     * レスポンスタイプ
     * @type {string}
     * @memberof ApiResponse
     */
    type: string;
    /**
     * メッセージ
     * @type {string}
     * @memberof ApiResponse
     */
    message: string;
}
```

## nullable なプロパティ
一方で、`nullable:true` なプロパティがTypescript側に反映されないようだ、という現象は確認していますが、どうやらnullableについては根深い問題のようで、プロパティ値に `null` を入れるぐらいなら、プロパティごと null にしておいた方が無難であるようです。

https://qiita.com/sakuraya/items/2d98c352c74ad72e2a9a#%E3%81%95%E3%81%A6%E3%81%A9%E3%81%86%E3%81%99%E3%82%8B%E3%81%8B

## typescript-fetch について
元記事では、他に２点の不満が挙げられており、typescript-fetch の出来が悪いというご意見のようです。

> あまりにも OpenAPI generator のクォリティに難がありすぎるため、時間があれば pure js で書かれた、別のコード生成を試したかったところです。もしくはもういっそ自作した方が確実なのでは？とすら思っています。

別にtypescript-fetch に対して思い入れも義理もないですが、酷い言われようだなと思ったので、ついでに残り２点も擁護しておきます。

### クラスやメソッドが探しにくい？
> API種別ごとにクラスがあって、細かいエンドポイントごとに、メソッドが生えるため、目的のクラスとメソッドを探し出すのが面倒くさい

そんなに探しにくいですかね？
むしろそのための Swagger UI による自動ドキュメント生成だと思っているのですが。

[こないだ書いた通り](https://qiita.com/tashinoso/items/7231e558c0429b2dab5d)、typescript-fetch で出力されてくるフォーマットは タグ名がAPIクラス名となり、オペレーションID名がメソッド名となります。

↓のようなSpecであれば、Typescript側のコードでは `AccountApi.postAccountAuth()` がメソッドとなります。

```yaml
  /account/auth:
    post:
      tags:
        - account
      summary: ログイン
      operationId: postAccountAuth
      responses:
        '200':
          description: successful
        '400':
          description: bad request
        '404':
          description: not found
```



クラス、メソッドを探すのが辛いということは、オペレーションIDを定義するための規約に問題があるんじゃないのかなあと思うのですが、どうなんでしょうね。
個人的には、下記のOpenAPI設計規約がとても勉強・参考になりました。

https://future-architect.github.io/articles/20200409/

もし、 Swagger UI の出力の中に オペレーションID名 が表示されないのが不便である、という意味なら、表示するための[設定](https://stackoverflow.com/questions/54203663/swagger-ui-does-not-display-operationid)があるそうです。


### axiosより使いにくい？

> 挙動が typescript-axios と全く別もの

そりゃまあ別物なんでしょう、という話なんですが。

「挙動」というのが、Codegenの挙動なのか、吐き出されたTSの挙動なのか分かりませんが、ここではTSの話だと仮定して、そんなに差がありますかね？

```ts
accountApi.postAccountAuth().then((res) => {
  // Success

}).catch((error) => {
  // Error

})
```

返ってくるのが Promise なんで、レスポンスを受ける分には、特に差異を感じたことがないんですけど、もっと別の段階の話かもしれませんね。

私も使用歴は浅いので、使い込んでいくと色々と不満は出てくるのだろうと思います。