LoginSignup
2
2

More than 1 year has passed since last update.

APIKitとPromiseKitでDeepL APIをMix-in(Trait)

Last updated at Posted at 2022-03-06

はじめに

賢い翻訳AIのDeepLを使うにあたり、タイプセーフな通信ライブラリAPIKitの存在を知ったので、PromiseKitと一緒に使ってみました。DeepLのAuth Keyをクライアントに持たせるのは危ういので実際にはこの方法は使わないですが、Alamofireから乗り換えようかなと思ってます。

  • DeepL API
    50万文字/月までの翻訳は無料で使えるステキなry。
  • APIKit
    タイプセーフなAPIプログラミングを可能としてくれるステキなry。
  • PromiseKit
    非同期処理を書くときにクロージャーの入れ子から開放してくれるステキなry。

Decodable準拠

DeepL APIのレスポンスは以下のようなJSON形式なので、JSONDecoderでdecodeできるように定義します。

DeepLのレスポンス
{
	"translations": [{
		"detected_source_language":"EN",
		"text":"Hallo, Welt!"
	}]
}
Decodable準拠
struct DeepLResponse: Decodable {
    let translations: [Translation]
}

struct Translation: Decodable {
    let text: String
    let detectedSourceLanguage: String
}

DataParser準拠

DataParser準拠のParserを定義します。contentTypeは"application/json"、parseメソッドでJSONDecoderを使用します。DeepLResponseタイプはcamel caseで、DeepLのレスポンスはsnake caseなので、keyDecodingStrategyにconvertFromSnakeCaseを指定します。

final class JSONDecodableDataParser<T:Decodable>:DataParser {
    var contentType:String? {
        return "application/json"
    }

    func parse(data:Data) throws -> Any {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
        return try jsonDecoder.decode(T.self, from:data)
    }
}

Request準拠

DeepL APIの呼び出しに合わせてリクエストプロトコルを定義します。
Content-Typeは、application/x-www-form-urlencodedです。

Bodyの例
auth_key=your_auth_key&text=Hello, world&target_lang=DE

GETメソッドも対応していますが、安全のためPOSTメソッドを使用します。
POSTメソッドの場合は、bodyParametersでFormURLEncodedBodyParametersを使えば良いようです。

DeepLRequestの実装
struct DeepLRequest: Request {
    typealias Response = DeepLResponse
    
    let authKey:String
    let text:String
    let targetLang:String
    let sourceLang:String?
    
    var baseURL:URL {
        URL(string:"https://api-free.deepl.com")!
    }
    var method:HTTPMethod {
        .post
    }
    var path:String {
        "/v2/translate"
    }
    var bodyParameters:BodyParameters? {
        var params = ["auth_key":authKey,
                      "text":text,
                      "target_lang":targetLang]
        if let sourceLang = sourceLang {
            params["source_lang"] = sourceLang
        }
        return FormURLEncodedBodyParameters(formObject:params)
    }

    var dataParser:DataParser {
        JSONDecodableDataParser<Response>()
    }

    func response(from object:Any, urlResponse:HTTPURLResponse) throws -> Response {
        guard let object = object as? Response else {
            throw ResponseError.unexpectedObject(object)
        }
        return object
    }
}

Mix-in

PromiseKitを使ってDeepLApiCallableプロトコルを定義します。

protocol DeepLApiCallable {
    func translate(text:String, targetLang:String, sourceLang:String?) -> Promise<String>
}

extension DeepLApiCallable {
    func translate(text:String, targetLang:String, sourceLang:String?=nil) -> Promise<String> {
        return Promise<String> { resolver in
            let request = DeepLRequest(authKey:your_auth_key, text:text, targetLang:targetLang, sourceLang:sourceLang)
            Session.send(request) { result in
                switch result {
                case .success(let deepLResponse):
                    resolver.fulfill(deepLResponse.translations[0].text)
                case .failure(let error):
                    resolver.reject(error)
                }
            }
        }
    }
}

使用例

struct SomeClass:DeepLApiCallable {...}
let someClass = SomeClass()

firstly {
    someClass.translate(text:"こんにちは", targetLang:"EN")
}.done { text in
    // 翻訳結果を利用
}.catch { error in
    // エラー処理
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2