Skip to content

Commit

Permalink
feat: [API] Merge non-GraphQL spec error fields into GraphQLError.ext…
Browse files Browse the repository at this point in the history
…ensions (#401)

* feat: [API] Merge non-GraphQL spec error fields into GraphQLError.extensions

* add AppSyncErrorType

* clean up mergeExtensions
  • Loading branch information
lawmicha committed Apr 30, 2020
1 parent 64c5ad7 commit b87811c
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 34 deletions.
14 changes: 13 additions & 1 deletion Amplify.xcodeproj/project.pbxproj
Expand Up @@ -92,6 +92,7 @@
219A88ED23F3309800BBC5F2 /* Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A88EC23F3309800BBC5F2 /* Tree.swift */; };
219A88EF23F3358F00BBC5F2 /* TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A88EE23F3358F00BBC5F2 /* TreeTests.swift */; };
219A88F123F3379900BBC5F2 /* GraphQLDocumentInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A88F023F3379900BBC5F2 /* GraphQLDocumentInput.swift */; };
21C395B3245729EC00597EA2 /* AppSyncErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C395B2245729EC00597EA2 /* AppSyncErrorType.swift */; };
21D1CE8C2334233F0003BAA8 /* AuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D1CE8B2334233F0003BAA8 /* AuthError.swift */; };
21D79FDA237617C60057D00D /* SubscriptionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D79FD9237617C60057D00D /* SubscriptionEvent.swift */; };
21D79FE12377BF4B0057D00D /* AuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D79FE02377BF4B0057D00D /* AuthProvider.swift */; };
Expand Down Expand Up @@ -622,6 +623,7 @@
219A88EC23F3309800BBC5F2 /* Tree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tree.swift; sourceTree = "<group>"; };
219A88EE23F3358F00BBC5F2 /* TreeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeTests.swift; sourceTree = "<group>"; };
219A88F023F3379900BBC5F2 /* GraphQLDocumentInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLDocumentInput.swift; sourceTree = "<group>"; };
21C395B2245729EC00597EA2 /* AppSyncErrorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncErrorType.swift; sourceTree = "<group>"; };
21D1CE8B2334233F0003BAA8 /* AuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthError.swift; sourceTree = "<group>"; };
21D79FD9237617C60057D00D /* SubscriptionEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEvent.swift; sourceTree = "<group>"; };
21D79FE02377BF4B0057D00D /* AuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1398,6 +1400,14 @@
path = Error;
sourceTree = "<group>";
};
21C395B4245729F100597EA2 /* API */ = {
isa = PBXGroup;
children = (
21C395B2245729EC00597EA2 /* AppSyncErrorType.swift */,
);
path = API;
sourceTree = "<group>";
};
21FFF999230C96E0005878EA /* Operation */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1977,9 +1987,10 @@
FA131AAB2360FE070008381C /* AWSPluginsCore */ = {
isa = PBXGroup;
children = (
21C395B4245729F100597EA2 /* API */,
FA131ACB2360FE470008381C /* Auth */,
FA131AAC2360FE070008381C /* AWSPluginsCore.h */,
FA131AAD2360FE070008381C /* Info.plist */,
FA131ACB2360FE470008381C /* Auth */,
2129BE0223947FA3006363A1 /* Model */,
6BBECD6F23ADA7C100C8DFBE /* ServiceConfiguration */,
2129BE3F23948909006363A1 /* Sync */,
Expand Down Expand Up @@ -3499,6 +3510,7 @@
21D79FE12377BF4B0057D00D /* AuthProvider.swift in Sources */,
21420AA1237222A900FA140C /* AWSMobileClientBehavior.swift in Sources */,
212CE71123E9EA6A007D8E71 /* ModelField+GraphQL.swift in Sources */,
21C395B3245729EC00597EA2 /* AppSyncErrorType.swift in Sources */,
212CE70523E9E967007D8E71 /* GraphQLQuery.swift in Sources */,
212CE70C23E9E991007D8E71 /* ConflictResolutionDecorator.swift in Sources */,
212CE71323E9F2ED007D8E71 /* DirectiveNameDecorator.swift in Sources */,
Expand Down
13 changes: 13 additions & 0 deletions Amplify/Categories/API/Response/GraphQLError.swift
Expand Up @@ -20,6 +20,19 @@ public struct GraphQLError: Decodable {
/// Additional map of of errors
public let extensions: [String: JSONValue]?

public init(message: String,
locations: [Location]? = nil,
path: [JSONValue]? = nil,
extensions: [String: JSONValue]? = nil) {
self.message = message
self.locations = locations
self.path = path
self.extensions = extensions
}
}

extension GraphQLError {

/// Both `line` and `column` are positive numbers describing the beginning of an associated syntax element
public struct Location: Decodable {
public let line: Int
Expand Down
Expand Up @@ -77,6 +77,8 @@
21D7A118237B54D90057D00D /* APIKeyURLRequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D7A0D5237B54D90057D00D /* APIKeyURLRequestInterceptor.swift */; };
21D7A119237B54D90057D00D /* IAMURLRequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D7A0D6237B54D90057D00D /* IAMURLRequestInterceptor.swift */; };
21D7A11A237B54D90057D00D /* AWSAPICategoryPluginError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D7A0D7237B54D90057D00D /* AWSAPICategoryPluginError.swift */; };
21E2E2282451E66A007D7767 /* GraphQLResponseDecoder+DecodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E2E2272451E66A007D7767 /* GraphQLResponseDecoder+DecodeError.swift */; };
21E2E22A2451E6B5007D7767 /* GraphQLResponseDecoderDecodeErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E2E2292451E6B5007D7767 /* GraphQLResponseDecoderDecodeErrorTests.swift */; };
21F40A2B23A0423C0074678E /* GraphQLSyncBasedTests-amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21F40A2923A0423C0074678E /* GraphQLSyncBasedTests-amplifyconfiguration.json */; };
21F40A2E23A0707E0074678E /* GraphQLModelBasedTests-amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21F40A2D23A0707E0074678E /* GraphQLModelBasedTests-amplifyconfiguration.json */; };
241355B5778C3B2C3826CE96 /* Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithIAMIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D57635393A9898E665C00A1 /* Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithIAMIntegrationTests.framework */; };
Expand Down Expand Up @@ -336,6 +338,8 @@
21D7A0D6237B54D90057D00D /* IAMURLRequestInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IAMURLRequestInterceptor.swift; sourceTree = "<group>"; };
21D7A0D7237B54D90057D00D /* AWSAPICategoryPluginError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSAPICategoryPluginError.swift; sourceTree = "<group>"; };
21D7A0DE237B54D90057D00D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
21E2E2272451E66A007D7767 /* GraphQLResponseDecoder+DecodeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphQLResponseDecoder+DecodeError.swift"; sourceTree = "<group>"; };
21E2E2292451E6B5007D7767 /* GraphQLResponseDecoderDecodeErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLResponseDecoderDecodeErrorTests.swift; sourceTree = "<group>"; };
21F40A2923A0423C0074678E /* GraphQLSyncBasedTests-amplifyconfiguration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "GraphQLSyncBasedTests-amplifyconfiguration.json"; sourceTree = "<group>"; };
21F40A2D23A0707E0074678E /* GraphQLModelBasedTests-amplifyconfiguration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "GraphQLModelBasedTests-amplifyconfiguration.json"; sourceTree = "<group>"; };
226F79D02FF47C0A8AE75467 /* Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests/Pods-HostApp-AWSAPICategoryPluginTestCommon-GraphQLWithUserPoolIntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -795,6 +799,7 @@
21D7A0CB237B54D90057D00D /* GraphQLOperationRequestUtils.swift */,
21D7A0CD237B54D90057D00D /* GraphQLOperationRequestUtils+Validator.swift */,
21D7A0CE237B54D90057D00D /* GraphQLResponseDecoder.swift */,
21E2E2272451E66A007D7767 /* GraphQLResponseDecoder+DecodeError.swift */,
21409C5F2384DF17000A53C9 /* RESTOperationRequest+RESTRequest.swift */,
21D7A0C7237B54D90057D00D /* RESTOperationRequest+Validate.swift */,
21D7A0C8237B54D90057D00D /* RESTOperationRequestUtils.swift */,
Expand Down Expand Up @@ -968,6 +973,7 @@
children = (
B4DFA5D0237A611D0013E17B /* GraphQLRequestUtils+ValidatorTests.swift */,
B4DFA5D3237A611D0013E17B /* GraphQLRequestUtilsTests.swift */,
21E2E2292451E6B5007D7767 /* GraphQLResponseDecoderDecodeErrorTests.swift */,
B4DFA5D1237A611D0013E17B /* GraphQLResponseDecoderTests.swift */,
B4DFA5D4237A611D0013E17B /* RESTRequestUtils+ValidatorTests.swift */,
B4DFA5D2237A611D0013E17B /* RESTRequestUtilsTests.swift */,
Expand Down Expand Up @@ -2093,6 +2099,7 @@
buildActionMask = 2147483647;
files = (
21D7A102237B54D90057D00D /* URLSessionBehaviorDelegate.swift in Sources */,
21E2E2282451E66A007D7767 /* GraphQLResponseDecoder+DecodeError.swift in Sources */,
21D7A0FD237B54D90057D00D /* URLSession+URLSessionBehavior.swift in Sources */,
21D7A113237B54D90057D00D /* GraphQLResponseDecoder.swift in Sources */,
21D7A0FF237B54D90057D00D /* URLSessionBehavior.swift in Sources */,
Expand Down Expand Up @@ -2165,6 +2172,7 @@
6B2E465A23AAA6AF0066EDCE /* NetworkReachabilityNotifierTests.swift in Sources */,
B4DFA5E1237A611D0013E17B /* MockURLSessionTask.swift in Sources */,
B4DFA5F8237A611D0013E17B /* AWSAPICategoryPlugin+ConfigureTests.swift in Sources */,
21E2E22A2451E6B5007D7767 /* GraphQLResponseDecoderDecodeErrorTests.swift in Sources */,
B4DFA5F1237A611D0013E17B /* AWSAPICategoryPlugin+URLSessionBehaviorDelegateTests.swift in Sources */,
B4DFA5E7237A611D0013E17B /* AWSAPICategoryPlugin+InterceptorBehaviorTests.swift in Sources */,
6B33896E23AABEEE00561E5B /* MockReachability.swift in Sources */,
Expand Down
@@ -0,0 +1,68 @@
//
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify

extension GraphQLResponseDecoder {

static func decodeErrors(graphQLErrors: [JSONValue]) throws -> [GraphQLError] {
var responseErrors = [GraphQLError]()
for error in graphQLErrors {
do {
let responseError = try decode(graphQLErrorJSON: error)
responseErrors.append(responseError)
} catch let decodingError as DecodingError {
throw APIError(error: decodingError)
} catch {
throw APIError.unknown("""
Unexpected failure while decoding GraphQL response containing errors:
\(String(describing: graphQLErrors))
""", "", error)
}
}

return responseErrors
}

static func decode(graphQLErrorJSON: JSONValue) throws -> GraphQLError {
let serializedJSON = try JSONEncoder().encode(graphQLErrorJSON)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = ModelDateFormatting.decodingStrategy
let graphQLError = try decoder.decode(GraphQLError.self, from: serializedJSON)
return mergeExtensions(from: graphQLErrorJSON, graphQLError: graphQLError)
}

/// Merge fields which are not in the generic GraphQL error json over into the `GraphQLError.extensions`
/// This is the opinionated implementation of the plugin to store service errors which do not conform to the
/// GraphQL Error spec (https://spec.graphql.org/June2018/#sec-Errors)
private static func mergeExtensions(from graphQLErrorJSON: JSONValue, graphQLError: GraphQLError) -> GraphQLError {
var keys = ["message", "locations", "path", "extensions"]
var mergedExtensions = [String: JSONValue]()
if let graphQLErrorExtensions = graphQLError.extensions {
mergedExtensions = graphQLErrorExtensions
keys += mergedExtensions.keys
}

guard case let .object(graphQLErrorObject) = graphQLErrorJSON else {
return graphQLError
}

graphQLErrorObject.forEach { key, value in
if keys.contains(key) {
return
}

mergedExtensions[key] = value
}

return GraphQLError(message: graphQLError.message,
locations: graphQLError.locations,
path: graphQLError.path,
extensions: mergedExtensions.isEmpty ? nil : mergedExtensions)
}
}
Expand Up @@ -165,29 +165,6 @@ struct GraphQLResponseDecoder {
return try decoder.decode(responseType, from: serializedJSON)
}

private static func decodeErrors(graphQLErrors: [JSONValue]) throws -> [GraphQLError] {
var responseErrors = [GraphQLError]()
for error in graphQLErrors {
do {
let responseError = try decode(graphQLError: error)
responseErrors.append(responseError)
} catch let decodingError as DecodingError {
throw APIError(error: decodingError)
} catch {
throw APIError.operationError("", "", error)
}
}

return responseErrors
}

private static func decode(graphQLError: JSONValue) throws -> GraphQLError {
let serializedJSON = try JSONEncoder().encode(graphQLError)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = ModelDateFormatting.decodingStrategy
return try decoder.decode(GraphQLError.self, from: serializedJSON)
}

private static func serialize(graphQLData: JSONValue,
at decodePath: String?) throws -> Data {
let modelJSON = try getModelJSONValue(from: graphQLData, at: decodePath)
Expand Down

0 comments on commit b87811c

Please sign in to comment.