Giter Club home page Giter Club logo

nerdznetworking's Introduction

NerdzNetworking

NerdzNetworking is a wrapper on top of URLSession and URLRequest to simplify creating and managing network requests written on Swift language.


Example

You need to define request.

class LoginWithFacebookRequest: Request {
    typealias ResponseObjectType = User
    typealias ErrorType = AuthError
    
    let path = "login/facebook"
    let methong = .post
    let body: RequestBody?
    
    init(token: String) {   
        body = .params(
            [
                "token": token
            ]
        )
    }
}

And then just use it. (each call is optional and can be skipped if needed)

LoginWithFacebookRequest(token: fbToken)
    .execute()
    
    .onSuccess { user in
        ...
    }
    
    .onFail { error in
        ...
    }
    
    .onStart { operation in
        ...
    }
    
    .onProgress { progress in
        ...
    }
    
    .onDebug { info in
        ...
    }
}

Ideology

Structure

The main ideology for creating NerdzNetworking library was to maximaly split networking into small pieces. The ideal scenario would be to have a separate class/structure per each request. This should help easily navigate and search information for specific request in files structure.

Requests example


Strong typization

Another flow that library truing to follow - predefined options for using and simplicity of using. We are trying to use generic types on top of protocols, as well as enumerations instead of raw values. As an example - predefined headers like Content-Type or Accept. Instead of giving a possibility to put any value, we have defined and enumerations that limits possbie input only to predefined scenarios like .application(.json). To make it simple, previously mentioned headers already have .application(.json) value preselected for you, so in case you are using standard REST API - everything ready from the box.


Tutorial

Endpoint setup

First of all you need to setup your endpoint that will be used later on for executing requests. To do that - you should be using Endpoint class. Endpoint class will collect all general settings for performing requests. You can change any parameter at any time you want.

let endpoint = Endpoint(baseUrl: myBaseUrl)
endpoint.headers = defaultHeaders // Specifying some default headers like OS, device language, device model, etc.
endpoint.headers.authToken = .bearer(tokenString) // Specifying user token

After creating your endpoint, you can mark it as a default, so every request will pick it up automatically.

Endpoint.default = endpoint

You can change default endpoint based on configuration or environment you need.


Request creation

To create a request you should implement Request protocol. You can or have separate class per each request or an enum.


Separate class for each request

class MyRequest: Request {
    typealias ResponseObjectType = MyExpectedResponse
    typealias ErrorType = MyUnexpectedError
    
    let path = "my/path/to/backend" // Required
    let methong = .get // Optional
    let queryParams = [("key", "value")] // Optional
    let body = .params(["key", "value"]) // Optional
    let headers = [RequestHeaderKey("key"): "value", .contentType: "application/json"] // Optional
    let timeout = 60 // Optional, by defauld will be picked from Endpoint
    let endpoint = myEndpoint // Optional, by default will be a Endpoint.default
}

This is just an example and probably you will not have all parameters required and static. To have dynamicaly created request - you can just use initializers that willl be taking dynamic parameters required for request. As an example - some dynamic bodyParams or dynamic path.


Default request

You can use buit in class DefaultRequest to perform requests without a need to create a separate class.

let myRequest = DefaultRequest<MyExpectedResponse, MyUnexpectedError>(
    path: "my/path/to/backend", 
    method: .get, 
    queryParams: [("key", "value")], 
    body: .params(["key", "value"]), 
    headers: [RequestHeaderKey("key"): "value", .contentType: "application/json"], 
    timeout: 60,
    responseConverter: myResponseConverter,
    errorConverter: myErrorConverter,
    endpoint: myEndpoint)

Multipart request

NerdzNetworking library also provide an easy way of creation and execution of multipart form-data requests. You just need to implement MultipartFormDataRequest instead of Request protocol or use DefaultMultipartFormDataRequest class.

In addition to Request fields you will need to provide files field of MultipartFile protocol instances. You can implement this protocol or use DefaultMultipartFile class.

class MyMultipartRequest: MultipartFormDataRequest {
    // Same fields as in Request example
    
    let files: [MultipartFile] = [
        DefaultMultipartFile(resource: fileData, mime: .image(.png), fileName: "avatar1"),
        DefaultMultipartFile(resource: fileUrl, mime: .audio(.mp4), fileName: "song"),
        DefaultMultipartFile(resource: filePath, mime: .image(.jpeg), fileName: "avatar2")
    ]
}

Request execution

To exucute request you can use next constructions:

  • myRequest.execute(): will execute myRequest on Endpoint.default
  • myRequest.execute(on: myEndpoint): will execute myRequest on myEndpoint
  • myEndpoint.execute(myRequest): will execute myRequest on myEndpoint

Handling execution process

To handle execution process you can use futures-style methods after execute method called. (every method is optional, so use only those you really need)

myRequest
    .execute()
    .responseOn(.main) // Response will be returned in `.main` queue
    .retryOnFail(false) // If this request will fail - system will not try to rerun it again
    
    .onStart { requestOperation in
        // Will be called when request will start and return request operation that allow to control request during the execution
    }
    
    .onProgress { progress in
        // Will provide a progress of request execution. Useful for multipart uploading requests
    }
    
    .onSuccess { response in
        // Will return a response object specified in request under `ResponseType`
    }
    
    .onFail { error in
        // Will return `ErrorResponse` that might contain `ErrorType` specified in request
    }
    
    .onDebug { info in
        // Will return `DebugInfo` that contain a list of useful information for debugging request failure 
    }

Mapping

For now NerdzNetworking library supports only native Codable mapping. Tutorial.


Response converters

NerdzNetworking supports response converters that might convert response before mapping process. Might be useful if case you need to adopt response data to internal model, or to bypass parent object to map only chileds.



ResponseJsonConverter

You can also provide a response converters to convert some unpropertly returned responses beаore mapping into expected response starts. The responsible protocol for this is ResponseJsonConverter.

Response converter should be specified in Request class under responseConverter(success) or/and errorConverter(fail) fields.

You can have your own converters that implement ResponseJsonConverter protocol, or use built in implementations: KeyPathResponseConverter, ClosureResponseConverter.



KeyPathResponseConverter

KeyPathResponseConverter allow you to pull a data from JSON chileds node by specific path to the node.

class MyRequest: Request {
    var responseConverter: ResponseJsonConverter {
        KeyPathResponseConverter(path: "path/to/node")
    }
    
    var errorConverter: ResponseJsonConverter {
        KeyPathResponseConverter(path: "path/to/error")
    }
}


ClosureResponseConverter

ClosureResponseConverter allow you to provide custom convertation by closure. You will need to provide a closure that takes Any and return Any after convertation.

class MyRequest: Request {
    var responseConverter: ResponseJsonConverter {
        ClosureResponseConverter { response in
            // Return converted response for success response
        }
    }
    
    var errorConverter: ResponseJsonConverter {
        ClosureResponseConverter { response in
            // Return converted response for error response
        }
    }
}


Custom ResponseJsonConverter

You can implement your ovn converter by implementing ResponseJsonConverter protocol.

class MyResponseConverter: ResponseJsonConverter {
    func convertedJson(from json: Any) throws -> Any {
        // Provide convertation and return converted code
    }
}

Installation

CocoaPods

You can use CocoaPods dependency manager to install NerdzNetworking. In your Podfile spicify:

pod 'NerdzNetworking', '~> 1.0.1'

Swift Package Manager

To add NerdzNetworking to a Swift Package Manager based project, add:

.package(url: "https://github.com/nerdzlab/NerdzNetworking")

Docummentation


@ Endpoint class

Class that represents and endpoint with all settings for requests execution. You need to create at least one instance to be able to execute requests.


Properties

Name Type Accessibility Description
Endpoint.default Endpoint static read-write An instance of endpoint that will be used for executing request
baseUrl URL readonly An endpoint base url
decoder JSONDecoder? A decoder that will be used by default for decoding json responses if provided
responseQueue DispatchQueue? A queue that will be used by default for dispatching requests completions
retryingCount Int A number of retryings that should happen before aborting an error on request exeqution
observation ObservationManager A manager where you should register observers for observing different types of requests/responses
requestRetrying RequestRetryingManager a manager where you should register retriers
sessionConfiguration URLSessionConfiguration readonly A configuration that is used for inner URLSession
headers [RequestHeaderKey: String] read-write A headers that will be used with every request

Methods

init(
    baseUrl             : URL,
    decoder             : JSONDecoder? = nil,
    responseQueue       : DispatchQueue? = nil,
    sessionConfiguration: URLSessionConfiguration = .default,
    retryingCount       : Int = 1,
    headers             : [RequestHeaderKey: String] = [:]
)

Initializing with all parameters

Name Type Default value Description
baseUrl URL An endpoint base url
decoder JSONDecoder? nil A JSON response default decoder
responseQueue DispatchQueue nil A default response queue for completions
sessionConfiguration URLSessionConfiguration .default A configuration that will be used for inner URLSession
retryingCount Int 1 A number of retryings on request failing
headers [RequestHeaderKey: String] [:] A headers that will be used with every request


func execute<T: Request>(_ request: T) -> ResponseInfoBuilder<T>

Executing request on current endpoint

Name Type Default value Description
request Request Request to be executed


func cURL<T: Request>(for request: T) throws -> String

Return cURL string representation for provided request

Useful for comparing requests from apps like Postman or Proxyman and code, or for fast execution request from terminal.



func useAsDefault()

Sets current instance as a default for future execution


@ ExecutionOperation<T> class

GENERICS: T: Request

A class that represents request operation execution parameters like queue and completions like onSuccess.


Properties

Name Type Accessibility Description
request T readonly A request that is used in operation

Methods

func response(on queue: DispatchQueue) -> Self

Setting up a queue on what all completions will be called. By default main queue will be used

Name Type Default value Description
queue DispatchQueue Queue on what completions will be called


func decode(with decoder: JSONDecoder) -> Self

Setting up a decoder that will be used for decoding JSON response or error

Name Type Default value Description
decoder JSONDecoder A decoder for response JSON decoding


func retryOnFail(_ retryingCount: Int) -> Self

Setting up retrying count for request failings. By default it is equal to 1

Name Type Default value Description
retryingCount Int A retrying count number


func onSuccess(_ closure: @escaping ResponseSuccessCallback) -> Self

ResponseSuccessCallback = (T.ResponseObjectType) -> Void

Registering closure that will be called if request was successful

You can call this method several times, and every registered closure will be triggered

Name Type Default value Description
closure (T.ResponseObjectType) -> Void Triggered closure


func onFail(_ closure: @escaping FailCallback) -> Self

FailCallback = (ErrorResponse<T.ErrorType>) -> Void

Registering closure that will be called if request fails

You can call this method several times, and every registered closure will be triggered

Name Type Default value Description
closure (ErrorResponse<T.ErrorType>) -> Void Triggered closure


func onProgress(_ closure: @escaping ProgressCallback) -> Self

ProgressCallback = (Double) -> Void

Registering closure that will return request processing progress

You can call this method several times, and every registered closure will be triggered

Name Type Default value Description
closure (Double) -> Void Triggered closure


func onDebug(_ closure: @escaping DebugCallback) -> Self

DebugCallback = (DebugInfo) -> Void

Registering closure that will return request & response information for debugging

You can call this method several times, and every registered closure will be triggered

Name Type Default value Description
closure (DebugInfo) -> Void Triggered closure


func onStart(_ closure: @escaping StartCallback) -> Self

StartCallback = () -> Void

Registering closure that will be triggered when request starts processing

You can call this method several times, and every registered closure will be triggered

Name Type Default value Description
closure () -> Void Triggered closure


func calncel()

Cancel request processing


@ Request protocol

TYPE: protocol

Protocol that represents a single request. You can imlement this protocol and then execute it. You can use DefaultRequest struct, that already implements this protocol, for executing requests.


associatedtype

Name Type Accessibility Description
ResponseObjectType ResponseObject A type of expected response from server. It should implement ResponseObject protocol
ErrorType ServerError A type of expected error from server. It should implement ServerError protocol

Properties

Name Type Accessibility Description
path String get required A request path
method HTTPMethod get required A request method
queryParams [(String, String)] get optional A request query parameters represented as an array of touples to save order
body RequestBody? get optional A request body
headers [RequestHeaderKey: String] get optional A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval get optional A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? get optional A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? get optional An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? get optional An endpoint that will be used for execution
decoder JSONDecoder? get optional A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used

Methods

func execute(on endpoint: Endpoint) -> ResponseInfoBuilder<Self>

Executing current request on provided endpoint

Name Type Default value Description
endpoint Endpoint Endpoint on what current request will be executed.


func execute() -> ResponseInfoBuilder<Self>

Executing current request on Endpoint.default instance


@ DefaultRequest struct

TYPE: struct

IMPLEMENT: Request

A default implementation of Request protocol that can be used for executing requests without creation of extra class


Generics

Name Types Description
Response ResponseObject A type of a successful response object
Error ServerError A type of an error response object

Properties

Name Type Accessibility Description
path String read-write A request path
method HTTPMethod read-write A request method
queryParams [(String, String)] read-write A request query parameters represented as an array of touples to save order
body RequestBody? read-write A request body
headers [RequestHeaderKey: String] read-write A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval read-write A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? read-write A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? read-write An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? read-write An endpoint that will be used for execution
decoder JSONDecoder? read-write A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used

Methods

init(
    path                : String, 
    method              : HTTPMethod, 
    queryParams         : [(String, String)] = [], 
    body                : RequestBody? = nil, 
    headers             : [RequestHeaderKey: String] = [:], 
    timeout             : TimeInterval? = nil,
    responseConverter   : ResponseJsonConverter? = nil,
    errorConverter      : ResponseJsonConverter? = nil,
    endpoint            : Endpoint? = nil,
    decoder             : JSONDecoder? = nil
)

Initialize DefaultRequest object with all possible parameters

Name Type Default value Description
path String - A request path
method HTTPMethod - A request method
queryParams [(String, String)] [] A request query parameters represented as an array of touples to save order
bodyParams [String: Any] [:] A request body params
headers [RequestHeaderKey: String] [:] A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval nil A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? nil A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? nil An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? nil An endpoint that will be used for execution
decoder JSONDecoder? nil A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used

@ MultipartFormDataRequest protocol

TYPE: protocol

INHERITS: Request protocol

Protocol that represents a multipart form-data request. Protocol inherits Request protocol, and adding files property on top. So mostly it is the same as Request protocol. You can use DefaultMultipartFormDataRequest struct, that already implements this protocol, for executing multipart requests.


associatedtype

Name Type Accessibility Description
ResponseObjectType ResponseObject A type of expected response from server. It should implement ResponseObject protocol
ErrorType ServerError A type of expected error from server. It should implement ServerError protocol

Properties

Name Type Accessibility Description
path String get required A request path
method HTTPMethod get required A request method
queryParams [(String, String)] get optional A request query parameters represented as an array of touples to save order
body RequestBody? get optional A request body
headers [RequestHeaderKey: String] get optional A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval get optional A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? get optional A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? get optional An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? get optional An endpoint that will be used for execution
decoder JSONDecoder? get optional A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used
files [MultipartFile] get required A list of files that needs to be processed with request

Methods

func execute(on endpoint: Endpoint) -> ResponseInfoBuilder<Self>

Executing current request on provided endpoint

Name Type Default value Description
endpoint Endpoint Endpoint on what current request will be executed.


func execute() -> ResponseInfoBuilder<Self>

Executing current request on Endpoint.default instance


@ DefaultMultipartFormDataRequest struct

TYPE: struct

IMPLEMENT: MultipartFormDataRequest

A default implementation of MultipartFormDataRequest protocol that can be used for executing multipart requests without creation of extra class


Generics

Name Types Description
Response ResponseObject A type of a successful response object
Error ServerError A type of an error response object

Properties

Name Type Accessibility Description
path String read-write A request path
method HTTPMethod read-write A request method
queryParams [(String, String)] read-write A request query parameters represented as an array of touples to save order
body RequestBody? read-write A request body
headers [RequestHeaderKey: String] read-write A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval read-write A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? read-write A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? read-write An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? read-write An endpoint that will be used for execution
decoder JSONDecoder? read-write A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used
files [MultipartFile] read-write A list of files that needs to be processed in request

Methods

init(
    path                : String, 
    method              : HTTPMethod, 
    queryParams         : [(String, String)] = [], 
    body                : RequestBody? = nil, 
    headers             : [RequestHeaderKey: String] = [:], 
    timeout             : TimeInterval? = nil,
    responseConverter   : ResponseJsonConverter? = nil,
    errorConverter      : ResponseJsonConverter? = nil,
    endpoint            : Endpoint? = nil,
    decoder             : JSONDecoder? = nil,
    files               : [MultipartFile] = []
)

Initialize DefaultMultipartFormDataRequest object with all possible parameters

Name Type Default value Description
path String - A request path
method HTTPMethod - A request method
queryParams [(String, String)] [] A request query parameters represented as an array of touples to save order
bodyParams [String: Any] [:] A request body params
headers [RequestHeaderKey: String] [:] A request specific headers. Will be used is addition to headers from Endpoint
timeout TimeInterval nil A request timeout. If not specified - will be used default from Endpoint
responseConverter ResponseJsonConverter? nil A successful response converter. Will be converted before mapping into a ResponseObjectType
errorConverter ResponseJsonConverter? nil An error response converter. Will be converted before mapping into a ErrorType
endpoint Endpoint? nil An endpoint that will be used for execution
decoder JSONDecoder? nil A JSON response decoder that will be used for decoding response. In case not provided - decoder from Endpoint will be used
files [MultipartFile] [] A list of files that needs to be processed in request

@ DefaultMultipartFile struct

A default implementation of MultipartFile protocol that you can use in case you do not want to create additional class for sending multipart request


Properties

Name Type Accessibility Description
fileName String read-write A file name
mime MimeType read-write a file mime type
resource MultipartResourceConvertable read-write A representation of file data. Might be String, Data, URL, InputStream

Methods

init(
    resource: MultipartResourceConvertable, 
    mime    : MimeType, 
    fileName: String
)

Initialize DefaultMultipartFile object with all possible parameters

Name Type Default value Description
resource MultipartResourceConvertable A file name
mime MimeType a file mime type
fileName String A representation of file data. Might be String, Data, URL, InputStream

@ ServerError protocol

A protocol that represents an error returned from server


Properties

Name Type Accessibility Description
message String get required A message of an error

Supported types

  • String
  • Optional

@ HTTPMethod enum

TYPE: enum

INHERITS: String

An enum that represents a request http method.

Name Description
.get A GET http method
.post A POST http method
.put A PUT http method
.delete A DELETE http method
.path A PATH http method

@ RequestBody enum

TYPE: enum

An enum that represents different types of request body

Name Parameters Description
.raw value: Data A raw data
.string value: String A string body
.params value: [String: Any] A body formed with parameters

@ DebugInfo struct

TYPE: struct

Represents an information for debugging networking request.

Can be received fromExecutionOperation class.


Properties

Name Type Accessibility Description
sessionConfiguration URLSessionConfiguration readonly Current request URLSession configuration
request URLRequest readonly URLRequest that were built for execution
dataResponse Data? readonly A response in Data format
urlResponse HTTPURLResponse readonly Response returned by system
errorResponse Error? readonly Response error returned by system
requestDuration TimeInterval readonly Request execution duration
cURL String? readonly Request representation in cURL format
stringResponse String? readonly A response in String format
jsonResponse Any? readonly A response in JSON format

API To be continued...


Next steps

  • Finish documentation
  • Improve request retriers logic
  • Improve request observation logic
  • Add Combine support
  • Add support of custom mapping logic

License

This code is distributed under the MIT license. See the LICENSE file for more info.

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.