Skip to content

Commit

Permalink
fix: Parse and surface returned subscription @auth errors (#810)
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 authored Oct 9, 2020
1 parent 1d90b60 commit 36206ef
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 2 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,19 @@ final public class AWSGraphQLSubscriptionOperation<R: Decodable>: GraphQLSubscri
}
}

private func onSubscriptionFailure(_ error: Error) {
let errorDescription = "Subscription item event failed with error"
if case let ConnectionProviderError.subscription(_, payload) = error,
let errors = payload?["errors"] as? AppSyncJSONValue,
let graphQLErrors = try? GraphQLResponseDecoder.decodeAppSyncErrors(errors) {
let graphQLResponseError = GraphQLResponseError<R>.error(graphQLErrors)
dispatch(result: .failure(APIError.operationError(errorDescription, "", graphQLResponseError)))
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,14 @@ extension GraphQLResponseDecoder {
return responseErrors
}

static func decodeAppSyncErrors(_ appSyncJSON: AppSyncJSONValue) throws -> [GraphQLError] {
guard case let .array(errors) = appSyncJSON 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
7 changes: 7 additions & 0 deletions AmplifyPlugins/Core/AWSPluginsCore/API/AppSyncErrorType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ public enum AppSyncErrorType: Equatable {

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 36206ef

Please sign in to comment.