Skip to content

fix(datastore): Various integration bug fixes #4954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class GraphQLResponseDecodeTests: XCTestCase {
"a": ["b"]
]
let expectedJson = "{\"a\":[\"b\"]}"
let result: Result<String, APIError> = GraphQLResponse<String>.decodeDataPayload(json, modelName: nil)
let result: Result<String, APIError> = GraphQLResponse<String>.decodeDataPayload(json, modelName: "Post")
XCTAssertNoThrow(try result.get())
XCTAssertEqual(expectedJson, try result.get())
}
Expand All @@ -137,7 +137,7 @@ class GraphQLResponseDecodeTests: XCTestCase {
"author": "authorId",
], modelName: "Post"))

let result: Result<AnyModel, APIError> = GraphQLResponse<AnyModel>.decodeDataPayload(json, modelName: nil)
let result: Result<AnyModel, APIError> = GraphQLResponse<AnyModel>.decodeDataPayload(json, modelName: "Post")
XCTAssertNoThrow(try result.get())
XCTAssertEqual(expectedModel.modelName, try result.get().modelName)
XCTAssertEqual(expectedModel.id, try result.get().id)
Expand Down Expand Up @@ -227,7 +227,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
"author": "authorId",
], modelName: "Post"))

let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(json: json, decodePath: "onCreatePost", modelName: nil)
let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(
json: json,
decodePath: "onCreatePost",
modelName: "Post"
)
XCTAssertNoThrow(try result.get())
let mutationSync = try! result.get()
XCTAssertNoThrow(try mutationSync.get())
Expand All @@ -244,7 +248,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
]
]

let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(json: json, decodePath: "onCreatePost", modelName: nil)
let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(
json: json,
decodePath: "onCreatePost",
modelName: "Post"
)
XCTAssertNoThrow(try result.get())
let mutationSync = try! result.get()
XCTAssertThrowsError(try mutationSync.get()) { error in
Expand Down Expand Up @@ -283,7 +291,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
"author": "authorId",
], modelName: "Post"))

let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(json: json, decodePath: "onCreatePost", modelName: nil)
let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(
json: json,
decodePath: "onCreatePost",
modelName: "Post"
)
XCTAssertNoThrow(try result.get())
let mutationSync = try! result.get()
XCTAssertThrowsError(try mutationSync.get()) { error in
Expand All @@ -305,7 +317,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
"a": "b"
]

let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(json: json, decodePath: "onCreatePost", modelName: nil)
let result: Result<GraphQLResponse<MutationSync<AnyModel>>, APIError> = .fromAppSyncResponse(
json: json,
decodePath: "onCreatePost",
modelName: "Post"
)
XCTAssertThrowsError(try result.get()) { error in
if case .unknown(let description, _, _) = (error as! APIError) {
XCTAssertEqual("Failed to get data object or errors from GraphQL response", description)
Expand All @@ -325,7 +341,7 @@ class GraphQLResponseDecodeTests: XCTestCase {
]

let jsonString = String(data: try! JSONEncoder().encode(json), encoding: .utf8)!
let response: GraphQLResponse<AnyModel> = .fromAppSyncResponse(string: jsonString, decodePath: nil)
let response: GraphQLResponse<AnyModel> = .fromAppSyncResponse(string: jsonString, decodePath: nil, modelName: "Post")
XCTAssertNoThrow(try response.get())
XCTAssertEqual(json.id?.stringValue, try response.get().identifier)
XCTAssertEqual(json.__typename?.stringValue, try response.get().modelName)
Expand All @@ -334,7 +350,7 @@ class GraphQLResponseDecodeTests: XCTestCase {
func testFromAppSyncResponse_withBrokenJsonString_failWithTransformationError() {
SchemaData.modelSchemaRegistry.registerModels(registry: ModelRegistry.self)
let jsonString = "{"
let response: GraphQLResponse<AnyModel> = .fromAppSyncResponse(string: jsonString, decodePath: nil)
let response: GraphQLResponse<AnyModel> = .fromAppSyncResponse(string: jsonString, decodePath: nil, modelName: "Post")
XCTAssertThrowsError(try response.get()) { error in
guard case .transformationError = error as! GraphQLResponseError<AnyModel> else {
XCTFail("Should failed with transformationError")
Expand All @@ -358,7 +374,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
]

let jsonString = String(data: try! JSONEncoder().encode(payloadJson), encoding: .utf8)!
let response: GraphQLResponse<MutationSync<AnyModel>> = .fromAppSyncResponse(string: jsonString, decodePath: "onCreatePost")
let response: GraphQLResponse<MutationSync<AnyModel>> = .fromAppSyncResponse(
string: jsonString,
decodePath: "onCreatePost",
modelName: "Post"
)
XCTAssertNoThrow(try response.get())
let mutationSync = try! response.get()
XCTAssertEqual(payloadJson.onCreatePost?.id?.stringValue, mutationSync.model.identifier)
Expand All @@ -368,7 +388,11 @@ class GraphQLResponseDecodeTests: XCTestCase {
func testFromAppSyncSubscriptionResponse_withWrongJsonString_failWithTransformationError() {
SchemaData.modelSchemaRegistry.registerModels(registry: ModelRegistry.self)
let jsonString = "{"
let response: GraphQLResponse<MutationSync<AnyModel>> = .fromAppSyncSubscriptionResponse(string: jsonString, decodePath: nil)
let response: GraphQLResponse<MutationSync<AnyModel>> = .fromAppSyncSubscriptionResponse(
string: jsonString,
decodePath: nil,
modelName: "Post"
)
XCTAssertThrowsError(try response.get()) { error in
guard case .transformationError = error as! GraphQLResponseError<MutationSync<AnyModel>> else {
XCTFail("Should failed with transformationError")
Expand Down
14 changes: 12 additions & 2 deletions packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ public class FlutterApiPlugin: APICategoryPlugin
guard let payload else {
throw DataStoreError.decodingError("Request payload could not be empty", "")
}

guard let datastoreOptions = request.options?.pluginOptions as? AWSAPIPluginDataStoreOptions else {
throw DataStoreError.decodingError("Failed to decode the GraphQLRequest due to a missing options field.", "")
}

return GraphQLResponse<R>.fromAppSyncResponse(
string: payload,
decodePath: request.decodePath
decodePath: request.decodePath,
modelName: datastoreOptions.modelName
)
}

Expand All @@ -113,10 +118,15 @@ public class FlutterApiPlugin: APICategoryPlugin
guard let payload else {
throw DataStoreError.decodingError("Request payload could not be empty", "")
}

guard let datastoreOptions = request.options?.pluginOptions as? AWSAPIPluginDataStoreOptions else {
throw DataStoreError.decodingError("Failed to decode the GraphQLRequest due to a missing options field.", "")
}

return GraphQLResponse<R>.fromAppSyncSubscriptionResponse(
string: payload,
decodePath: request.decodePath
decodePath: request.decodePath,
modelName: datastoreOptions.modelName
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ extension GraphQLRequest {
let variablesJson = self.variables
.flatMap { try? JSONSerialization.data(withJSONObject: $0, options: []) }
.flatMap { String(data: $0, encoding: .utf8) }

let datastoreOptions = self.options?.pluginOptions as? AWSAPIPluginDataStoreOptions

return NativeGraphQLRequest(
document: self.document,
apiName: self.apiName,
variablesJson: variablesJson ?? "{}",
responseType: String(describing: self.responseType),
decodePath: self.decodePath
decodePath: self.decodePath,
authMode: datastoreOptions?.authType.map { String(describing: $0) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension GraphQLResponse {
public static func fromAppSyncResponse<R: Decodable>(
string: String,
decodePath: String?,
modelName: String? = nil
modelName: String
) -> GraphQLResponse<R> {
guard let data = string.data(using: .utf8) else {
return .failure(.transformationError(
Expand All @@ -44,7 +44,7 @@ extension GraphQLResponse {
public static func fromAppSyncSubscriptionResponse<R: Decodable>(
string: String,
decodePath: String?,
modelName: String? = nil
modelName: String
) -> GraphQLResponse<R> {
guard let data = string.data(using: .utf8) else {
return .failure(.transformationError(
Expand Down Expand Up @@ -98,7 +98,7 @@ extension GraphQLResponse {
static func fromAppSyncResponse<R: Decodable>(
json: JSONValue,
decodePath: String?,
modelName: String?
modelName: String
) -> Result<GraphQLResponse<R>, APIError> {
let data = decodePath != nil ? json.value(at: decodePath!) : json
let errors = json.errors?.asArray
Expand Down Expand Up @@ -147,17 +147,17 @@ extension GraphQLResponse {

static func decodeDataPayload<R: Decodable>(
_ dataPayload: JSONValue,
modelName: String?
modelName: String
) -> Result<R, APIError> {
if R.self == String.self {
return encodeDataPayloadToString(dataPayload).map { $0 as! R }
}

let dataPayloadWithTypeName = modelName.flatMap {
dataPayload.asObject?.merging(
["__typename": .string($0)]
) { a, _ in a }
}.map { JSONValue.object($0) } ?? dataPayload
/// This is included to allow multi-platform support. Requests that do not have `__typename`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Was there supposed to be more to this comment? I am not following the second sentence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yah reads like a fragment 😅 Updated.

let dataPayloadWithTypeName = (dataPayload.asObject?.merging(
["__typename": .string(modelName)],
uniquingKeysWith: { a, _ in a }
)).map { JSONValue.object($0) } ?? dataPayload

if R.self == AnyModel.self {
return decodeDataPayloadToAnyModel(dataPayloadWithTypeName).map { $0 as! R }
Expand All @@ -174,16 +174,16 @@ extension GraphQLResponse {
_ dataPayload: JSONValue
) -> Result<AnyModel, APIError> {
guard let typeName = dataPayload.__typename?.stringValue else {
return .failure(.operationError(
"Could not retrieve __typename from object",
"""
Could not retrieve the `__typename` attribute from the return value. Be sure to include __typename in \
the selection set of the GraphQL operation. GraphQL:
\(dataPayload)
"""
))
}

return .failure(.operationError(
"Could not retrieve __typename from object",
"""
Could not retrieve the `__typename` attribute from the return value. Be sure to include __typename in \
the selection set of the GraphQL operation. GraphQL:
\(dataPayload)
"""
))
}
return encodeDataPayloadToString(dataPayload).flatMap { underlyingModelString in
do {
return .success(.init(try ModelRegistry.decode(
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions packages/amplify_datastore/lib/amplify_datastore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,13 @@ class _NativeAmplifyApi

final subscription = operation.listen(
(GraphQLResponse<String> event) =>
sendNativeDataEvent(flutterRequest.id, event.data),
onError: (error) {
// TODO(equartey): verify that error.toString() is the correct payload format. Should match AppSync
final errorPayload = error.toString();
sendSubscriptionEvent(flutterRequest.id, event),
onError: (Object error) {
final errorPayload = jsonEncode({
'errors': [
{'message': error.toString()}
]
});
sendNativeErrorEvent(flutterRequest.id, errorPayload);
},
onDone: () => sendNativeCompleteEvent(flutterRequest.id));
Expand Down
5 changes: 5 additions & 0 deletions packages/amplify_datastore/lib/src/native_plugin.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading