Skip to content

Latest commit

 

History

History
166 lines (115 loc) · 7.61 KB

Network.md

File metadata and controls

166 lines (115 loc) · 7.61 KB

Network

The network layer, represented by the Network type, serves as a namespace encapsulating the different pieces required to deal with core networking operations.

The core element is the network stack, acting as the main entry point for any networking operations. In its turn, a stack has a configuration that may provide additional functionality to the base implementation, such as a server trust validator, an authenticator or even request interceptors.

Concepts

Network Stack

The network stack, represented by the NetworkStack protocol, is the centerpiece of a network layer.

This protocol enforces a single function that represents a network request, which fetches a resource and then calls a completion block with a result, which is either a successful value (wrapping the remote data type) or a failed one (wrapping a network error).

Configuration

A configuration, represented by the Network.Configuration type, allows one to extend a network stack with additional optional functionality. It can have an authentication challenge handler, an authenticator, and request interceptors.

  • The authentication challenge handler, represented by the AuthenticationChallengeHandler protocol, has a single method for handling challenges from a server requiring authentication from the client.

  • The network authenticator, represented by the NetworkAuthenticator protocol, authenticates requests and validates the corresponding responses as well.

  • At last, the request interceptors, as the name suggests, can intercept requests as well as their respective responses. An interceptor, represented by the RequestInterceptor protocol, defines a method to be invoked for each situation. In other words, this means that any request and respective response can be modified by each interceptor before the network stack executes and returns it, respectively.

Resource

A resource, represented by the NetworkResource protocol, represents an action that one can take on an API. Ultimately, the network stack turns a resource into a network request.

At its core, this protocol inherits from the Resource protocol, which has three associated types:

  • Remote - the raw type, typically a byte buffer representation like Data.
  • Local - the model type, a type-safe representation of the data.
  • Error - the error type used to represent API errors.

Additionally, a resource has three closures:

  • The parse closure to transform the Remote type into the Local one.
  • The serialize closure to do the reverse, serializing the Local type into the Remote.
  • The errorParser closure that returns an optional error when a networking error occurs.

In the network stack, one can use the NetworkResource or one of the more complete representations of a resource that Alicerce provides: the relative network resource, represented by the RelativeNetworkResource protocol, and the static network resource, represented by the StaticNetworkResource protocol.

Both are quite similar, but while the former always operates on relative paths on top of a static base URL, the latter operates on absolute paths.

These resources store the following data:

  • HTTP method (e.g., GET or POST)
  • HTTP header fields
  • HTTP query
  • HTTP body

Additionally, a relative resource stores a relative path, whereas a static resource stores a URL.

Error

The fetching action of a stack, in case of error, should throw an error of the Network.Error type. This type encapsulates the different possible error scenarios:

  • http, which represents an HTTP error and has an associated error code and an optional API error – the type of this error is the Error associated type of the resource which the stack is trying to fetch.
  • noData, when the response is empty
  • url, when the request fails
  • badResponse, when a valid HTTP response is missing
  • authenticator, when the authentication fails (for stacks with an authenticator configured)

Usage

Although one can conform to the NetworkStack protocol to create a network stack from scratch, Alicerce provides a default stack implementation to handle HTTP requests, represented by the URLSessionNetworkStack class, which should cover most of the use cases. Internally, this stack is backed by an URLSession.

Setup

First, start with a network stack, the centerpiece of your network layer. For HTTP networking, it's simple as initializing an URLSessionNetworkStack. You need to inject a session into it before making any requests – not doing will result in a fatal error.

import Alicerce

let network = Network.URLSessionNetworkStack()

network.session = URLSession(configuration: .default, delegate: network, delegateQueue: nil)

Note that the delegate assigned to the session must be the network stack itself. A session without a delegate or a delegate that is anything but the stack itself results in a fatal error.

To preserve dependency injection, and since a session's delegate is only defined on its initialization, the session must be injected via a property.

Second, you need to create your implementation of a resource. The following example uses Swift's Codable to parse and serialize the models.

enum APIError: Error, Decodable {
    case generic(message: String)

    init(from decoder: Decoder) throws {
        // ...
    }
}

struct RESTResource<T: Codable>: StaticNetworkResource {

    typealias Remote = Data
    typealias Local = T
    typealias Error = APIError

    static var empty: Data { return Data() }

    let parse: (Data) throws -> T = { return try JSONDecoder().decode(T.self, from: $0) }
    let serialize: (T) throws -> Data = { return try JSONEncoder().encode($0) }
    let errorParser: (Data) -> APIError? = { return try? JSONDecoder().decode(APIError.self, from: $0) }

    let url: URL
    let method: HTTP.Method
    let headers: HTTP.Headers?
    let query: HTTP.Query?
    let body: Data?

    init(url: URL,
         method: HTTP.Method = .GET,
         headers: HTTP.Headers? = nil,
         query: HTTP.Query? = nil,
         body: Data? = nil) {

        self.url = url
        self.method = method
        self.headers = headers
        self.query = query
        self.body = body
    }
}

Making a request

struct Model: Codable {
    // ...
}

let resource = RESTResource<Model>(url: URL(string: "http://localhost/")!)

network.fetch(resource: resource) { result in
    switch result {
    case let .success(data):
        // Valid response
    case let .failure(.http(code, apiError as APIError)):
        // API error
    case let .failure(error):
        // Network error
    }
}