AlamofireのURLRequestConvertibleをAPIKitのRequest型風に使いたい


# 最初に結論

Alamofireの`URLRequestConvertible`は公式リファレンスを見るとenum `Router`を使ってリクエスト方法を列挙してるんですな。

```swift
enum Router: URLRequestConvertible {
    case get, post
    
    var baseURL: URL {
        return URL(string: "https://httpbin.org")!
    }
    
    var method: HTTPMethod {
        switch self {
        case .get: return .get
        case .post: return .post
        }
    }
    
    var path: String {
        switch self {
        case .get: return "get"
        case .post: return "post"
        }
    }
    
    func asURLRequest() throws -> URLRequest {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.method = method
        
        return request
    }
}

AF.request(Router.get)
```

でも、こういうふうに列挙してもそれを網羅する意味はないと私は思います。この例の`https://httpbin.org/get`と`https://httpbin.org/post`を網羅する意味ないと思うんですよね。それはパターンの漏れを考慮する必要性を感じないからです。

ただ、列挙することで一覧性があるという長所は認めます。しかしWeb APIが増えると見辛くなります。そうなると分割すればいいと思うのは当然なんですが、唯一の長所である一覧性は落ちるので増えたら長所がないんです。

つまり、enumでやる意味がないことをenumでやってしまっているので他の方法でやりゃーいいじゃんというのが主張です。

そのためにAlamofire v5くらいからの`responseDecodable`を使って、APIKitのように型でリクエストを表現したいということを書いておきます。


- URLRequestConvertibleは便利だけどそれをenumに準拠させる必要はない
- responseDecodableメソッドもある

# 前提知識

## Decodable

Alamofire v5くらいからResponseをJSONでデコードした型に変換してくれるメソッドがあります

```swift
// こいつが通信成功後の型
struct HTTPBinResponse: Decodable {
    let url: String 
}

// 通信成功したらHTTPBinResponseに変換する
AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self) { response in
    debugPrint("Response: \(response)")
}
```

これで`responseData`メソッドからData型から変換したり、`responseJSON`メソッドでDictionaryから変換する必要はなくなってきます。もちろんSwiftのDecodableに準拠させてたら変換自体はやらんけども。

https://github.com/Alamofire/Alamofire/blob/5.4.2/Documentation/Usage.md#response-decodable-handler

# 本題

## リクエスト用のprotocolを考える

APIKitのようにリクエスト型をリクエストごとに用意したいので、リクエスト型が準拠したいprotocolを考えます。

```swift
protocol WebAPIRequest: Alamofire.URLRequestConvertible {
    // 認証が不要な場合はnil
    var accessToken: String? { get }

    // 接続先を各Requestごとに変えられていい
    var baseURL: URL { get }

    var path: String { get }

    var method: Alamofire.HTTPMethod { get }

    var parameters: Alamofire.Parameters { get }

    // アプリのバージョンとかをHeaderに入れるための
    func addInformationToURLRequest(_ urlRequest: Foundation.URLRequest) -> Foundation.URLRequest
}
```

よくある利用例として、ヘッダーに自動で情報を入れてもらうための処理はリクエストごとに勝手に組み立てて欲しいので、`addInformationToURLRequest`メソッドをprotocol extensionで自動的にやっておいてもらう


```swift
extension WebAPIRequest {

    func addInformationToURLRequest(_ urlRequest: Foundation.URLRequest) -> Foundation.URLRequest {
        var request = urlRequest

        // これはサーバー側が欲しい情報を組み立てたりする
        let headers = [
           /* 省略 */
        ]

        headers.forEach { key, value in
            request.addValue(value, forHTTPHeaderField: key)
        }

        return request
    }
}
```

## Request型に準拠したリクエストを作る

リクエストを型で表現したいのでやりたいことは次のようなことです

- `https://httpbin.org/get`にアクセスする例
    - baseURLが`https://httpbin.org`
    - pathが`/get`
    - HTTPMethodが`get`
    - accessTokenはnil
    - レスポンスは`HTTPBinResponse`型

それをコードで表現すると次のようになります

```swift
struct HTTPBinResponse: Decodable {
    let url: String
}

struct GetRequest: WebAPIRequest {
    typealias Response = HTTPBinResponse

    let path = "/get"

    let method = HTTPMethod.get

    let baseURL: URL
    // 今回は使わない
    let accessToken: String?
    // 今回は使わない
    let parameters = Parameters()

    func asURLRequest() throws -> URLRequest {
        var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue

        urlRequest = addDeviceInformationToURLRequest(urlRequest)

        return try Alamofire.URLEncoding.queryString.encode(urlRequest, with: parameters)
    }
}
```

利用時は次のような感じです。

```swift
let request = GetRequest(baseURL: baseURL)

AF.request(request)
    .validate()
    .responseDecodable(of: GetRequest.Response.self) {
        debugPrint("Response: \($0)")
    }
```

`responseDecodable`に戻り値の型を自前で指定しているところはリクエストが決まってるなら自動的に決まるはずで、工夫すればもう少し良くなりそうですが、まあそれは込み入ってくるので各自良さそうなやり方をコメントに書いてもらうのが良いでしょう。

## 一覧性はないが実装済みのリクエストを把握するための方法

Alamofire公式のenum Routerのような一覧性がほしいという要望もあるとは思いますが、caseなしのenumを使うことで、実装済みのリクエストを把握するための方法で事足りる気がします。

具体的にはenumの下に置いて実装するようにします。

```swift
enum WebAPI { }
```

```swift
extension WebAPI {
    struct GetRequest: WebAPIRequest { 
        /* 省略 */
    }
}
```

```swift
extension WebAPI {
    struct PostRequest: WebAPIRequest { 
        /* 省略 */
    }
}
```

ちなみに全てのリクエストを1ファイルに書いてしまうとめちゃ長くなるのでファイルを分けたほうが絶対良いでしょうね。

# まとめ

- Alamofireの公式ではenum Routerを作ってるが筆者は意味がない気がしてる
  - caseで列挙するのはいいがcaseで網羅しなきゃいけない必要性を筆者は感じない
- enum caseで網羅するんじゃなく、structで型としてそれぞれのAPI仕様を示せば？
- enum Routerの一覧性のメリット
    - caseなしenumで名前空間みたいにすればいいのでは？