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.
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).
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.
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 likeData
.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 theRemote
type into theLocal
one.- The
serialize
closure to do the reverse, serializing theLocal
type into theRemote
.- 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
orPOST
) - HTTP header fields
- HTTP query
- HTTP body
Additionally, a relative resource stores a relative path, whereas a static resource stores a URL.
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 theError
associated type of the resource which the stack is trying to fetch.noData
, when the response is emptyurl
, when the request failsbadResponse
, when a valid HTTP response is missingauthenticator
, when the authentication fails (for stacks with an authenticator configured)
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.
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
}
}
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
}
}