Resources now declare their required API versions, making GraphQL support a little cleaner
This commit is contained in:
parent
e4ae58b14b
commit
135510dcb9
|
@ -19,17 +19,17 @@ public final class Buildkite {
|
|||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
return encoder
|
||||
}()
|
||||
|
||||
|
||||
let decoder: JSONDecoder = {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .custom(Formatters.decodeISO8601)
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
return decoder
|
||||
}()
|
||||
|
||||
|
||||
var configuration: Configuration
|
||||
var transport: Transport
|
||||
|
||||
|
||||
public var token: String? {
|
||||
get {
|
||||
configuration.token
|
||||
|
@ -38,12 +38,12 @@ public final class Buildkite {
|
|||
configuration.token = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public init(configuration: Configuration = .default, transport: Transport = URLSession.shared) {
|
||||
self.configuration = configuration
|
||||
self.transport = transport
|
||||
}
|
||||
|
||||
|
||||
private func handleContentfulResponse<Content: Decodable>(completion: @escaping (Result<Response<Content>, Error>) -> Void) -> Transport.Completion {
|
||||
return { [weak self] result in
|
||||
guard let self = self else {
|
||||
|
@ -63,7 +63,7 @@ public final class Buildkite {
|
|||
completion(.success(Response(content: content, response: response)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handleEmptyResponse(completion: @escaping (Result<Response<Void>, Error>) -> Void) -> Transport.Completion {
|
||||
return { [weak self] result in
|
||||
guard let self = self else {
|
||||
|
@ -80,7 +80,7 @@ public final class Buildkite {
|
|||
completion(.success(Response(content: (), response: response)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func checkResponseForIssues(_ response: URLResponse, data: Data? = nil) throws {
|
||||
guard let response = response as? HTTPURLResponse,
|
||||
let statusCode = StatusCode(rawValue: response.statusCode) else {
|
||||
|
@ -101,12 +101,24 @@ public final class Buildkite {
|
|||
|
||||
public extension Buildkite {
|
||||
func send<R: Resource & HasResponseBody>(_ resource: R, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
|
||||
let request = URLRequest(resource, configuration: configuration)
|
||||
let request: URLRequest
|
||||
do {
|
||||
request = try URLRequest(resource, configuration: configuration)
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
|
||||
}
|
||||
|
||||
func send<R: Resource & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
|
||||
let request = URLRequest(resource, configuration: configuration, pageOptions: pageOptions)
|
||||
let request: URLRequest
|
||||
do {
|
||||
request = try URLRequest(resource, configuration: configuration, pageOptions: pageOptions)
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
|
||||
}
|
||||
|
||||
|
@ -133,7 +145,13 @@ public extension Buildkite {
|
|||
}
|
||||
|
||||
func send<R: Resource>(_ resource: R, completion: @escaping (Result<Response<Void>, Error>) -> Void) {
|
||||
let request = URLRequest(resource, configuration: configuration)
|
||||
let request: URLRequest
|
||||
do {
|
||||
request = try URLRequest(resource, configuration: configuration)
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
transport.send(request: request, completion: handleEmptyResponse(completion: completion))
|
||||
}
|
||||
|
||||
|
@ -157,7 +175,9 @@ import Combine
|
|||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Buildkite {
|
||||
public func sendPublisher<R: Resource & HasResponseBody>(_ resource: R) -> AnyPublisher<Response<R.Content>, Error> {
|
||||
transport.sendPublisher(request: URLRequest(resource, configuration: configuration))
|
||||
Result { try URLRequest(resource, configuration: configuration) }
|
||||
.publisher
|
||||
.flatMap(transport.sendPublisher)
|
||||
.tryMap {
|
||||
try self.checkResponseForIssues($0.response, data: $0.data)
|
||||
let content = try self.decoder.decode(R.Content.self, from: $0.data)
|
||||
|
@ -165,9 +185,11 @@ extension Buildkite {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
public func sendPublisher<R: Resource & HasResponseBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<R.Content>, Error> {
|
||||
transport.sendPublisher(request: URLRequest(resource, configuration: configuration, pageOptions: pageOptions))
|
||||
Result { try URLRequest(resource, configuration: configuration, pageOptions: pageOptions) }
|
||||
.publisher
|
||||
.flatMap(transport.sendPublisher)
|
||||
.tryMap {
|
||||
try self.checkResponseForIssues($0.response, data: $0.data)
|
||||
let content = try self.decoder.decode(R.Content.self, from: $0.data)
|
||||
|
@ -175,7 +197,7 @@ extension Buildkite {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
public func sendPublisher<R: Resource & HasRequestBody & HasResponseBody>(_ resource: R) -> AnyPublisher<Response<R.Content>, Error> {
|
||||
Result { try URLRequest(resource, configuration: configuration, encoder: encoder) }
|
||||
.publisher
|
||||
|
@ -187,7 +209,7 @@ extension Buildkite {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
public func sendPublisher<R: Resource & HasRequestBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<R.Content>, Error> {
|
||||
Result { try URLRequest(resource, configuration: configuration, encoder: encoder, pageOptions: pageOptions) }
|
||||
.publisher
|
||||
|
@ -199,16 +221,18 @@ extension Buildkite {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
public func sendPublisher<R: Resource>(_ resource: R) -> AnyPublisher<Response<Void>, Error> {
|
||||
transport.sendPublisher(request: URLRequest(resource, configuration: configuration))
|
||||
Result { try URLRequest(resource, configuration: configuration) }
|
||||
.publisher
|
||||
.flatMap(transport.sendPublisher)
|
||||
.tryMap {
|
||||
try self.checkResponseForIssues($0.response)
|
||||
return Response(content: (), response: $0.response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
func sendPublisher<R: Resource & HasRequestBody>(_ resource: R) -> AnyPublisher<Response<Void>, Error> {
|
||||
Result { try URLRequest(resource, configuration: configuration, encoder: encoder) }
|
||||
.publisher
|
||||
|
|
|
@ -12,7 +12,7 @@ import Foundation
|
|||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
public struct APIVersion {
|
||||
public struct APIVersion: Equatable {
|
||||
public enum REST {
|
||||
private static let baseURL = URL(string: "https://api.buildkite.com")!
|
||||
public static let v2 = APIVersion(baseURL: baseURL, version: "v2")
|
||||
|
|
|
@ -12,18 +12,18 @@ import Foundation
|
|||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
let libraryVersion = "0.0.1"
|
||||
|
||||
public struct Configuration {
|
||||
public let userAgent = "buildkite-swift/\(libraryVersion)"
|
||||
public let userAgent = "buildkite-swift"
|
||||
public var version: APIVersion
|
||||
public var graphQLVersion: APIVersion
|
||||
|
||||
public static var `default`: Configuration {
|
||||
.init(version: APIVersion.REST.v2)
|
||||
.init(version: APIVersion.REST.v2, graphQLVersion: APIVersion.GraphQL.v1)
|
||||
}
|
||||
|
||||
public init(version: APIVersion) {
|
||||
public init(version: APIVersion = APIVersion.REST.v2, graphQLVersion: APIVersion = APIVersion.GraphQL.v1) {
|
||||
self.version = version
|
||||
self.graphQLVersion = graphQLVersion
|
||||
}
|
||||
|
||||
var token: String?
|
||||
|
|
|
@ -14,11 +14,16 @@ import FoundationNetworking
|
|||
|
||||
public protocol Resource {
|
||||
associatedtype Content
|
||||
var version: APIVersion { get }
|
||||
var path: String { get }
|
||||
func transformRequest(_ request: inout URLRequest)
|
||||
}
|
||||
|
||||
extension Resource {
|
||||
public var version: APIVersion {
|
||||
APIVersion.REST.v2
|
||||
}
|
||||
|
||||
public func transformRequest(_ request: inout URLRequest) {}
|
||||
}
|
||||
|
||||
|
@ -34,8 +39,14 @@ public protocol HasResponseBody {
|
|||
public protocol Paginated: HasResponseBody {}
|
||||
|
||||
extension URLRequest {
|
||||
init<R: Resource>(_ resource: R, configuration: Configuration) {
|
||||
let url = configuration.version.url(for: resource.path)
|
||||
init<R: Resource>(_ resource: R, configuration: Configuration) throws {
|
||||
let version = resource.version
|
||||
guard version == configuration.version
|
||||
|| version == configuration.graphQLVersion else {
|
||||
throw ResponseError.incompatibleVersion
|
||||
}
|
||||
let url = version.url(for: resource.path)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
configuration.transformRequest(&request)
|
||||
resource.transformRequest(&request)
|
||||
|
@ -43,12 +54,12 @@ extension URLRequest {
|
|||
}
|
||||
|
||||
init<R: Resource & HasRequestBody>(_ resource: R, configuration: Configuration, encoder: JSONEncoder) throws {
|
||||
self.init(resource, configuration: configuration)
|
||||
try self.init(resource, configuration: configuration)
|
||||
httpBody = try encoder.encode(resource.body)
|
||||
}
|
||||
|
||||
init<R: Resource & Paginated>(_ resource: R, configuration: Configuration, pageOptions: PageOptions? = nil) {
|
||||
self.init(resource, configuration: configuration)
|
||||
init<R: Resource & Paginated>(_ resource: R, configuration: Configuration, pageOptions: PageOptions? = nil) throws {
|
||||
try self.init(resource, configuration: configuration)
|
||||
if let options = pageOptions {
|
||||
appendPageOptions(options)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import FoundationNetworking
|
|||
#endif
|
||||
|
||||
enum ResponseError: Error {
|
||||
case incompatibleVersion
|
||||
case missingResponse
|
||||
case unexpectedlyNoContent
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ public struct GraphQL<T: Codable>: Resource, HasResponseBody, HasRequestBody {
|
|||
|
||||
public var body: Body
|
||||
|
||||
public var version: APIVersion {
|
||||
APIVersion.GraphQL.v1
|
||||
}
|
||||
|
||||
public let path = ""
|
||||
|
||||
public init(rawQuery query: String, variables: [String: JSONValue] = [:]) {
|
||||
|
@ -47,7 +51,6 @@ public struct GraphQL<T: Codable>: Resource, HasResponseBody, HasRequestBody {
|
|||
}
|
||||
|
||||
public func transformRequest(_ request: inout URLRequest) {
|
||||
request.url = APIVersion.GraphQL.v1.url(for: path)
|
||||
request.httpMethod = "POST"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue