Skip to content

Commit

Permalink
Merge pull request #1474 from stripe-ios/kg-addstatuscode
Browse files Browse the repository at this point in the history
StripeCore: added support for seeing HTTP response status code as part of StripeServiceError.
  • Loading branch information
kgaidis-stripe authored Sep 29, 2022
2 parents 4f73a03 + 982c199 commit 2496978
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 13 deletions.
16 changes: 9 additions & 7 deletions StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,16 +433,17 @@ extension STPAPIClient {
request: URLRequest,
completion: @escaping (Result<T, Error>) -> Void
) {
urlSession.stp_performDataTask(with: request, completionHandler: { (data, _, error) in
urlSession.stp_performDataTask(with: request, completionHandler: { (data, response, error) in
DispatchQueue.main.async {
completion(STPAPIClient.decodeResponse(data: data, error: error))
completion(STPAPIClient.decodeResponse(data: data, error: error, response: response))
}
})
}

@_spi(STP) public static func decodeResponse<T: Decodable>(
data: Data?,
error: Error?
error: Error?,
response: URLResponse?
) -> Result<T, Error> {
if let error = error {
return .failure(error)
Expand All @@ -454,15 +455,15 @@ extension STPAPIClient {
do {
/// HACK: We must first check if EmptyResponses contain an error since it'll always parse successfully.
if T.self == EmptyResponse.self,
let decodedStripeError = decodeStripeErrorResponse(data: data) {
let decodedStripeError = decodeStripeErrorResponse(data: data, response: response) {
return .failure(decodedStripeError)
}

let decodedObject: T = try StripeJSONDecoder.decode(jsonData: data)
return .success(decodedObject)
} catch {
// Try decoding the error from the service if one is available
if let decodedStripeError = decodeStripeErrorResponse(data: data) {
if let decodedStripeError = decodeStripeErrorResponse(data: data, response: response) {
return .failure(decodedStripeError)
} else {
// Return decoding error directly
Expand All @@ -472,11 +473,12 @@ extension STPAPIClient {
}

/// Decodes request data to see if it can be parsed as a Stripe error
private static func decodeStripeErrorResponse(data: Data) -> StripeError? {
private static func decodeStripeErrorResponse(data: Data, response: URLResponse?) -> StripeError? {
var decodedError: StripeError?

if let decodedErrorResponse: StripeAPIErrorResponse = try? StripeJSONDecoder.decode(jsonData: data),
let apiError = decodedErrorResponse.error {
var apiError = decodedErrorResponse.error {
apiError.statusCode = (response as? HTTPURLResponse)?.statusCode
decodedError = StripeError.apiError(apiError)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import Foundation
@_spi(STP) public var message: String?
/// If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field.
@_spi(STP) public var param: String?
/// The response’s HTTP status code.
@_spi(STP) public var statusCode: Int?

// More information may be available in `allResponseFields`, including
// the PaymentIntent or PaymentMethod.
Expand Down
2 changes: 1 addition & 1 deletion StripeCore/StripeCoreTestUtils/Mocks/MockData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public extension MockData {
}

func make() throws -> ResponseType {
let result: Result<ResponseType, Error> = STPAPIClient.decodeResponse(data: try data(), error: nil)
let result: Result<ResponseType, Error> = STPAPIClient.decodeResponse(data: try data(), error: nil, response: nil)
switch result {
case .success(let response):
return response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,34 @@ class STPAPIClient_EmptyResponseTest: XCTestCase {
]

let responseData = try JSONSerialization.data(withJSONObject: response, options: [])
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(data: responseData, error: nil)
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(
data: responseData,
error: nil,
response: HTTPURLResponse(
url: URL(string: "https://www.stripe.com")!,
statusCode: 400,
httpVersion: nil,
headerFields: nil
)
)

guard case .failure = result else {
switch result {
case .success(_):
XCTFail("The request should not have succeeded")
return
case .failure(let error):
if let stripeError = error as? StripeError, case .apiError(let apiError) = stripeError {
XCTAssert(apiError.statusCode == 400, "expected status code to be set")
} else {
XCTFail("The error should have been an `.apiError`")
}
}
}

/// Response is an empty response; Error is not nil
/// Should result in a failure
func testEmptyResponse_WithError() throws {
let responseData = try JSONSerialization.data(withJSONObject: [:], options: [])
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(data: responseData, error: NSError.stp_genericConnectionError())
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(data: responseData, error: NSError.stp_genericConnectionError(), response: nil)

guard case .failure = result else {
XCTFail("The request should not have succeeded")
Expand All @@ -44,7 +59,7 @@ class STPAPIClient_EmptyResponseTest: XCTestCase {
/// Should result in a success
func testEmptyResponse_NoError() throws {
let responseData = try JSONSerialization.data(withJSONObject: [:], options: [])
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(data: responseData, error: nil)
let result: Result<EmptyResponse, Error> = STPAPIClient.decodeResponse(data: responseData, error: nil, response: nil)

guard case .success = result else {
XCTFail("The request should have succeeded")
Expand Down

0 comments on commit 2496978

Please sign in to comment.