lucasmpaim / EasyRest

MIT License
13 stars 3 forks source link

Build Status

Motivation

After reviewing many REST clients for iOS , we realize that all are very verbose , which is unnecessary. This library was born from the need to simplify the communication between client and server.

Requirements

For Swift 2.2, 2.3 and 3 check the branches.

Documentation

You can read the doc's in this wiki

Usage

To add EasyRest to your project, add the following in your podfile

pod 'EasyRest'
pod 'EasyRest/PromiseKit'

Model Example:

class Post : Codable {

    var id: String?
    var title: String?

}

Route Examples:


// Maps API calls and infos, like method and query params
enum JsonPlaceholderRoutable: Routable {
    // Each API interaction is a case
    case posts
    // Using query params
    case postsFilter(userId: Int)
    // Example with path
    case post(id: Int)
    case deletePost(id: Int)
    // Example with body
    case makePost(body: PostModel)
    // Example with path, body and header
    case editPost(id: Int, body: PostModel)
    case editTitle(id: Int, body: PostModel)

    // Now test every possibility in a fashion Swift syntax
    var rule: Rule {
        switch(self) {
        case .posts:
            return Rule(
                method: .get, // HTTP verb
                path: "/posts/", // Endpoint
                isAuthenticable: false, // Uses EasyRest Authenticated Service?
                parameters: [:]) // query/path/header params? Header Body? Multipart form data? Put here!
        case let .postsFilter(userId):
            return Rule(
                method: .get,
                path: "/posts/",
                isAuthenticable: false,
                parameters: [.query: ["userId": "\(userId)"]])
        case let .post(id):
            return Rule(
                method: .get,
                path: "/posts/{id}/",
                isAuthenticable: false,
                parameters: [.path: ["id": id]])
        case let .deletePost(id):
            return Rule(
                method: .delete,
                path: "/posts/{id}/",
                isAuthenticable: false,
                parameters: [.path: ["id": id]])
        case let .makePost(body):
            return Rule(
                method: .post,
                path: "/posts/",
                isAuthenticable: false,
                parameters: [.body: body])
        case let .editPost(id, body):
            return Rule(
                method: .put,
                path: "/posts/{id}",
                isAuthenticable: false,
                parameters: [
                    .path: ["id": id],
                    .body: body,
                    .header: ["Content-type": "application/json; charset=UTF-8"]])
        case let .editTitle(id, body):
            return Rule(
                method: .patch,
                path: "/posts/{id}",
                isAuthenticable: false,
                parameters: [
                    .path: ["id": id],
                    .body: body,
                    .header: ["Content-type": "application/json; charset=UTF-8"]])
        }
    }
}

enum TestRoute: Routable{

    case me(String)
    case post(String)

    var rule: Rule {
        switch(self) {
            case let .me(name):
                return Rule(method: .get, path: "/api/v1/users/me/", isAuthenticable: true, parameters: [.query : ["name": name]])
            case let .post(id):
                let parameters : [ParametersType: AnyObject] = [:]
                return Rule(method: .get, path: "/api/v1/posts/\(id)/",   isAuthenticable: true, parameters: parameters)
            }
    }

}

Service Examples


class TestRouteService : OAuth2Service<TestRoute> {
    override var base: String { return BASE_URL }
    override var interceptors: [Interceptor] { return [DefaultHeadersInterceptor()] }

    convenience init() {
        self.init()
    }

    func me(name: String, onSuccess: (result: Response<UserTest>?) -> Void, onError: (ErrorType?) -> Void, always: () -> Void) {
        try! call(.me(name), type: UserTest.self, onSuccess: onSuccess, onError: defaultErrorHandler(onError), always: always)
    }

    func post(id: Int, onSuccess: (result: Response<Post>?) -> Void, onError: (ErrorType?) -> Void, always: () -> Void) {
        try! call(.post(id), type: Post.self, onSuccess: onSuccess, onError: defaultErrorHandler(onError), always: always)
    }

    func defaultErrorHandler(onError: (ErrorType?) -> Void) -> (ErrorType?) -> Void {
        return { error in
            /* Do whatever is default for all errors, like
                switch error.cause {
                    case .InternetConnection:
                        // backOff()
                    case .FailedJsonSerialization:
                        Crashlytics.sendMessage(error....)
                        Sentry.captureEvent(...)
                }
            */

            onError(error)
        }
    }
}

We also support PromiseKit calls:

class JsonPlaceholderService : Service<JsonPlaceholderRoutable> {
    override public var base: String { return "https://jsonplaceholder.typicode.com" }
}

And then:

let service = JsonPlaceholderService()
try! service.call(.post(id: 1), type: PostModel.self).promise
    .done { result in
        print("RESPONSE ITEM ID: \(result!.body!.id!)")
    }.catch { error in
        print("Error : \(error.localizedDescription)")
    }.finally {
        print("This code will be called all the time")
    }

Interceptor Example:

class DefaultHeadersInterceptor : Interceptor {
    required init() {}

    func requestInterceptor<T: Codable>(_ api: API<T>) {
        api.headers["Content-Type"] = "application/json"
        api.headers["Accept"] = "application/json"
        api.headers["Device-Token"] = "8ec3bba7de23cda5e8a2726c081be79204faede67529e617b625c984d61cf5c1"
        api.headers["Device-Agent"] = "iOS_SANDBOX"
        api.headers["Accept-Language"] = "pt-br"
    }

    func responseInterceptor<T: Codable, U>(_ api: API<T>, response: DataResponse<U>) {

    }
}

TODO

Third party libraries and references

LICENSE

EasyRest is available under the MIT license. See the LICENSE file for more info.