179 lines
8.4 KiB
Swift
179 lines
8.4 KiB
Swift
//
|
|
// BuildkiteClient.swift
|
|
// Buildkite
|
|
//
|
|
// Created by Aaron Sky on 3/22/20.
|
|
// Copyright © 2020 Aaron Sky. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
public actor BuildkiteClient {
|
|
nonisolated var encoder: JSONEncoder {
|
|
let encoder = JSONEncoder()
|
|
encoder.dateEncodingStrategy = .custom(Formatters.encodeISO8601)
|
|
return encoder
|
|
}
|
|
|
|
nonisolated var decoder: JSONDecoder {
|
|
let decoder = JSONDecoder()
|
|
decoder.dateDecodingStrategy = .custom(Formatters.decodeISO8601)
|
|
return decoder
|
|
}
|
|
|
|
/// Configuration for general interaction with the Buildkite API, including access tokens and supported API versions.
|
|
var configuration: Configuration
|
|
|
|
/// The network (or whatever) transport layer. Implemented by URLSession by default.
|
|
var transport: Transport
|
|
|
|
/// Convenience property for setting the access token used by the client.
|
|
var tokens: TokenProvider?
|
|
|
|
/// Creates a session with the specified configuration, transport layer, and token provider.
|
|
/// - Parameters:
|
|
/// - configuration: Configures supported API versions and the access token. Uses the latest supported API versions by default.
|
|
/// - transport: Transport layer used for API communication. Uses the shared URLSession by default.
|
|
/// - tokens: Token provider used for authorization. Unauthenticated by default.
|
|
public init(
|
|
configuration: Configuration = .default,
|
|
transport: Transport = URLSession.shared,
|
|
tokens: TokenProvider? = nil
|
|
) {
|
|
self.configuration = configuration
|
|
self.transport = transport
|
|
self.tokens = tokens
|
|
}
|
|
|
|
/// Creates a session with the specified configuration, transport layer, and fixed token string.
|
|
/// - Parameters:
|
|
/// - configuration: Configures supported API versions and the access token. Uses the latest supported API versions by default.
|
|
/// - transport: Transport layer used for API communication. Uses the shared URLSession by default.
|
|
/// - token: Raw token used for authorization.
|
|
public init(
|
|
configuration: Configuration = .default,
|
|
transport: Transport = URLSession.shared,
|
|
token: String
|
|
) {
|
|
self.init(configuration: configuration, transport: transport, tokens: RawTokenProvider(rawValue: token))
|
|
}
|
|
|
|
private func checkResponseForIssues(_ response: URLResponse, data: Data? = nil) throws {
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
let statusCode = StatusCode(rawValue: httpResponse.statusCode)
|
|
else {
|
|
throw ResponseError.incompatibleResponse(response)
|
|
}
|
|
if !statusCode.isSuccess {
|
|
guard let data = data,
|
|
let errorIntermediary = try? decoder.decode(BuildkiteError.Intermediary.self, from: data)
|
|
else {
|
|
throw statusCode
|
|
}
|
|
throw BuildkiteError(statusCode: statusCode, intermediary: errorIntermediary)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Async/Await API
|
|
|
|
extension BuildkiteClient {
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameter resource: A resource.
|
|
/// - Returns: A response containing the content of the response body, as well as other information about the HTTP operation.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R) async throws -> Response<R.Content> where R: Resource, R.Content: Decodable {
|
|
let request = try URLRequest(resource, configuration: configuration, tokens: tokens)
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
let content = try self.decoder.decode(R.Content.self, from: data)
|
|
return Response(content: content, response: response)
|
|
}
|
|
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameters:
|
|
/// - resource: A resource.
|
|
/// - pageOptions: Page options to perform pagination.
|
|
/// - Returns: A response containing the content of the response body, as well as other information about the HTTP operation.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R, pageOptions: PageOptions? = nil) async throws -> Response<R.Content>
|
|
where R: PaginatedResource {
|
|
let request = try URLRequest(resource, configuration: configuration, tokens: tokens, pageOptions: pageOptions)
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
let content = try self.decoder.decode(R.Content.self, from: data)
|
|
return Response(content: content, response: response)
|
|
}
|
|
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameter resource: A resource.
|
|
/// - Returns: A response containing the content of the response body, as well as other information about the HTTP operation.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R) async throws -> Response<R.Content>
|
|
where R: Resource, R.Body: Encodable, R.Content: Decodable {
|
|
let request = try URLRequest(resource, configuration: configuration, tokens: tokens, encoder: encoder)
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
let content = try self.decoder.decode(R.Content.self, from: data)
|
|
return Response(content: content, response: response)
|
|
}
|
|
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameters:
|
|
/// - resource: A resource.
|
|
/// - pageOptions: Page options to perform pagination.
|
|
/// - Returns: A response containing the content of the response body, as well as other information about the HTTP operation.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R, pageOptions: PageOptions? = nil) async throws -> Response<R.Content>
|
|
where R: PaginatedResource, R.Body: Encodable {
|
|
let request = try URLRequest(
|
|
resource,
|
|
configuration: configuration,
|
|
tokens: tokens,
|
|
encoder: encoder,
|
|
pageOptions: pageOptions
|
|
)
|
|
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
let content = try self.decoder.decode(R.Content.self, from: data)
|
|
return Response(content: content, response: response)
|
|
}
|
|
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameter resource: A resource.
|
|
/// - Returns: A response containing the content of the response body, as well as other information about the HTTP operation.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R) async throws -> Response<R.Content> where R: Resource, R.Content == Void {
|
|
let request = try URLRequest(resource, configuration: configuration, tokens: tokens)
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
return Response(content: (), response: response)
|
|
}
|
|
|
|
/// Performs the given resource asynchronously.
|
|
/// - Parameter resource: A resource.
|
|
/// - Returns: A response containing information about the HTTP operation, and no content.
|
|
/// - Throws: An error describing the manner in which the resource failed to complete.
|
|
public func send<R>(_ resource: R) async throws -> Response<R.Content>
|
|
where R: Resource, R.Body: Encodable, R.Content == Void {
|
|
let request = try URLRequest(resource, configuration: configuration, tokens: tokens, encoder: encoder)
|
|
let (data, response) = try await transport.send(request: request)
|
|
try checkResponseForIssues(response, data: data)
|
|
return Response(content: (), response: response)
|
|
}
|
|
|
|
/// Performs the given GraphQL query or mutation and returns the content asynchronously.
|
|
/// - Parameter resource: A resource.
|
|
/// - Returns: Content of the resolved GraphQL operation.
|
|
/// - Throws: An error either of type ``BuildkiteError`` or ``GraphQL/Errors``.
|
|
public func sendQuery<T>(_ resource: GraphQL<T>) async throws -> T {
|
|
let response = try await send(resource)
|
|
return try response.content.get()
|
|
}
|
|
}
|