Skip to content

Commit 9f9d658

Browse files
authored
fix(api): selection set with optional association (#509)
1 parent a29bb23 commit 9f9d658

File tree

11 files changed

+422
-4
lines changed

11 files changed

+422
-4
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
2144226E234BDE23009357F7 /* StorageUploadFileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2144226D234BDE23009357F7 /* StorageUploadFileOperation.swift */; };
7272
214F49CD24898E8500DA616C /* Article.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49CB24898E8400DA616C /* Article.swift */; };
7373
214F49CE24898E8500DA616C /* Article+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49CC24898E8500DA616C /* Article+Schema.swift */; };
74+
214F49772486D8A200DA616C /* UserFollowers+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49712486D8A100DA616C /* UserFollowers+Schema.swift */; };
75+
214F49782486D8A200DA616C /* UserFollowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49722486D8A200DA616C /* UserFollowing.swift */; };
76+
214F49792486D8A200DA616C /* UserFollowing+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49732486D8A200DA616C /* UserFollowing+Schema.swift */; };
77+
214F497A2486D8A200DA616C /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49742486D8A200DA616C /* User.swift */; };
78+
214F497B2486D8A200DA616C /* User+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49752486D8A200DA616C /* User+Schema.swift */; };
79+
214F497C2486D8A200DA616C /* UserFollowers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F49762486D8A200DA616C /* UserFollowers.swift */; };
80+
214F497E2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214F497D2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift */; };
7481
21558E3E237BB4BF0032A5BB /* GraphQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21558E3D237BB4BF0032A5BB /* GraphQLRequest.swift */; };
7582
21558E40237CB8640032A5BB /* GraphQLError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21558E3F237CB8640032A5BB /* GraphQLError.swift */; };
7683
216879FE23636A0A004A056E /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216879FD23636A0A004A056E /* RepeatingTimer.swift */; };
@@ -707,6 +714,13 @@
707714
2144226D234BDE23009357F7 /* StorageUploadFileOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageUploadFileOperation.swift; sourceTree = "<group>"; };
708715
214F49CB24898E8400DA616C /* Article.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Article.swift; sourceTree = "<group>"; };
709716
214F49CC24898E8500DA616C /* Article+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Schema.swift"; sourceTree = "<group>"; };
717+
214F49712486D8A100DA616C /* UserFollowers+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserFollowers+Schema.swift"; sourceTree = "<group>"; };
718+
214F49722486D8A200DA616C /* UserFollowing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFollowing.swift; sourceTree = "<group>"; };
719+
214F49732486D8A200DA616C /* UserFollowing+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserFollowing+Schema.swift"; sourceTree = "<group>"; };
720+
214F49742486D8A200DA616C /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
721+
214F49752486D8A200DA616C /* User+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "User+Schema.swift"; sourceTree = "<group>"; };
722+
214F49762486D8A200DA616C /* UserFollowers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFollowers.swift; sourceTree = "<group>"; };
723+
214F497D2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequestOptionalAssociationTests.swift; sourceTree = "<group>"; };
710724
21558E3D237BB4BF0032A5BB /* GraphQLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequest.swift; sourceTree = "<group>"; };
711725
21558E3F237CB8640032A5BB /* GraphQLError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLError.swift; sourceTree = "<group>"; };
712726
215F4BCAAB89FA54AA121BDE /* Pods-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin.release.xcconfig"; path = "Target Support Files/Pods-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin/Pods-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin.release.xcconfig"; sourceTree = "<group>"; };
@@ -1503,6 +1517,7 @@
15031517
2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */,
15041518
219A888623EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift */,
15051519
21A3FDB8246494CD00E76120 /* GraphQLRequestAuthRuleTests.swift */,
1520+
214F497D2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift */,
15061521
);
15071522
path = GraphQLRequest;
15081523
sourceTree = "<group>";
@@ -2185,18 +2200,24 @@
21852200
B952182D237E21B900F53237 /* Models */ = {
21862201
isa = PBXGroup;
21872202
children = (
2188-
B952182F237E21B900F53237 /* schema.graphql */,
21892203
FAF512AD23986791001ADF4E /* AmplifyModels.swift */,
21902204
214F49CB24898E8400DA616C /* Article.swift */,
21912205
214F49CC24898E8500DA616C /* Article+Schema.swift */,
2206+
B9FAA10C23878BD6009414B4 /* Associations */,
21922207
B9521830237E21B900F53237 /* Comment.swift */,
21932208
B952182E237E21B900F53237 /* Comment+Schema.swift */,
21942209
FAA2E8BB239FFC7700E420EA /* MockModels.swift */,
21952210
B9521832237E21B900F53237 /* Post.swift */,
21962211
B9521831237E21B900F53237 /* Post+Schema.swift */,
21972212
2129BE002394627B006363A1 /* PostCommentModelRegistration.swift */,
21982213
B9AA09F02473CA29000E6FBB /* PostStatus.swift */,
2199-
B9FAA10C23878BD6009414B4 /* Associations */,
2214+
B952182F237E21B900F53237 /* schema.graphql */,
2215+
214F49742486D8A200DA616C /* User.swift */,
2216+
214F49752486D8A200DA616C /* User+Schema.swift */,
2217+
214F49762486D8A200DA616C /* UserFollowers.swift */,
2218+
214F49712486D8A100DA616C /* UserFollowers+Schema.swift */,
2219+
214F49722486D8A200DA616C /* UserFollowing.swift */,
2220+
214F49732486D8A200DA616C /* UserFollowing+Schema.swift */,
22002221
);
22012222
path = Models;
22022223
sourceTree = "<group>";
@@ -3961,6 +3982,7 @@
39613982
2183A56823EA4A8E00232880 /* GraphQLDeleteMutationTests.swift in Sources */,
39623983
21A3FDB42463C49F00E76120 /* ModelReadUpdateAuthRuleTests.swift in Sources */,
39633984
2183A56323EA4A7800232880 /* GraphQLSubscriptionTests.swift in Sources */,
3985+
214F497E2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift in Sources */,
39643986
2129BE562395CAF9006363A1 /* PaginatedListTests.swift in Sources */,
39653987
2183A56723EA4A8B00232880 /* GraphQLCreateMutationTests.swift in Sources */,
39663988
2183A56623EA4A8700232880 /* GraphQLSyncQueryTests.swift in Sources */,
@@ -4418,20 +4440,26 @@
44184440
B9FAA11823879A57009414B4 /* Author+Schema.swift in Sources */,
44194441
B9521836237E21BA00F53237 /* Post+Schema.swift in Sources */,
44204442
FA4A955F239ADEBD008E876E /* MockResponder.swift in Sources */,
4443+
214F497A2486D8A200DA616C /* User.swift in Sources */,
44214444
B9FAA11223878C96009414B4 /* UserAccount+Schema.swift in Sources */,
44224445
FACA36152327FC39000E74F6 /* MessageReporter.swift in Sources */,
44234446
FAF512AE23986791001ADF4E /* AmplifyModels.swift in Sources */,
44244447
B9FAA11C23879B35009414B4 /* Book.swift in Sources */,
4448+
214F49772486D8A200DA616C /* UserFollowers+Schema.swift in Sources */,
44254449
B9FAA11423878CEA009414B4 /* UserProfile+Schema.swift in Sources */,
4450+
214F49792486D8A200DA616C /* UserFollowing+Schema.swift in Sources */,
44264451
FACA361D2327FC84000E74F6 /* MockAPICategoryPlugin.swift in Sources */,
44274452
B9FAA11023878C5E009414B4 /* UserProfile.swift in Sources */,
44284453
B9AA09F12473CA29000E6FBB /* PostStatus.swift in Sources */,
4454+
214F497B2486D8A200DA616C /* User+Schema.swift in Sources */,
44294455
B9FAA12023879BD0009414B4 /* BookAuthor+Schema.swift in Sources */,
44304456
21F40A4023A295470074678E /* TestCommonConstants.swift in Sources */,
4457+
214F497C2486D8A200DA616C /* UserFollowers.swift in Sources */,
44314458
B9521835237E21BA00F53237 /* Comment.swift in Sources */,
44324459
FACA361E2327FC8E000E74F6 /* MockAnalyticsCategoryPlugin.swift in Sources */,
44334460
2129BE012394627B006363A1 /* PostCommentModelRegistration.swift in Sources */,
44344461
B9FAA10E23878BF3009414B4 /* UserAccount.swift in Sources */,
4462+
214F49782486D8A200DA616C /* UserFollowing.swift in Sources */,
44354463
21F40A3C23A2952C0074678E /* AuthHelper.swift in Sources */,
44364464
B4F3E9FA24314ECC00F23296 /* MockAuthCategoryPlugin.swift in Sources */,
44374465
214F49CE24898E8500DA616C /* Article+Schema.swift in Sources */,

AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ extension SelectionSet {
3535

3636
func withModelFields(_ fields: [ModelField]) {
3737
fields.forEach { field in
38-
let isRequiredAssociation = field.isRequired && field.isAssociationOwner
39-
if isRequiredAssociation, let associatedModel = field.associatedModel {
38+
if field.isAssociationOwner, let associatedModel = field.associatedModel {
4039
let child = SelectionSet(value: .init(name: field.name, fieldType: .model))
4140
child.withModelFields(associatedModel.schema.graphQLFields)
4241
self.addChild(settingParentOf: child)
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//
2+
// Copyright 2018-2020 Amazon.com,
3+
// Inc. or its affiliates. All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
10+
@testable import Amplify
11+
@testable import AmplifyTestCommon
12+
@testable import AWSPluginsCore
13+
14+
class GraphQLRequestOptionalAssociationTests: XCTestCase {
15+
override func setUp() {
16+
ModelRegistry.register(modelType: User.self)
17+
ModelRegistry.register(modelType: UserFollowing.self)
18+
ModelRegistry.register(modelType: UserFollowers.self)
19+
}
20+
21+
override func tearDown() {
22+
ModelRegistry.reset()
23+
}
24+
25+
func testCreateUserGraphQLRequest() {
26+
let user = User(name: "username")
27+
let documentStringValue = """
28+
mutation CreateUser($input: CreateUserInput!) {
29+
createUser(input: $input) {
30+
id
31+
name
32+
__typename
33+
}
34+
}
35+
"""
36+
let request = GraphQLRequest<User>.create(user)
37+
XCTAssertEqual(documentStringValue, request.document)
38+
39+
guard let variables = request.variables else {
40+
XCTFail("The request doesn't contain variables")
41+
return
42+
}
43+
guard let input = variables["input"] as? [String: Any] else {
44+
XCTFail("The document variables property doesn't contain a valid input")
45+
return
46+
}
47+
XCTAssertEqual(input["id"] as? String, user.id)
48+
XCTAssertEqual(input["name"] as? String, user.name)
49+
}
50+
51+
func testCreateUserFollowingGraphQLRequest() {
52+
let user1 = User(name: "user1")
53+
let user2 = User(name: "user2")
54+
let userFollowing = UserFollowing(user: user1, followingUser: user2)
55+
let documentStringValue = """
56+
mutation CreateUserFollowing($input: CreateUserFollowingInput!) {
57+
createUserFollowing(input: $input) {
58+
id
59+
followingUser {
60+
id
61+
name
62+
__typename
63+
}
64+
user {
65+
id
66+
name
67+
__typename
68+
}
69+
__typename
70+
}
71+
}
72+
"""
73+
let request = GraphQLRequest<User>.create(userFollowing)
74+
XCTAssertEqual(documentStringValue, request.document)
75+
guard let variables = request.variables else {
76+
XCTFail("The request doesn't contain variables")
77+
return
78+
}
79+
guard let input = variables["input"] as? [String: Any] else {
80+
XCTFail("The document variables property doesn't contain a valid input")
81+
return
82+
}
83+
XCTAssertEqual(input["id"] as? String, userFollowing.id)
84+
XCTAssertEqual(input["userFollowingUserId"] as? String, user1.id)
85+
XCTAssertEqual(input["userFollowingFollowingUserId"] as? String, user2.id)
86+
}
87+
88+
func testQueryUserFollowingGraphQLRequest() {
89+
let documentStringValue = """
90+
query GetUserFollowing($id: ID!) {
91+
getUserFollowing(id: $id) {
92+
id
93+
followingUser {
94+
id
95+
name
96+
__typename
97+
}
98+
user {
99+
id
100+
name
101+
__typename
102+
}
103+
__typename
104+
}
105+
}
106+
"""
107+
let request = GraphQLRequest<UserFollowing>.get(UserFollowing.self, byId: "id")
108+
XCTAssertEqual(documentStringValue, request.document)
109+
guard let variables = request.variables else {
110+
XCTFail("The request doesn't contain variables")
111+
return
112+
}
113+
XCTAssertEqual(variables["id"] as? String, "id")
114+
}
115+
116+
func testQueryUserGraphQLRequest() {
117+
let documentStringValue = """
118+
query GetUser($id: ID!) {
119+
getUser(id: $id) {
120+
id
121+
name
122+
__typename
123+
}
124+
}
125+
"""
126+
let request = GraphQLRequest<UserFollowing>.get(User.self, byId: "id")
127+
XCTAssertEqual(documentStringValue, request.document)
128+
guard let variables = request.variables else {
129+
XCTFail("The request doesn't contain variables")
130+
return
131+
}
132+
XCTAssertEqual(variables["id"] as? String, "id")
133+
}
134+
135+
func testListUserFollowingGraphQLRequest() {
136+
let documentStringValue = """
137+
query ListUserFollowings($limit: Int) {
138+
listUserFollowings(limit: $limit) {
139+
items {
140+
id
141+
followingUser {
142+
id
143+
name
144+
__typename
145+
}
146+
user {
147+
id
148+
name
149+
__typename
150+
}
151+
__typename
152+
}
153+
nextToken
154+
}
155+
}
156+
"""
157+
let request = GraphQLRequest<UserFollowing>.list(UserFollowing.self)
158+
XCTAssertEqual(documentStringValue, request.document)
159+
guard let variables = request.variables else {
160+
XCTFail("The request doesn't contain variables")
161+
return
162+
}
163+
XCTAssertEqual(variables["limit"] as? Int, 1_000)
164+
}
165+
166+
func testSubscribeToUserFollowingGraphQLRequest() {
167+
let documentStringValue = """
168+
subscription OnCreateUserFollowing {
169+
onCreateUserFollowing {
170+
id
171+
followingUser {
172+
id
173+
name
174+
__typename
175+
}
176+
user {
177+
id
178+
name
179+
__typename
180+
}
181+
__typename
182+
}
183+
}
184+
"""
185+
let request = GraphQLRequest<UserFollowing>.subscription(of: UserFollowing.self, type: .onCreate)
186+
XCTAssertEqual(documentStringValue, request.document)
187+
XCTAssertNil(request.variables)
188+
}
189+
}

AmplifyTestCommon/Models/AmplifyModels.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ final public class AmplifyModels: AmplifyModelRegistration {
1717
public func registerModels(registry: ModelRegistry.Type) {
1818
ModelRegistry.register(modelType: Post.self)
1919
ModelRegistry.register(modelType: Comment.self)
20+
ModelRegistry.register(modelType: User.self)
21+
ModelRegistry.register(modelType: UserFollowers.self)
22+
ModelRegistry.register(modelType: UserFollowing.self)
2023
}
2124
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Copyright 2018-2020 Amazon.com,
3+
// Inc. or its affiliates. All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
// swiftlint:disable all
9+
import Amplify
10+
import Foundation
11+
12+
extension User {
13+
// MARK: - CodingKeys
14+
public enum CodingKeys: String, ModelKey {
15+
case id
16+
case name
17+
case following
18+
case followers
19+
}
20+
21+
public static let keys = CodingKeys.self
22+
// MARK: - ModelSchema
23+
24+
public static let schema = defineSchema { model in
25+
let user = User.keys
26+
27+
model.pluralName = "Users"
28+
29+
model.fields(
30+
.id(),
31+
.field(user.name, is: .required, ofType: .string),
32+
.hasMany(user.following, is: .optional, ofType: UserFollowing.self, associatedWith: UserFollowing.keys.user),
33+
.hasMany(user.followers, is: .optional, ofType: UserFollowers.self, associatedWith: UserFollowers.keys.user)
34+
)
35+
}
36+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Copyright 2018-2020 Amazon.com,
3+
// Inc. or its affiliates. All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
// swiftlint:disable all
9+
import Amplify
10+
import Foundation
11+
12+
public struct User: Model {
13+
public let id: String
14+
public var name: String
15+
public var following: List<UserFollowing>?
16+
public var followers: List<UserFollowers>?
17+
18+
public init(id: String = UUID().uuidString,
19+
name: String,
20+
following: List<UserFollowing>? = [],
21+
followers: List<UserFollowers>? = []) {
22+
self.id = id
23+
self.name = name
24+
self.following = following
25+
self.followers = followers
26+
}
27+
}

0 commit comments

Comments
 (0)