Skip to content

Commit

Permalink
fix(auth): parse and surface returned subscription @auth errors
Browse files Browse the repository at this point in the history
When a subscription fails due to an authorization issue, parse and surface AppSyncJSONValue errors
in order to let DataStore recover
  • Loading branch information
diegocstn committed Oct 7, 2020
1 parent 892e38d commit a7d585e
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
6B33896E23AABEEE00561E5B /* MockReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B33896D23AABEEE00561E5B /* MockReachability.swift */; };
6B33897023AABF1800561E5B /* NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B33896F23AABF1800561E5B /* NetworkReachability.swift */; };
6B33897223AAD94800561E5B /* AWSAPIPlugin+Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B33897123AAD94800561E5B /* AWSAPIPlugin+Reachability.swift */; };
7632AD8A252E1E10009B5BC9 /* AppSyncJSONValue+toJSONValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7632AD89252E1E10009B5BC9 /* AppSyncJSONValue+toJSONValue.swift */; };
9B13EA5E48896E8B38883633 /* Pods_HostApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 930DD773E0FB4047393CA2AD /* Pods_HostApp.framework */; };
A04815BCD5F9181C8AEDEF43 /* Pods_AWSAPICategoryPlugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 881AB4B98B48235DEC7754C2 /* Pods_AWSAPICategoryPlugin.framework */; };
B1F5048F35638D3D142C4F1F /* Pods_AWSAPICategoryPlugin_AWSAPICategoryPluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B13CFC866A30622EDD91AF4 /* Pods_AWSAPICategoryPlugin_AWSAPICategoryPluginTests.framework */; };
Expand Down Expand Up @@ -386,6 +387,7 @@
6B33897123AAD94800561E5B /* AWSAPIPlugin+Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSAPIPlugin+Reachability.swift"; sourceTree = "<group>"; };
6DD6386039136045F18D44AC /* Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithUserPoolIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithUserPoolIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74EDB7008F5342ED4B38C9CA /* Pods_HostApp_AWSAPICategoryPluginIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HostApp_AWSAPICategoryPluginIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7632AD89252E1E10009B5BC9 /* AppSyncJSONValue+toJSONValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppSyncJSONValue+toJSONValue.swift"; sourceTree = "<group>"; };
77792DD821FC754D857FC63C /* Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithIAMIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithIAMIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithIAMIntegrationTests/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithIAMIntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
7866FCFB5807C2D20219CEBE /* Pods-HostApp-AWSAPICategoryPluginTestCommon-RESTWithIAMIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSAPICategoryPluginTestCommon-RESTWithIAMIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSAPICategoryPluginTestCommon-RESTWithIAMIntegrationTests/Pods-HostApp-AWSAPICategoryPluginTestCommon-RESTWithIAMIntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
7A255F655FE0AE43E68F2972 /* Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -820,6 +822,7 @@
21D7A0C6237B54D90057D00D /* Utils */ = {
isa = PBXGroup;
children = (
7632AD89252E1E10009B5BC9 /* AppSyncJSONValue+toJSONValue.swift */,
2129BE3D239486D2006363A1 /* AnyModel+JSONInit.swift */,
21D7A0CC237B54D90057D00D /* APIError+DecodingError.swift */,
21D7A0CF237B54D90057D00D /* GraphQLOperationRequest+Validate.swift */,
Expand Down Expand Up @@ -2168,6 +2171,7 @@
21D38B9B240C517C00EC2A8D /* AWSOIDCAuthProvider.swift in Sources */,
21D7A112237B54D90057D00D /* GraphQLOperationRequestUtils+Validator.swift in Sources */,
21D7A0FC237B54D90057D00D /* URLSessionFactory.swift in Sources */,
7632AD8A252E1E10009B5BC9 /* AppSyncJSONValue+toJSONValue.swift in Sources */,
21D7A103237B54D90057D00D /* URLRequestConstants.swift in Sources */,
21D7A118237B54D90057D00D /* APIKeyURLRequestInterceptor.swift in Sources */,
21D7A116237B54D90057D00D /* AWSAPIPlugin+URLSessionBehaviorDelegate.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ final public class AWSGraphQLSubscriptionOperation<R: Decodable>: GraphQLSubscri
case .data(let data):
onGraphQLResponseData(data)
case .failed(let error):
dispatch(result: .failure(APIError.operationError("subscription item event failed with error", "", error)))
finish()
onSubscriptionFailure(error)
}
}

Expand Down Expand Up @@ -161,4 +160,27 @@ final public class AWSGraphQLSubscriptionOperation<R: Decodable>: GraphQLSubscri
}
}

private func onSubscriptionFailure(_ error: Error) {
let errorDescription = "Subscription item event failed with error"
guard case let ConnectionProviderError.subscription(_, payload) = error,
let errors = payload?["errors"] as? AppSyncJSONValue else {
dispatch(result: .failure(APIError.operationError(errorDescription, "", error)))
finish()
return
}

if let graphQLErrors = try? GraphQLResponseDecoder.decodeAppSyncErrors(errors),
let extensions = graphQLErrors.first?.extensions,
let recoveryMessage = graphQLErrors.first?.message,
case let .string(errorTypeValue) = extensions["errorType"] {
let appSyncError = AppSyncErrorType(errorTypeValue)
dispatch(result: .failure(APIError.operationError(errorDescription, recoveryMessage, appSyncError)))
finish()
return
}

dispatch(result: .failure(APIError.operationError(errorDescription, "", error)))
finish()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
import AppSyncRealTimeClient

extension AppSyncJSONValue {
static func toJSONValue(_ json: AppSyncJSONValue) -> JSONValue {
switch json {
case .array(let values):
return JSONValue.array(values.map(AppSyncJSONValue.toJSONValue))
case .boolean(let value):
return JSONValue.boolean(value)
case .null:
return JSONValue.null
case .number(let value):
return JSONValue.number(value)
case .object(let content):
return JSONValue.object(content.reduce(into: [:]) { acc, partial in
let (key, value) = partial
acc[key] = AppSyncJSONValue.toJSONValue(value)
})
case .string(let value):
return JSONValue.string(value)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import Amplify
import AppSyncRealTimeClient

extension GraphQLResponseDecoder {

Expand All @@ -29,6 +30,20 @@ extension GraphQLResponseDecoder {
return responseErrors
}

static func decodeAppSyncErrors(_ appSyncJSON: AppSyncJSONValue?) throws -> [GraphQLError] {
guard let errorResponse = appSyncJSON else {
throw APIError.unknown("""
Unexpected failure while decoding GraphQL response containing errors:
\(String(describing: appSyncJSON))
""", "", nil)
}
guard case let .array(errors) = errorResponse else {
throw APIError.unknown("Expected 'errors' field not found in \(String(describing: appSyncJSON))", "", nil)
}
let convertedValues = errors.map(AppSyncJSONValue.toJSONValue)
return try GraphQLResponseDecoder.decodeErrors(graphQLErrors: convertedValues)
}

static func decode(graphQLErrorJSON: JSONValue) throws -> GraphQLError {
let serializedJSON = try JSONEncoder().encode(graphQLErrorJSON)
let decoder = JSONDecoder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@
import Foundation

/// Common AppSync error types
public enum AppSyncErrorType: Equatable {
public enum AppSyncErrorType: Equatable, Error {

private static let conditionalCheckFailedErrorString = "ConditionalCheckFailedException"
private static let conflictUnhandledErrorString = "ConflictUnhandled"
private static let unauthorizedErrorString = "Unauthorized"

/// Conflict detection finds a version mismatch and the conflict handler rejects the mutation.
/// See https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html for more information
case conflictUnhandled

case conditionalCheck

case unauthorized

case unknown(String)

public init(_ value: String) {
Expand All @@ -27,6 +30,8 @@ public enum AppSyncErrorType: Equatable {
self = .conditionalCheck
case AppSyncErrorType.conflictUnhandledErrorString:
self = .conflictUnhandled
case AppSyncErrorType.unauthorizedErrorString:
self = .unauthorized
default:
self = .unknown(value)
}
Expand All @@ -38,6 +43,8 @@ public enum AppSyncErrorType: Equatable {
return AppSyncErrorType.conditionalCheckFailedErrorString
case .conflictUnhandled:
return AppSyncErrorType.conflictUnhandledErrorString
case .unauthorized:
return AppSyncErrorType.unauthorizedErrorString
case .unknown(let value):
return value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation {
finish(result: .success(nil))
case .conflictUnhandled:
processConflictUnhandled(extensions)
case .unauthorized:
// TODO: dispatch Hub event
log.debug("Unauthorized mutation \(errorType)")
finish(result: .success(nil))
case .unknown(let errorType):
log.debug("Unhandled error with errorType \(errorType)")
finish(result: .success(nil))
Expand Down

0 comments on commit a7d585e

Please sign in to comment.