Skip to content

Commit 69b5b0b

Browse files
committed
add SQL and GraphQL tests for PostComment4V2
1 parent 3be957f commit 69b5b0b

File tree

30 files changed

+1478
-1511
lines changed

30 files changed

+1478
-1511
lines changed

Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ public protocol ModelProvider {
4141
}
4242

4343
public enum ModelProviderState<Element: Model> {
44-
case notLoaded(identifier: String)
44+
case notLoaded(identifiers: [String: String])
4545
case loaded(Element?)
4646
}

AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListDecoder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public struct AppSyncListDecoder: ModelListDecoder {
5656

5757
public struct AppSyncModelDecoder: ModelProviderDecoder {
5858
public static func shouldDecode<ModelType: Model>(modelType: ModelType.Type, decoder: Decoder) -> Bool {
59-
if (try? AppSyncPartialModelMetadata(from: decoder)) != nil {
59+
if (try? AppSyncModelIdentifierMetadata(from: decoder)) != nil {
6060
return true
6161
}
6262

@@ -80,7 +80,7 @@ public struct AppSyncModelDecoder: ModelProviderDecoder {
8080
decoder: Decoder) throws -> AppSyncModelProvider<ModelType>? {
8181
if let model = try? ModelType.init(from: decoder) {
8282
return AppSyncModelProvider(model: model)
83-
} else if let metadata = try? AppSyncPartialModelMetadata.init(from: decoder) {
83+
} else if let metadata = try? AppSyncModelIdentifierMetadata.init(from: decoder) {
8484
return AppSyncModelProvider<ModelType>(metadata: metadata)
8585
}
8686
let json = try JSONValue(from: decoder)

AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ public class AppSyncModelProvider<ModelType: Model>: ModelProvider {
1414
let apiName: String?
1515

1616
enum LoadedState {
17-
case notLoaded(identifier: String)
17+
case notLoaded(identifiers: [String: String])
1818
case loaded(model: ModelType?)
1919
}
2020

2121
var loadedState: LoadedState
2222

2323
// init(AppSyncModelMetadata) creates a notLoaded provider
24-
convenience init(metadata: AppSyncPartialModelMetadata) {
25-
self.init(identifier: metadata.identifier,
24+
convenience init(metadata: AppSyncModelIdentifierMetadata) {
25+
self.init(identifiers: metadata.identifiers,
2626
apiName: metadata.apiName)
2727
}
2828

@@ -33,8 +33,8 @@ public class AppSyncModelProvider<ModelType: Model>: ModelProvider {
3333
}
3434

3535
// Initializer for not loaded state
36-
init(identifier: String, apiName: String? = nil) {
37-
self.loadedState = .notLoaded(identifier: identifier)
36+
init(identifiers: [String: String], apiName: String? = nil) {
37+
self.loadedState = .notLoaded(identifiers: identifiers)
3838
self.apiName = apiName
3939
}
4040

@@ -44,11 +44,15 @@ public class AppSyncModelProvider<ModelType: Model>: ModelProvider {
4444
public func load() async throws -> ModelType? {
4545

4646
switch loadedState {
47-
case .notLoaded(let identifier):
47+
case .notLoaded(let identifiers):
48+
// TODO: account for more than one identifier
49+
guard let identifier = identifiers.first else {
50+
throw CoreError.operation("CPK not yet implemented", "", nil)
51+
}
4852
let request = GraphQLRequest<ModelType?>.getQuery(responseType: ModelType.self,
49-
modelSchema: ModelType.schema,
50-
identifier: identifier,
51-
apiName: apiName)
53+
modelSchema: ModelType.schema,
54+
identifier: identifier.value,
55+
apiName: apiName)
5256
do {
5357
let graphQLResponse = try await Amplify.API.query(request: request)
5458
switch graphQLResponse {
@@ -76,8 +80,8 @@ public class AppSyncModelProvider<ModelType: Model>: ModelProvider {
7680

7781
public func getState() -> ModelProviderState<ModelType> {
7882
switch loadedState {
79-
case .notLoaded(let identifier):
80-
return .notLoaded(identifier: identifier)
83+
case .notLoaded(let identifiers):
84+
return .notLoaded(identifiers: identifiers)
8185
case .loaded(let model):
8286
return .loaded(model)
8387
}

AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelMetadata.swift

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ public struct AppSyncModelMetadata: Codable {
1616
}
1717

1818
/// Metadata that contains partial information of a model
19-
public struct AppSyncPartialModelMetadata: Codable {
20-
let identifier: String
19+
// TODO this should expand to more than just the identifier for composite keys.
20+
public struct AppSyncModelIdentifierMetadata: Codable {
21+
let identifiers: [String: String]
2122
let apiName: String?
2223
}
2324

@@ -28,7 +29,7 @@ public struct AppSyncModelMetadataUtils {
2829
// It needs to have the Model type from `__typename` so it can populate it as "associatedField"
2930
//
3031
// This check is currently broken for CPK use cases since the identifier may not be named `id` anymore
31-
// and also can be a composite key made up of multiple fields.
32+
// and can be a composite key made up of multiple fields.
3233
static func shouldAddMetadata(toModel graphQLData: JSONValue) -> Bool {
3334
guard case let .object(modelJSON) = graphQLData,
3435
case let .string(modelName) = modelJSON["__typename"],
@@ -81,17 +82,29 @@ public struct AppSyncModelMetadataUtils {
8182
encoder.dateEncodingStrategy = ModelDateFormatting.encodingStrategy
8283
let decoder = JSONDecoder()
8384
decoder.dateDecodingStrategy = ModelDateFormatting.decodingStrategy
84-
// Iterate over the associations of the model and for each association, store its association data when
85-
// the object at the association is empty. For example, if the modelType is a Post and has a field that is an
86-
// array association like Comment, store the post's identifier and the ModelField name of the parent, ie.
87-
// "post" in the comments object as metadata.
85+
86+
// Iterate over the associations of the model and for each association, either create the identifier metadata
87+
// for lazy loading belongs-to or create the model association metadata for lazy loading has-many.
88+
// The metadata gets decoded to the LazyModel and List implementations respectively.
8889
for modelField in modelSchema.fields.values {
8990

91+
// Handle Belongs-to associations. For the current `modelField` that is a belongs-to association,
92+
// retrieve the data and attempt to decode to the association's modelType. If it can be decoded,
93+
// this means it is eager loaded and does not need to be lazy loaded. If it cannot, extract the
94+
// identifiers out of the data in the AppSyncModelIdentifierMetadata and store that in place for
95+
// the LazyModel to decode from.
9096
if !modelField.isArray && modelField.hasAssociation,
9197
let nestedModelJSON = modelJSON[modelField.name],
92-
let partialModelMetadata = isPartialModel(nestedModelJSON, apiName: apiName) {
93-
94-
if let serializedMetadata = try? encoder.encode(partialModelMetadata),
98+
case .object(let modelObject) = nestedModelJSON,
99+
let associatedModelName = modelField.associatedModelName,
100+
let associatedModelType = ModelRegistry.modelType(from: associatedModelName),
101+
let serializedModelObject = try? encoder.encode(modelObject),
102+
!((try? decoder.decode(associatedModelType.self, from: serializedModelObject)) != nil),
103+
let modelIdentifierMetadata = containsOnlyIdentifiers(associatedModelType,
104+
modelObject: modelObject,
105+
apiName: apiName) {
106+
107+
if let serializedMetadata = try? encoder.encode(modelIdentifierMetadata),
95108
let metadataJSON = try? decoder.decode(JSONValue.self, from: serializedMetadata) {
96109
modelJSON.updateValue(metadataJSON, forKey: modelField.name)
97110
} else {
@@ -101,6 +114,11 @@ public struct AppSyncModelMetadataUtils {
101114
}
102115
}
103116

117+
// Handle Has-many. Store the association data (parent's identifier and field name) only when the model
118+
// at the association is empty. If it's not empty, that means the has-many has been eager loaded.
119+
// For example, when traversing the Post's fields and encounters the has-many association Comment, store
120+
// the association metadata containing the post's identifier at comment, to be decoded to the List
121+
// This allows the list to perform lazy loading of the Comments with a filter on the post's identifier.
104122
if modelField.isArray && modelField.hasAssociation,
105123
let associatedField = modelField.associatedField,
106124
modelJSON[modelField.name] == nil {
@@ -121,24 +139,25 @@ public struct AppSyncModelMetadataUtils {
121139
return JSONValue.object(modelJSON)
122140
}
123141

124-
// A partial model is when only the values of the identifier of the model exists, and nothing else.
125-
// Traverse of the primary keys of the model, and check if there's exactly those values exists.
126-
// This means that the model are missing required/optional fields that are not the identifier of the model.
127-
// TODO: This code needs to account for CPK.
128-
static func isPartialModel(_ modelJSON: JSONValue, apiName: String?) -> AppSyncPartialModelMetadata? {
129-
guard case .object(let modelObject) = modelJSON else {
130-
return nil
131-
}
142+
// At this point, we know the model cannot be decoded to the fully model
143+
// so extract the primary key and values out.
144+
static func containsOnlyIdentifiers(_ associatedModel: Model.Type,
145+
modelObject: [String: JSONValue],
146+
apiName: String?) -> AppSyncModelIdentifierMetadata? {
147+
let primarykeys = associatedModel.schema.primaryKey
148+
print("primaryKeys \(primarykeys)")
132149

133-
// TODO: This should be based on the number of fields that make up the identifier + __typename
134-
guard modelObject.count == 2 else {
135-
return nil
150+
var identifiers = [String: String]()
151+
for identifierField in primarykeys.fields {
152+
if case .string(let id) = modelObject[identifierField.name] {
153+
print("Found key value \(identifierField.name) value: \(id)")
154+
identifiers.updateValue(id, forKey: identifierField.name)
155+
}
136156
}
137-
138-
if case .string(let id) = modelObject["id"] {
139-
return AppSyncPartialModelMetadata(identifier: id, apiName: apiName)
157+
if !identifiers.isEmpty {
158+
return AppSyncModelIdentifierMetadata(identifiers: identifiers, apiName: apiName)
159+
} else {
160+
return nil
140161
}
141-
142-
return nil
143162
}
144163
}

AmplifyPlugins/API/Sources/AWSAPIPlugin/Support/Decode/GraphQLResponseDecoder+DecodeData.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension GraphQLResponseDecoder {
4040
if request.responseType == AnyModel.self { // 2
4141
let anyModel = try AnyModel(modelJSON: graphQLData)
4242
serializedJSON = try encoder.encode(anyModel)
43-
} else if request.responseType is ModelListMarker.Type, // 2
43+
} else if request.responseType is ModelListMarker.Type, // 3
4444
case .object(var graphQLDataObject) = graphQLData,
4545
case .array(var graphQLDataArray) = graphQLDataObject["items"] {
4646
for (index, item) in graphQLDataArray.enumerated() {

AmplifyPlugins/API/Tests/APIHostApp/APIHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)