Skip to content

Commit

Permalink
split out some files and clean up error handling in foundation client
Browse files Browse the repository at this point in the history
  • Loading branch information
atierian committed Sep 20, 2023
1 parent 6802f8c commit a2b0b63
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import ClientRuntime

extension Foundation.URLRequest {
init(sdkRequest: ClientRuntime.SdkHttpRequest) throws {
guard let url = sdkRequest.endpoint.url else {
throw FoundationHTTPClientError.invalidRequestURL(sdkRequest: sdkRequest)
}
self.init(url: url)
httpMethod = sdkRequest.method.rawValue

for header in sdkRequest.headers.headers {
for value in header.value {
addValue(value, forHTTPHeaderField: header.name)
}
}

switch sdkRequest.body {
case .data(let data): httpBody = data
case .stream(let stream): httpBody = stream.toBytes().getData()
case .none: break
}
}
}

extension ClientRuntime.HttpResponse {
private static func headers(
from allHeaderFields: [AnyHashable: Any]
) -> ClientRuntime.Headers {
var headers = Headers()
for header in allHeaderFields {
switch (header.key, header.value) {
case let (key, value) as (String, String):
headers.add(name: key, value: value)
case let (key, values) as (String, [String]):
headers.add(name: key, values: values)
default: continue
}
}
return headers
}

convenience init(httpURLResponse: HTTPURLResponse, data: Data) throws {
let headers = Self.headers(from: httpURLResponse.allHeaderFields)
let body = HttpBody.stream(ByteStream.from(data: data))

guard let statusCode = HttpStatusCode(rawValue: httpURLResponse.statusCode) else {
// This shouldn't happen, but `HttpStatusCode` only exposes a failable
// `init`. The alternative here is force unwrapping, but we can't
// make the decision to crash here on behalf on consuming applications.
throw FoundationHTTPClientError.unexpectedStatusCode(
statusCode: httpURLResponse.statusCode
)
}
self.init(headers: headers, body: body, statusCode: statusCode)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
import ClientRuntime

struct FoundationHTTPClientError: AmplifyError {
let errorDescription: ErrorDescription
let recoverySuggestion: RecoverySuggestion
let underlyingError: Error?

// protocol requirement
init(
errorDescription: ErrorDescription,
recoverySuggestion: RecoverySuggestion,
error: Error
) {
self.errorDescription = errorDescription
self.recoverySuggestion = recoverySuggestion
self.underlyingError = error
}
}

extension FoundationHTTPClientError {
init(
errorDescription: ErrorDescription,
recoverySuggestion: RecoverySuggestion,
error: Error?
) {
self.errorDescription = errorDescription
self.recoverySuggestion = recoverySuggestion
self.underlyingError = error
}

static func invalidRequestURL(sdkRequest: ClientRuntime.SdkHttpRequest) -> Self {
.init(
errorDescription: """
The SdkHttpRequest generated by ClientRuntime doesn't include a valid URL
- \(sdkRequest)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}

static func invalidURLResponse(urlRequest: URLResponse) -> Self {
.init(
errorDescription: """
The URLResponse received is not an HTTPURLResponse
- \(urlRequest)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}

static func unexpectedStatusCode(statusCode: Int) -> Self {
.init(
errorDescription: """
The status code received isn't a valid `HttpStatusCode` value.
- status code: \(statusCode)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,93 +9,18 @@ import Foundation
import ClientRuntime
import Amplify

struct InvalidRequestURL: AmplifyError {
let errorDescription: ErrorDescription
let recoverySuggestion: RecoverySuggestion
let underlyingError: Error?

init(sdkRequest: ClientRuntime.SdkHttpRequest) {
self.errorDescription = """
The SdkHttpRequest generated by ClientRuntime doesn't include a valid URL
- \(sdkRequest)
"""
self.recoverySuggestion = """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
"""
self.underlyingError = nil
}

// Protocol requirement
init(
errorDescription: ErrorDescription,
recoverySuggestion: RecoverySuggestion,
error: Error
) {
self.errorDescription = errorDescription
self.recoverySuggestion = recoverySuggestion
self.underlyingError = error
}
}

extension Foundation.URLRequest {
init(sdkRequest: ClientRuntime.SdkHttpRequest) throws {
guard let url = sdkRequest.endpoint.url else {
throw InvalidRequestURL(sdkRequest: sdkRequest)
}
self.init(url: url)
httpMethod = sdkRequest.method.rawValue

for header in sdkRequest.headers.headers {
for value in header.value {
addValue(value, forHTTPHeaderField: header.name)
}
}

switch sdkRequest.body {
case .data(let data): httpBody = data
case .stream(let stream): httpBody = stream.toBytes().getData()
case .none: break
}
}
}

extension ClientRuntime.HttpResponse {
private static func headers(
from allHeaderFields: [AnyHashable: Any]
) -> ClientRuntime.Headers {
var headers = Headers()
for header in allHeaderFields {
switch (header.key, header.value) {
case let (key, value) as (String, String):
headers.add(name: key, value: value)
case let (key, values) as (String, [String]):
headers.add(name: key, values: values)
default: continue
}
}
return headers
}

convenience init(httpURLResponse: HTTPURLResponse, data: Data) {
let headers = Self.headers(from: httpURLResponse.allHeaderFields)
let body = HttpBody.stream(ByteStream.from(data: data))
// fix force unwrap
let statusCode = HttpStatusCode(rawValue: httpURLResponse.statusCode)!
self.init(headers: headers, body: body, statusCode: statusCode)
}
}

@_spi(FoundationHTTPClientEngine)
public struct FoundationHTTPClient: HttpClientEngine {
public func execute(request: ClientRuntime.SdkHttpRequest) async throws -> ClientRuntime.HttpResponse {
let urlRequest = try URLRequest(sdkRequest: request)

let (data, response) = try await URLSession.shared.data(for: urlRequest)
// Quinn says it's ok to do this.
// Safely Force Downcasting a URLResponse to an HTTPURLResponse -
// https://developer.apple.com/forums/thread/120099?answerId=372749022#372749022
let httpURLResponse = response as! HTTPURLResponse
guard let httpURLResponse = response as? HTTPURLResponse else {
// This shouldn't be necessary because we're only making HTTP requests.
// `URLResponse` should always be a `HTTPURLResponse`.
// But to refrain from crashing consuming applications, we're throwing here.
throw FoundationHTTPClientError.invalidURLResponse(urlRequest: response)
}

let httpResponse = HttpResponse(
httpURLResponse: httpURLResponse,
Expand Down

0 comments on commit a2b0b63

Please sign in to comment.