/Microya

Micro version of the Moya network abstraction layer written in Swift.

Primary LanguageSwiftMIT LicenseMIT

Build Status codebeat badge Version: 0.1.1 Swift: 4.2 Platforms: iOS | macOS | tvOS | watchOS License: MIT

InstallationUsageIssuesContributingLicense

Microya

A micro version of the Moya network abstraction layer written in Swift.

Installation

Installation is supported via CocoaPods, Carthage, SwiftPM and Mint.

Usage

Step 1: Defining your Endpoints

Create an Api enum with all supported endpoints as cases with the request parameters/data specified as parameters.

For example, when writing a client for the Microsoft Translator API:

enum MicrosoftTranslatorApi {
    case languages
    case translate(texts: [String], from: Language, to: [Language])
}

Note that the Language type used above does not necessarily need to be an Encodable type:

enum Language: String {
    case english = "en"
    case german = "de"
    case japanese = "jp"
    case turkish = "tr"
}

Step 2: Making your Api JsonApi compliant

Add an extension for your Api enum that makes it JsonApi compliant, which means you need to add implementations for the following protocol:

protocol JsonApi {
    var decoder: JSONDecoder { get }
    var encoder: JSONEncoder { get }

    var baseUrl: URL { get }
    var headers: [String: String] { get }
    var path: String { get }
    var method: Method { get }
    var queryParameters: [(key: String, value: String)] { get }
    var bodyData: Data? { get }
}

Use switch statements over self to differentiate between the cases (if needed) and to provide the appropriate data the protocol asks for (using Value Bindings).

Toggle me to see an example
extension MicrosoftTranslatorApi: JsonApi {
    var decoder: JSONDecoder {
        return JSONDecoder()
    }

    var encoder: JSONEncoder {
        return JSONEncoder()
    }

    var baseUrl: URL {
        return URL(string: "https://api.cognitive.microsofttranslator.com")!
    }

    var path: String {
        switch self {
        case .languages:
            return "/languages"

        case .translate:
            return "/translate"
        }
    }

    var method: Method {
        switch self {
        case .languages:
		        return .get

        case .translate:
            return .post
        }
    }

    var queryParameters: [(key: String, value: String)] {
        var urlParameters: [(String, String)] = [(key: "api-version", value: "3.0")]

        switch self {
        case .languages:
            break

        case let .translate(_, sourceLanguage, targetLanguages, _):
            urlParameters.append((key: "from", value: sourceLanguage.rawValue))

            for targetLanguage in targetLanguages {
                urlParameters.append((key: "to", value: targetLanguage.rawValue))
            }              
        }

        return urlParameters
    }

    var bodyData: Data? {
        switch self {
        case .translate:
            return nil // no request data needed

        case let .translate(texts, _, _, _):
            return try! encoder.encode(texts)
        }
    }

    var headers: [String: String] {
        switch self {
        case .languages:
		        return [:]

        case .translate:
            return [
                "Ocp-Apim-Subscription-Key": "<SECRET>",
                "Content-Type": "application/json"
            ]
        }
    }
}

Step 3: Calling your API endpoint with the Result type

Call an API endpoint providing a Decodable type of the expected result (if any) by using this method pre-implemented in the JsonApi protocol:

func request<ResultType: Decodable>(type: ResultType.Type) -> Result<ResultType, JsonApiError>

For example:

let endpoint = MicrosoftTranslatorApi.translate(texts: ["Test"], from: .english, to: [.german, .japanese, .turkish])

switch endpoint.request(type: [String: String].self) {
case let .success(translationsByLanguage):
    // use the already decoded `[String: String]` result

case let .failure(error):
    // error handling
}

Note that you can also use the throwing get() function of Swift 5's Result type instead of using a switch statement:

let endpoint = MicrosoftTranslatorApi.translate(texts: ["Test"], from: .english, to: [.german, .japanese, .turkish])
let translationsByLanguage = try endpoint.request(type: [String: String].self)
// use the already decoded `[String: String]` result

There's even useful functional methods defined on the Results type like map(), flatMap() or mapError() and flatMapError(). See the "Transforming Result" section in this article for more information.

Contributing

See the file CONTRIBUTING.md.

License

This library is released under the MIT License. See LICENSE for details.