Code Monkey home page Code Monkey logo

apiclient's Introduction

Build Status codecov Carthage compatible

APIClient

APIClient is a client library for OpenAPI. It makes OpenAPI generated code remarkably more straightforward than the default one.

The generated code by Open API is a strongly tied scheme definition and networking code. It makes debugging and logging difficult. This library separates networking code from OpenAPI generated code, and you can depend on only schema and model definitions.

BEFORE

import Foundation
import Alamofire

open class PetAPI {
    open class func getPetById(petId: Int64, completion: @escaping ((_ data: Pet?,_ error: Error?) -> Void)) {
        getPetByIdWithRequestBuilder(petId: petId).execute { (response, error) -> Void in
            completion(response?.body, error)
        }
    }

    open class func getPetByIdWithRequestBuilder(petId: Int64) -> RequestBuilder<Pet> {
        var path = "/pet/{petId}"
        let petIdPreEscape = "\(petId)"
        let petIdPostEscape = petIdPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
        path = path.replacingOccurrences(of: "{petId}", with: petIdPostEscape, options: .literal, range: nil)
        let URLString = PetstoreAPI.basePath + path
        let parameters: [String:Any]? = nil
        
        let url = URLComponents(string: URLString)

        let requestBuilder: RequestBuilder<Pet>.Type = PetstoreAPI.requestBuilderFactory.getBuilder()

        return requestBuilder.init(method: "GET", URLString: (url?.string ?? URLString), parameters: parameters, isBody: false)
    }
    ...

AFTER

import Foundation

open class PetAPI {
    open class func getPetById(petId: Int64) -> RequestProvider<Pet> {
        var path = "/pet/{petId}"
        let petIdPreEscape = "\(petId)"
        let petIdPostEscape = petIdPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
        path = path.replacingOccurrences(of: "{petId}", with: petIdPostEscape, options: .literal, range: nil)
        
        return RequestProvider<Pet>(endpoint: path, method: "GET")
    }
    ...

RequestProvider<Response> just encodes an endpoint (path), parameters (query, form or JSON), an HTTP method and a response type.

Usage

Add an extension to convert OpenAPI's RequestProvider<Response> to APIClient's Request<Response>.

import Foundation
import APIClient
import Petstore

extension RequestProvider {
    func request() -> Request<Response> {
        if let parameters = parameters {
            switch parameters {
            case .query(let raw):
                return Request(endpoint: endpoint, method: method, parameters: Request.Parameters(raw))
            case .form(let raw):
                return Request(endpoint: endpoint, method: method, parameters: Request.Parameters(raw))
            case .json(let raw):
                return Request(endpoint: endpoint, method: method, parameters: Request.Parameters(raw))
            }
        }
        return Request(endpoint: endpoint, method: method)
    }
}

Initialize Client instance.

let client = Client(baseURL: URL(string: "https://petstore.swagger.io/v2")!)
...

Then you can call Client.perform with Request<Response> object.

client.perform(request: PetAPI.getPetById(petId: 1000).request()) {
    switch $0 {
    case .success(let response):
        let pet = response.body
        ...
    case .failure(let error):
        ...
    }
}

Installation

Carthage

github "folio-sec/APIClient"

Then run carthage update.

Follow the current instructions in Carthage's README for up to date installation instructions.

Advanced

Interceptor

APIClient supports request and response interceptors.

The following example is a logger interceptor.

import Foundation
import APIClient

public struct Logger: Intercepting {
    public init() {}

    public func intercept(client: Client, request: URLRequest) -> URLRequest {
        print("\(requestToCurl(client: client, request: request))")
        return request
    }

    // swiftlint:disable large_tuple
    public func intercept(client: Client, request: URLRequest, response: URLResponse?, data: Data?, error: Error?) -> (URLResponse?, Data?, Error?) {
        if let response = response as? HTTPURLResponse {
            let path = request.url?.path ?? ""
            print("\(request.httpMethod?.uppercased() ?? "") \(path) \(response.statusCode)")
        } else if let error = error {
            print("\(error)")
        }
        return (response, data, error)
    }

    private func requestToCurl(client: Client, request: URLRequest) -> String {
        ...
    }
}
client.interceptors = [Logger()]
...

Authenticator

The Authenticator has an opportunity to retry when it receives a 401 response. It will be used to seamlessly refresh access tokens.

import Foundation
import APIClient

struct Authenticator: Intercepting, Authenticating {
    private let credentials: Credentials

    init(credentials: Credentials) {
        self.credentials = credentials
    }

    func intercept(client: Client, request: URLRequest) -> URLRequest {
        return sign(request: request)
    }

    func authenticate(client: Client, request: URLRequest, response: HTTPURLResponse, data: Data?, completion: @escaping (AuthenticationResult) -> Void) {
        switch response.statusCode {
        case 401:
            if let url = request.url, !url.path.hasSuffix("/login"), let refreshToken = credentials.fetch()?.refreshToken {
                client.perform(request: AuthenticationAPI.authorize(refreshToken: refreshToken).request()) {
                    switch $0 {
                    case .success(let response):
                        let body = response.body
                        self.credentials.update(token: Token(accessToken: body.accessToken, refreshToken: body.refreshToken, expiry: Date().addingTimeInterval(TimeInterval(body.expiresIn))))
                        completion(.success(self.sign(request: request)))
                        return
                    case .failure(let error):
                        switch error {
                        case .networkError, .decodingError:
                            completion(.failure(error))
                            return
                        case .responseError(let code, _, _):
                            switch code {
                            case 400...499:
                                self.credentials.update(token: nil)
                                completion(.failure(error))
                                return
                            case 500...499:
                                completion(.failure(error))
                                return
                            default:
                                break
                            }
                        }
                        completion(.failure(error))
                        return
                    }
                }
            } else {
                completion(.cancel)
                return
            }
        default:
            completion(.cancel)
            return
        }
    }

    private func sign(request: URLRequest) -> URLRequest {
        var request = request
        if let url = request.url, !url.path.hasSuffix("/login") {
            if let accessToken = credentials.fetch()?.accessToken {
                request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
            }
        }
        return request
    }
}
let authenticator = Authenticator(credentials: credentials)
client.authenticator = authenticator
client.interceptors = [authenticator] + client.interceptors // for signing all requests
...

apiclient's People

Contributors

kishikawakatsumi avatar kkishikawa-folio avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.