2 min read
Networking
Last Updated - Platform 25.0 - SDK 20.0The 'network layer' or networking within the PoqSDK was rewritten in v23.0
using Swift Concurrency and SwiftUI style API.
Session
The NetworkSession
is responsible for executing network requests.
Create and execute network tasks to execute network requests using a network session.
With Swift's evolution URLSession
has grown and branched a few ways to execute network requests.
Under the hood, the poq NetworkSession
uses async / await but the SDK provides all other means of execution via asynchronous tasks.
The main session can be accessed via Container.shared.networkSession()
which defaults to PoqNetworkSession
, or MockSession.shared
when running tests.
PoqNetworkSession
The PoqNetworkSession
adds common headers, signs (if enabled) and authorises all requests before execution.
By default it is initialised using the Container.shared.authenticator()
which can be optional for no authentication.
MockSession
The MockSession
subclasses the PoqNetworkSession
but uses URLProtocol
to replace requests using the mocks
dictionary.
Requests are replaced with the MockResponder
value from the first match where their path contains a key from the mocks
dictionary.
Requests with no matching mock return a 'file not found' error.
Mock requests can be set using the subscript and a mock json file added to the bundle.
Use the response
function to create a MockResponder
and assign it to a path.
// Match only `/category/some-title`.MockSession.shared["category/some-title"] = .response(from: .init(for: Self.self), json: "Category")
// Match all categories.MockSession.shared["category"] = .response(from: .init(for: Self.self), json: "Category")
Authenticator
The Authenticator
has the following two important async functions:
authorize(request:)
is called by the session to authorise requests.authenticate()
is called by the session on 401 responses.
When using the PoqAccount
SDK the authenticator defaults to the PoqAccountAuthenticator
and uses the Account API for authorization and authentication.
Otherwise, it defaults to the PoqAuthenticator
that performs no authentication.
Requests
The NetworkRequest
API allows easy and chainable creation of HTTPURLRequests using SwiftUI style syntax.
Most service implementations will create a network request then convert it to a network task. Network requests default on initialisation to the current environment api host.
// Creates the update cart request.NetworkRequest(method: .POST) .path("/cart") .queryItem(name: "flag", value: true) .body(cart)
Response Decoding
The NetworkResponseDecoder
decodes the response from the session into the expected data type or a useful error from the response body.
The network session decides if a response is a failure based on status but the decoder can also override this.
This can be replaced for specific requests when converting them to tasks by providing a decoder
.
Or, globally, using PoqNetworkSession(decoder:)
and injecting into the shared container.
The default network decoder treats failures as PoqMessage
responses and pulls the best error message from them.
Tasks
Requests can be converted to asynchronous network tasks using .task()
and optionally providing a session.
Tasks are constrained to the expected response data type be which can specified using expecting:
or inferred from the surrounding context.
// Creates a task wrapping the shop request against the main session.func shop() -> NetworkTask<CategoryTreeData> { NetworkRequest() .path("/shop") .task()}
Network tasks can also be converted from requests expecting one type but mapping to a different type.
// Creates a task wrapping the shop request and converting it to just name against the main session.func shopMainCategory() -> NetworkTask<CategoryData> { NetworkRequest() .path("/shop") .task(expecting: CategoryTreeData.self) { $0.category ?? { throw NetworkError.unknown }() }}
Execution
Tasks do not execute immediately. Instead you execute them later using your preferred method (async / await, combine or completion handlers).
The task()
function returns a NetworkTask
; a special task type that returns the full network response returned by the decoder and session.
This can be consumed by a repository to gain access to the exact statusCode
and the response of the expected data type.
let response = await shop().execute()if response.statusCode == 201 { print("Updated?") }
Network tasks provide convenience functions for directly executing and accessing the response data value using value()
.
// Async / Awaitlet shop = await shop().value()
// Closureshop().value { result in }
// Combineshop().valueProvider().sink { _ in } receiveValue: { shop in }
Type Overriding
Network tasks are also special in that the response type can be overridden outside of the service to reduce code.
This is useful when your API does not match the contract and cannot be fulfilled using customData
.
// Decode the raw response data to a different type using the session as the default response decoder.let shop = await shop().decode(expecting: CustomShopCategoryData.self).value()
// Or pass a custom response decoder.let shop = await shop().decode(expecting: CustomShopCategoryData.self, decoder: CustomResponseDecoder()).value()