OpenAPI で API スキーマから Elm コードを生成する

## OpenAPI とは

[WEB+DB PRESS Vol.108](https://gihyo.jp/magazine/wdpress/archive/2019/vol108) で、「スキーマ駆動Web API開発 OpenAPI/GraphQLで仕様からコードもテストも作成」という特集がありました。
OpenAPI の仕様に従って API スキーマ定義を YAML か JSON で書けば、以下をサポートしてくれます。

* スキーマから API サーバ・クライアント両方のコード生成
* スキーマを読み込んで実装が仕様を満たしているかテストできる
* スキーマからスタブサーバ生成

スキーマからコードを生成する OpenAPI Generator は、API クライアントとして Elm コード（0.19）の出力に対応していました。
Elm で作るアプリケーションにおいて、外部との境界周辺でバグが起きやすいため、そこを補強する手段としてもとても魅力的に見えます。
さっそく試してみました。

## 1. スキーマを作る

適当に探した API https://thecatapi.com/ をサンプルに使います。
https://api.thecatapi.com/v1/images/search
ランダムで猫の画像 URL を一つ返します。
<img width="405" alt="Screenshot 2018-12-25 22.44.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/15528/a0bca837-ee12-c457-e9f7-2b641097b82e.png">

スキーマのエディター https://editor.swagger.io/ で以下のように書きます。

```yaml:openapi.yaml
openapi: 3.0.2
info:
  title: TheCatApi
  version: 1.0.0
servers: 
  - url: https://api.thecatapi.com
    description: server
paths:
  /v1/images/search:
    get:
      description: Get random
      operationId: getRandom
      responses:
        '200':
          description: Get random
          $ref: '#/components/schemas/Images'
components:
  schemas:
    Images:
      type: array
      items:
        $ref: '#/components/schemas/Image'
    Image:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
```

エディター上で定義にエラーが無いことを確認し、[Save as YAML] で YAML ファイルをダウンロードできます。以降ダウンロードしたファイルを `openapi.yaml` とします。

## 2. Elm コードを生成する

[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator) を使います。
特集では Docker を使用していましたが、Mac なら Homebrew でインストールすることもできます。

```bash
$ brew install openapi-generator
$ openapi-generator generate -i openapi.yaml -g elm -o elm-open-api-test
```

これで以下のように Elm コードが出力されます。

<img width="201" alt="Screenshot 2018-12-25 21.54.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/15528/a17afc67-9ad6-a034-8a9f-8894cdea2e00.png">

内容としては、モデルと HTTP リクエストのコードがメインです。Main.elm では特に何もしていませんでした。以下が生成されたコードです。

### モデル

```elm:src/Data/Image.elm
module Data.Image exposing (Image, decoder, encoder)

import Dict exposing (Dict)
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (optional, required)
import Json.Encode as Encode


type alias Image =
    { id : Maybe (String)
    , url : Maybe (String)
    }


decoder : Decoder Image
decoder =
    Decode.succeed Image
        |> optional "id" (Decode.nullable Decode.string) Nothing
        |> optional "url" (Decode.nullable Decode.string) Nothing



encoder : Image -> Encode.Value
encoder model =
    Encode.object
        [ ( "id", Maybe.withDefault Encode.null (Maybe.map Encode.string model.id) )
        , ( "url", Maybe.withDefault Encode.null (Maybe.map Encode.string model.url) )

        ]
```

### HTTP リクエスト

```elm:src/Request/Default.elm
module Request.Default exposing (getRandom)

import Dict
import Http
import Json.Decode as Decode


basePath : String
basePath =
    "https://api.thecatapi.com"


{-| Get random
-}
getRandom : Http.Request ()
getRandom =
    { method = "GET"
    , url = basePath ++ "/v1/images/search"
    , headers = []
    , body = Http.emptyBody
    , expect = Http.expectStringResponse (\_ -> Ok ())
    , timeout = Just 30000
    , withCredentials = False
    }
        |> Http.request
```

スキーマで `operationId` として定義した名前がリクエスト関数名になるようです。

## まとめ

コードを残しておきます。生成されたコードにちょっと書き足して、画像を画面に表示するようにしてあります。
https://github.com/kawausokun/elm-open-api-test

OpenAPI を使った開発の実践を WEB+DB PRESS に詳しく書いてあるので、興味がある方は読んでみてはどうでしょうか。