Skip to content

Commit

Permalink
fix(internal-client): FabricError as response content for error (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
portellaa committed Oct 27, 2022
1 parent f7f3455 commit 52cff3f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 66 deletions.
11 changes: 11 additions & 0 deletions .devcontainer/devcontainer.json
@@ -0,0 +1,11 @@
{
"name": "Swift 5.6",
"image": "swift:5.6-focal",
"extensions": [
"sswg.swift-lang"
],
"settings": {
"lldb.library": "/usr/lib/liblldb.so"
},
"forwardPorts": [8080, 1337]
}
40 changes: 10 additions & 30 deletions Sources/YData/Client/InternalClient.swift
Expand Up @@ -10,7 +10,7 @@ public protocol InternalClient {
var host: String { get }
var port: Int? { get }
var basePath: String? { get }

var httpClient: Vapor.Client { get }
var logger: Logger { get }
}
Expand All @@ -22,32 +22,32 @@ public enum InternalClientError: Error {
public extension InternalClient {
var scheme: URI.Scheme { URI.Scheme("http") }
var basePath: String? { nil }

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

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

internal func buildClientRequest<R: InternalRequest>(for request: R) -> ClientRequest {
let path = basePath.flatMap { base in request.path.flatMap { "\(base)/\($0)" } ?? base } ?? request.path ?? ""

let query = request.query.flatMap { queries in
queries.compactMap { query -> String? in
guard let value = query.value else { return nil }

guard let escapedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return nil
}

return "\(query.name)=\(escapedValue)"
}.joined(separator: "&")
}

let url = URI(scheme: scheme, host: host, port: port, path: path, query: query)

var clientRequest = ClientRequest()
request.headers.flatMap { clientRequest.headers = .init($0.map { (key, value) in (key, value) }) }
clientRequest.method = request.method
Expand All @@ -64,27 +64,7 @@ private extension EventLoopFuture where Value: InternalResponse {
case (100..<400):
return .success(R(headers: response.headers, status: response.status, body: response.body))
default:
do {
let contentError = try response.content.decode(Internal.ServiceError.self)
return .failure(Internal.ErrorResponse(headers: response.headers,
status: response.status,
message:contentError.message))
} catch DecodingError.keyNotFound {
do {
let contentError = try response.content.decode(String.self)
return .failure(Internal.ErrorResponse(headers: response.headers,
status: response.status,
message: contentError))
} catch {
return .failure(Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response with error \(error)"))
}
} catch {
return .failure(Internal.ErrorResponse(headers: [:],
status: .internalServerError,
message: "failed to decode response with error \(error)"))
}
return .failure(Internal.ErrorResponse(response: response))
}
}
}
Expand Down
22 changes: 13 additions & 9 deletions Sources/YData/Client/InternalRequest.swift
Expand Up @@ -19,22 +19,26 @@ public extension Internal {

public var body: ByteBuffer?

public init<C: Content>(method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil,
content: C) throws {
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)
}

public init(method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil) {
public init(
method: HTTPMethod,
path: String? = nil,
headers: HTTPHeaders? = nil,
query: [URLQueryItem]? = nil
) {
self.method = method
self.path = path
self.headers = headers
Expand Down
28 changes: 8 additions & 20 deletions Sources/YData/Client/InternalResponse+Vapor.swift
Expand Up @@ -58,29 +58,17 @@ public extension ClientResponse {
return try content.decode(C.self)
} catch {
throw Internal.ErrorResponse(
content: GenericFabricError(
description: "\(error)",
httpCode: Int(HTTPResponseStatus.internalServerError.code),
name: "DecodingError",
returnValue: -1),
headers: [:],
status: .internalServerError,
message: "failed to decode response \(error)")
status: .internalServerError
)
}
default:
do {
let contentError = try content.decode(Internal.ServiceError.self)
throw Internal.ErrorResponse(
headers: headers,
status: status,
message: contentError.message)
} catch DecodingError.keyNotFound {
let contentError = try content.decode(String.self)
throw Internal.ErrorResponse(
headers: headers,
status: status,
message: contentError)
} catch {
throw Internal.ErrorResponse(
headers: [:],
status: .internalServerError,
message: "failed to decode response with error \(error)")
}
throw Internal.ErrorResponse(response: self)
}
}
}
Expand Down
53 changes: 46 additions & 7 deletions Sources/YData/Client/InternalResponse.swift
Expand Up @@ -32,9 +32,52 @@ public extension InternalResponse {

public extension Internal {
struct ErrorResponse: Error {
public let headers: HTTPHeaders
public let status: HTTPResponseStatus
public let message: String
public var headers: HTTPHeaders
public var status: HTTPResponseStatus
public let content: any FabricError

init(content: any FabricError, headers: HTTPHeaders, status: HTTPResponseStatus) {
self.content = content
self.headers = headers
self.status = status
}

init(response: any InternalResponse) {
self.headers = response.headers
var status = response.status

if let fabricError = try? response.content.decode(GenericFabricError.self) {
self.content = fabricError
} else if let serviceError = try? response.content.decode(Internal.ServiceError.self) {
self.content = GenericFabricError(
description: serviceError.message,
httpCode: Int(status.code),
name: "\(Internal.ServiceError.self)",
returnValue: -1
)
} else {
do {
let errorMessage = try response.content.decode(String.self)

self.content = GenericFabricError(
description: errorMessage,
httpCode: Int(response.status.code),
name: "Unknown Error",
returnValue: -1
)
} catch {
status = .internalServerError
self.content = GenericFabricError(
description: "\(error)",
httpCode: Int(response.status.code),
name: "Unknown Error",
returnValue: -1
)
}
}

self.status = status
}
}

struct SuccessResponse: InternalResponse {
Expand All @@ -50,10 +93,6 @@ public extension Internal {
}
}

extension Internal.ErrorResponse: AbortError {
public var reason: String { message }
}

public extension Internal.SuccessResponse {
private struct _ContentContainer: ContentContainer {
var body: ByteBuffer?
Expand Down

0 comments on commit 52cff3f

Please sign in to comment.