protocol Network {
    func execute(
        request: RequestParameters,
        variables: VariableData,
        cacheConfig: CacheConfig
    ) -> AnyPublisher<Data, Error>
}

Network is a protocol that you implement in your app to control how your Relay.swift talks to your GraphQL API.

Executing an operation

Network requires you to implement a single method, execute. Relay.swift will call this method when it needs to make a call to the GraphQL server. You are responsible for making the network call in whatever way makes sense for your app. You return the server's response to Relay.swift through a Combine publisher.

This gives you a great deal of flexibility around how your app talks to the server. We'll give a basic example using URLSession and a small Encodable struct, and that will probably be a good starting point for most apps. But you can also substitute another networking library, or even skip calling out to the network at all! The network layer is also a good place to address app-specific concerns like authentication.

Parameters

Returns

A Combine publisher that outputs Data. The data must be a valid JSON-formatted GraphQL response.

Examples

Basic URLSession implementation

This is a great starting point for most apps, and can be customized as needed.

import Combine
import Foundation
import Relay

private let graphqlURL = URL(string: "<https://www.example.org/graphql>")!

struct RequestPayload: Encodable {
    var query: String
    var operationName: String
    var variables: VariableData
}

class Network: Relay.Network {
    func execute(request: RequestParameters, variables: VariableData, cacheConfig: CacheConfig) -> AnyPublisher<Data, Error> {
        var req = URLRequest(url: graphqlURL)
        req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        req.httpMethod = "POST"

        do {
            let payload = RequestPayload(query: request.text!, operationName: request.name, variables: variables)
            req.httpBody = try JSONEncoder().encode(payload)
        } catch {
            return Fail(error: error).eraseToAnyPublisher()
        }

        return URLSession.shared.dataTaskPublisher(for: req)
            .map { $0.data }
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }
}

Authentication with Auth0.swift

This example is based on a real app that uses Auth0 for authentication. For each request, it asks the Auth0 credentials manager for the current credentials. If the user's token is expired, the credentials manager may have to request a new one using a refresh token, so getting credentials is an asynchronous call. The network layer wraps this in a Combine Future and chains this into a data task publisher from URLSession once it has the credentials.

import Auth0
import Combine
import Foundation
import Relay

private let url = "<https://example.org/graphql>"

class Network: Relay.Network {
    let credentialsManager: CredentialsManager

    init(credentialsManager: CredentialsManager) {
        self.credentialsManager = credentialsManager
    }

    func execute(request: RequestParameters, variables: VariableData, cacheConfig: CacheConfig) -> AnyPublisher<Data, Error> {
        return Future<Credentials, Error> { promise in
            self.credentialsManager.credentials { error, creds in
                if let error = error {
                    promise(.failure(error as Error))
                } else {
                    promise(.success(creds!))
                }
            }
        }.flatMap { credentials -> AnyPublisher<Data, Error> in
            var req = URLRequest(url: self.endpoint.url)
            req.setValue("application/json", forHTTPHeaderField: "Content-Type")
            req.httpMethod = "POST"
            req.setValue("Bearer \\(credentials.accessToken!)", forHTTPHeaderField: "Authorization")

            do {
                let payload = RequestPayload(query: request.text ?? "", operationName: request.name, variables: variables)
                req.httpBody = try JSONEncoder().encode(payload)
            } catch {
                return Fail(error: error).eraseToAnyPublisher()
            }

            return URLSession.shared.dataTaskPublisher(for: req)
                .map { $0.data }
                .mapError { $0 as Error }
                .eraseToAnyPublisher()
        }.eraseToAnyPublisher()
    }
}

struct RequestPayload: Encodable {
    var query: String
    var operationName: String
    var variables: VariableData
}