Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 4 additions & 41 deletions Sources/YData/Client/InternalClient+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,11 @@ import Foundation
import Vapor

public extension InternalClient {
func send<Request: InternalRequest, Resp: Response>(_ request: Request) async throws -> Resp
where Request.Content: Encodable {
return try await self.send(request).get()
func send<Request: InternalRequest, Response: InternalResponse>(_ request: Request) async throws -> Response {
try await send(request).get()
}

func send<Request: InternalRequest, Resp: Response>(_ request: Request) async throws -> Resp {
return try await self.send(request).get()
}

func send<Request: InternalRequest, Resp: InternalModel>(_ request: Request) async throws -> Resp
where Request.Content: Encodable {
try await (send(request) as EventLoopFuture<ClientResponse>).get().mapToInternalModel()
}

func send<Request: InternalRequest, Resp: InternalModel>(_ request: Request) async throws -> Resp {
try await (send(request) as EventLoopFuture<ClientResponse>).get().mapToInternalModel()
}
}

private extension ClientResponse {
func mapToInternalModel<R>() async throws -> R where R: InternalModel {
switch status.code {
case (100..<400):
do {
return try content.decode(R.self)
} catch {
throw Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response \(error)")
}
default:
do {
let contentError = try content.decode(Internal.ServiceError.self)
throw Internal.ErrorResponse(headers: headers,
status: status,
message:contentError.message)
} catch {
throw Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response with error \(error)")
}
}
func send<Request: InternalRequest, C: Decodable>(_ request: Request) async throws -> C {
try await (send(request) as EventLoopFuture<ClientResponse>).get().mapToModel()
}
}

32 changes: 9 additions & 23 deletions Sources/YData/Client/InternalClient.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import Vapor

public extension Internal {
enum Client {}
}

public protocol InternalClient {
var scheme: URI.Scheme { get }
var host: String { get }
Expand All @@ -9,8 +13,6 @@ public protocol InternalClient {

var httpClient: Vapor.Client { get }
var logger: Logger { get }

func send<Req: InternalRequest, Resp: Response>(_ request: Req) -> EventLoopFuture<Resp>
}

public enum InternalClientError: Error {
Expand All @@ -21,27 +23,10 @@ public extension InternalClient {
var scheme: URI.Scheme { URI.Scheme("http") }
var basePath: String? { nil }

func send<Request: InternalRequest, R: Response>(_ request: Request) -> EventLoopFuture<R>
where Request.Content: Encodable {

var clientRequest = buildClientRequest(for: request)

do {
try request.content.flatMap { try clientRequest.content.encode($0, as: .json) }
} catch {
return httpClient.eventLoop.makeFailedFuture(InternalClientError.encode(error))
}

return httpClient.send(clientRequest)
.always { self.logger.info("response for request \(clientRequest.url): \($0)") }
.mapToInternalResponse()
}

func send<Request: InternalRequest, R: Response>(_ request: Request) -> EventLoopFuture<R> {

func send<Request: InternalRequest, Response: InternalResponse>(_ request: Request) -> EventLoopFuture<Response> {
let clientRequest = buildClientRequest(for: request)

return httpClient.send(clientRequest)
return httpClient.send(buildClientRequest(for: request))
.always { self.logger.info("response for request \(clientRequest.url): \($0)") }
.mapToInternalResponse()
}
Expand All @@ -67,12 +52,13 @@ public extension InternalClient {
request.headers.flatMap { clientRequest.headers = .init($0.map { (key, value) in (key, value) }) }
clientRequest.method = request.method
clientRequest.url = url
clientRequest.body = request.body
return clientRequest
}
}

private extension EventLoopFuture where Value == ClientResponse {
func mapToInternalResponse<R>() -> EventLoopFuture<R> where R: Response {
private extension EventLoopFuture where Value: InternalResponse {
func mapToInternalResponse<R>() -> EventLoopFuture<R> where R: InternalResponse {
return self.flatMapResult { response -> Result<R, Internal.ErrorResponse> in
switch response.status.code {
case (100..<400):
Expand Down
85 changes: 59 additions & 26 deletions Sources/YData/Client/InternalRequest.swift
Original file line number Diff line number Diff line change
@@ -1,53 +1,86 @@
import Vapor

public protocol InternalRequest {
associatedtype Content

var method: HTTPMethod { get }
var path: String? { get }
var headers: HTTPHeaders? { get }
var query: [URLQueryItem]? { get }
var content: Content? { get }
var content: ContentContainer { get }
var body: ByteBuffer? { get }
}

public extension Internal {
struct NoContentRequest: InternalRequest {
public typealias Content = Optional<Void>

struct ContentRequest: InternalRequest {
public let method: HTTPMethod
public let path: String?
public let headers: HTTPHeaders?
public var headers: HTTPHeaders?
public let query: [URLQueryItem]?
public let content: Content? = nil

public var body: ByteBuffer?

public init(method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil) {
public init<C: Content>(method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil,
content: C) throws {
self.method = method
self.path = path
self.headers = headers
self.query = query
try self.content.encode(content)
}
}

struct ContentRequest<Content: Encodable>: InternalRequest {
public let method: HTTPMethod
public let path: String?
public let headers: HTTPHeaders?
public let query: [URLQueryItem]?
public let content: Content?


public init(method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil,
content: Content? = nil) {
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil) {
self.method = method
self.path = path
self.headers = headers
self.query = query
self.content = content
}
}
}

public extension Internal.ContentRequest {
private struct _ContentContainer: ContentContainer {
var body: ByteBuffer?
var headers: HTTPHeaders

var contentType: HTTPMediaType? { headers.contentType }

func decode<D>(_ decodable: D.Type, using decoder: ContentDecoder) throws -> D where D : Decodable {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
return try decoder.decode(D.self, from: body, headers: self.headers)
}

mutating func encode<E>(_ encodable: E, using encoder: ContentEncoder) throws where E : Encodable {
var body = ByteBufferAllocator().buffer(capacity: 0)
try encoder.encode(encodable, to: &body, headers: &self.headers)
self.body = body
}
}

var content: ContentContainer {
get {
return _ContentContainer(body: self.body, headers: self.headers ?? [:])
}
set {
let container = (newValue as! _ContentContainer)
self.body = container.body
self.headers += container.headers
}
}
}

extension Optional where Wrapped == HTTPHeaders {
static func +=(left: inout Self, right: Wrapped) {
if var left = left {
left.add(contentsOf: right)
}

left = right
}
}
70 changes: 70 additions & 0 deletions Sources/YData/Client/InternalResponse+Vapor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Foundation
import Vapor

extension ResponseEncodable where Self: InternalResponse {
func encodeResponse(for request: Request) -> EventLoopFuture<Vapor.Response> {
let response = Vapor.Response(status: status, headers: headers)
response.body ?= body.flatMap(Vapor.Response.Body.init)
return request.eventLoop.makeSucceededFuture(response)
}
}

public extension EventLoopFuture where Value: InternalResponse {
func mapContent<NewValue>(_ callback: @escaping (ContentContainer) throws -> (NewValue))
-> EventLoopFuture<NewValue> where NewValue: Content {
flatMapThrowing { try callback($0.content) }
}

func mapToContent<R>() -> EventLoopFuture<R> where R: Decodable {
flatMapThrowing { response -> R in try response.content.decode(R.self) }
}

func flatMapContentThrowing<NewValue>(_ callback: @escaping (ContentContainer) throws -> (NewValue))
-> EventLoopFuture<NewValue> { flatMapThrowing { try callback($0.content) } }

func flatMapContentResult<NewValue, E>(_ callback: @escaping (ContentContainer) -> Result<NewValue, E>)
-> EventLoopFuture<NewValue> where NewValue: Content, E: Error { flatMapResult { callback($0.content) } }
}

extension ClientResponse: InternalResponse {
public init(headers: HTTPHeaders, status: HTTPResponseStatus, body: ByteBuffer?) {
self.init(status: status, headers: headers, body: body)
}

@inlinable
func map<NewValue>(_ callback: (ContentContainer) throws -> (NewValue)) throws -> Self where NewValue: Content {
let newValue = try callback(content)

var newResponse = Self.init(headers: headers, status: status, body: nil)
try newResponse.content.encode(newValue)

return newResponse
}
}

public extension ClientResponse {
func mapToModel<C>() throws -> C where C: Decodable {
switch status.code {
case (100..<400):

do {
return try content.decode(C.self)
} catch {
throw Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response \(error)")
}
default:
do {
let contentError = try content.decode(Internal.ServiceError.self)
throw Internal.ErrorResponse(headers: headers,
status: status,
message:contentError.message)
} catch {
throw Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response with error \(error)")
}
}
}
}
12 changes: 11 additions & 1 deletion Sources/YData/Client/InternalResponse.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import Vapor

public protocol InternalResponse {
var headers: HTTPHeaders { get set }
var status: HTTPResponseStatus { get }
var body: ByteBuffer? { get set }

var content: ContentContainer { get }

init(headers: HTTPHeaders, status: HTTPResponseStatus, body: ByteBuffer?)
}

public extension Internal {
struct ErrorResponse: Error {
public let headers: HTTPHeaders
public let status: HTTPResponseStatus
public let message: String
}

struct SuccessResponse: Response {
struct SuccessResponse: InternalResponse {
public var headers: HTTPHeaders
public let status: HTTPResponseStatus
public var body: ByteBuffer?
Expand Down
55 changes: 0 additions & 55 deletions Sources/YData/Client/Response.swift

This file was deleted.