diff --git a/Amplify.xcodeproj/project.pbxproj b/Amplify.xcodeproj/project.pbxproj index f2477e10b5..39c2dbd761 100644 --- a/Amplify.xcodeproj/project.pbxproj +++ b/Amplify.xcodeproj/project.pbxproj @@ -22,27 +22,12 @@ 2125E25E2319EC3200B3DEB5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2125E25C2319EC3200B3DEB5 /* LaunchScreen.storyboard */; }; 2125E265231C2D3100B3DEB5 /* awsconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 2125E2102318D73B00B3DEB5 /* awsconfiguration.json */; }; 2129BE012394627B006363A1 /* PostCommentModelRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE002394627B006363A1 /* PostCommentModelRegistration.swift */; }; - 2129BE0D23948005006363A1 /* GraphQLDocument+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0623948005006363A1 /* GraphQLDocument+Subscription.swift */; }; - 2129BE0E23948005006363A1 /* GraphQLDocument+SyncMutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0723948005006363A1 /* GraphQLDocument+SyncMutation.swift */; }; - 2129BE0F23948005006363A1 /* GraphQLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0823948005006363A1 /* GraphQLDocument.swift */; }; - 2129BE1023948005006363A1 /* GraphQLDocument+SyncQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0923948005006363A1 /* GraphQLDocument+SyncQuery.swift */; }; - 2129BE1123948005006363A1 /* GraphQLDocument+GetQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0A23948005006363A1 /* GraphQLDocument+GetQuery.swift */; }; - 2129BE1223948005006363A1 /* GraphQLDocument+ListQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0B23948005006363A1 /* GraphQLDocument+ListQuery.swift */; }; - 2129BE1323948005006363A1 /* GraphQLDocument+Mutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0C23948005006363A1 /* GraphQLDocument+Mutation.swift */; }; + 2129BE0F23948005006363A1 /* SingleDirectiveGraphQLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE0823948005006363A1 /* SingleDirectiveGraphQLDocument.swift */; }; 2129BE1723948065006363A1 /* GraphQLRequest+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE1523948065006363A1 /* GraphQLRequest+Model.swift */; }; - 2129BE1823948065006363A1 /* GraphQLRequest+AnyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE1623948065006363A1 /* GraphQLRequest+AnyModel.swift */; }; 2129BE1E2394806B006363A1 /* QueryPredicate+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE1A2394806B006363A1 /* QueryPredicate+GraphQL.swift */; }; 2129BE1F2394806B006363A1 /* Model+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE1B2394806B006363A1 /* Model+GraphQL.swift */; }; 2129BE212394806B006363A1 /* ModelSchema+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE1D2394806B006363A1 /* ModelSchema+GraphQL.swift */; }; - 2129BE332394828B006363A1 /* GraphQLSubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE272394828A006363A1 /* GraphQLSubscriptionTests.swift */; }; - 2129BE342394828B006363A1 /* GraphQLMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE282394828A006363A1 /* GraphQLMutationTests.swift */; }; - 2129BE352394828B006363A1 /* GraphQLListQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE292394828A006363A1 /* GraphQLListQueryTests.swift */; }; - 2129BE362394828B006363A1 /* GraphQLDocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE2A2394828A006363A1 /* GraphQLDocumentTests.swift */; }; - 2129BE372394828B006363A1 /* GraphQLSyncQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE2B2394828A006363A1 /* GraphQLSyncQueryTests.swift */; }; - 2129BE382394828B006363A1 /* GraphQLGetQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE2C2394828A006363A1 /* GraphQLGetQueryTests.swift */; }; - 2129BE392394828B006363A1 /* GraphQLSyncMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE2D2394828A006363A1 /* GraphQLSyncMutationTests.swift */; }; 2129BE3A2394828B006363A1 /* QueryPredicateGraphQLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE2F2394828A006363A1 /* QueryPredicateGraphQLTests.swift */; }; - 2129BE3B2394828B006363A1 /* GraphQLRequestAnyModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE312394828B006363A1 /* GraphQLRequestAnyModelTests.swift */; }; 2129BE3C2394828B006363A1 /* GraphQLRequestModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */; }; 2129BE4223948924006363A1 /* MutationSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE4123948924006363A1 /* MutationSync.swift */; }; 2129BE4423948951006363A1 /* MutationSyncMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE4323948951006363A1 /* MutationSyncMetadata.swift */; }; @@ -51,6 +36,18 @@ 2129BE512395A66F006363A1 /* AmplifyModelRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE502395A66F006363A1 /* AmplifyModelRegistration.swift */; }; 2129BE552395CAEF006363A1 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE542395CAEF006363A1 /* PaginatedList.swift */; }; 2129BE562395CAF9006363A1 /* PaginatedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE522395CA05006363A1 /* PaginatedListTests.swift */; }; + 212CE6FC23E9E523007D8E71 /* SelectionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE6FB23E9E523007D8E71 /* SelectionSet.swift */; }; + 212CE6FE23E9E5A2007D8E71 /* GraphQLDocumentnputValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE6FD23E9E5A2007D8E71 /* GraphQLDocumentnputValue.swift */; }; + 212CE70323E9E967007D8E71 /* GraphQLMutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70023E9E967007D8E71 /* GraphQLMutation.swift */; }; + 212CE70423E9E967007D8E71 /* GraphQLSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70123E9E967007D8E71 /* GraphQLSubscription.swift */; }; + 212CE70523E9E967007D8E71 /* GraphQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70223E9E967007D8E71 /* GraphQLQuery.swift */; }; + 212CE70B23E9E991007D8E71 /* ModelIdDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */; }; + 212CE70C23E9E991007D8E71 /* ConflictResolutionDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */; }; + 212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70823E9E990007D8E71 /* PredicateDecorator.swift */; }; + 212CE70E23E9E991007D8E71 /* PaginationDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70923E9E991007D8E71 /* PaginationDecorator.swift */; }; + 212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70A23E9E991007D8E71 /* ModelDecorator.swift */; }; + 212CE71123E9EA6A007D8E71 /* ModelField+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */; }; + 212CE71323E9F2ED007D8E71 /* DirectiveNameDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71223E9F2ED007D8E71 /* DirectiveNameDecorator.swift */; }; 21409C552384C55D000A53C9 /* LabelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C542384C55D000A53C9 /* LabelType.swift */; }; 21409C5A2384C57D000A53C9 /* GraphQLMutationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C572384C57D000A53C9 /* GraphQLMutationType.swift */; }; 21409C5B2384C57D000A53C9 /* GraphQLQueryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C582384C57D000A53C9 /* GraphQLQueryType.swift */; }; @@ -79,11 +76,24 @@ 21687A3E236371C4004A056E /* AnalyticsCategory+HubPayloadEventName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21687A3D236371C4004A056E /* AnalyticsCategory+HubPayloadEventName.swift */; }; 21687A41236371E1004A056E /* AnalyticsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21687A40236371E1004A056E /* AnalyticsError.swift */; }; 217855C3237F84D700A30D19 /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217855C2237F84D700A30D19 /* RESTRequest.swift */; }; + 2183A56323EA4A7800232880 /* GraphQLSubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE72023EA184F007D8E71 /* GraphQLSubscriptionTests.swift */; }; + 2183A56423EA4A7F00232880 /* GraphQLGetQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71A23EA1847007D8E71 /* GraphQLGetQueryTests.swift */; }; + 2183A56523EA4A8400232880 /* GraphQLListQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71B23EA1847007D8E71 /* GraphQLListQueryTests.swift */; }; + 2183A56623EA4A8700232880 /* GraphQLSyncQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71C23EA1847007D8E71 /* GraphQLSyncQueryTests.swift */; }; + 2183A56723EA4A8B00232880 /* GraphQLCreateMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71623EA1839007D8E71 /* GraphQLCreateMutationTests.swift */; }; + 2183A56823EA4A8E00232880 /* GraphQLDeleteMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71423EA1838007D8E71 /* GraphQLDeleteMutationTests.swift */; }; + 2183A56923EA4A9100232880 /* GraphQLUpdateMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71523EA1839007D8E71 /* GraphQLUpdateMutationTests.swift */; }; + 219A887F23EB627100BBC5F2 /* ModelBasedGraphQLDocumentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A887E23EB627100BBC5F2 /* ModelBasedGraphQLDocumentBuilder.swift */; }; + 219A888123EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A888023EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift */; }; + 219A888523EB897700BBC5F2 /* GraphQLRequest+AnyModelWithSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A888423EB897700BBC5F2 /* GraphQLRequest+AnyModelWithSync.swift */; }; + 219A888723EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219A888623EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift */; }; + 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 */; }; 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 */; }; 21D79FE32377F4120057D00D /* SubscriptionConnectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D79FE22377F4120057D00D /* SubscriptionConnectionState.swift */; }; - 21F40A3223A160FC0074678E /* GraphQLDocument+DeleteSyncMutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F40A3123A160FC0074678E /* GraphQLDocument+DeleteSyncMutation.swift */; }; 21F40A3A23A294770074678E /* TestConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F40A3923A294770074678E /* TestConfigHelper.swift */; }; 21F40A3C23A2952C0074678E /* AuthHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F40A3B23A2952C0074678E /* AuthHelper.swift */; }; 21F40A3E23A295390074678E /* AWSMobileClient+Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F40A3D23A295390074678E /* AWSMobileClient+Message.swift */; }; @@ -211,7 +221,7 @@ B9FAA1272388BE91009414B4 /* List+LazyLoad.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA1262388BE91009414B4 /* List+LazyLoad.swift */; }; B9FAA13A238BBADE009414B4 /* List+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA139238BBADE009414B4 /* List+Combine.swift */; }; B9FAA13C238BBE67009414B4 /* DataStoreCallback+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA13B238BBE67009414B4 /* DataStoreCallback+Combine.swift */; }; - B9FAA175238EFC5A009414B4 /* String+Casing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA174238EFC59009414B4 /* String+Casing.swift */; }; + B9FAA175238EFC5A009414B4 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA174238EFC59009414B4 /* String+Extensions.swift */; }; B9FAA180238FBB5D009414B4 /* Model+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA17F238FBB5D009414B4 /* Model+Array.swift */; }; B9FB05F82383740D00DE1FD4 /* DataStoreStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB05F72383740D00DE1FD4 /* DataStoreStatement.swift */; }; FA0173352375F8A5005DDDFC /* LoggingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0173342375F8A5005DDDFC /* LoggingError.swift */; }; @@ -530,27 +540,12 @@ 2125E25F2319EC3200B3DEB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2125E2A72321DCDD00B3DEB5 /* MockAWSAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAWSAuthService.swift; sourceTree = ""; }; 2129BE002394627B006363A1 /* PostCommentModelRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommentModelRegistration.swift; sourceTree = ""; }; - 2129BE0623948005006363A1 /* GraphQLDocument+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+Subscription.swift"; sourceTree = ""; }; - 2129BE0723948005006363A1 /* GraphQLDocument+SyncMutation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+SyncMutation.swift"; sourceTree = ""; }; - 2129BE0823948005006363A1 /* GraphQLDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLDocument.swift; sourceTree = ""; }; - 2129BE0923948005006363A1 /* GraphQLDocument+SyncQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+SyncQuery.swift"; sourceTree = ""; }; - 2129BE0A23948005006363A1 /* GraphQLDocument+GetQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+GetQuery.swift"; sourceTree = ""; }; - 2129BE0B23948005006363A1 /* GraphQLDocument+ListQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+ListQuery.swift"; sourceTree = ""; }; - 2129BE0C23948005006363A1 /* GraphQLDocument+Mutation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+Mutation.swift"; sourceTree = ""; }; + 2129BE0823948005006363A1 /* SingleDirectiveGraphQLDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleDirectiveGraphQLDocument.swift; sourceTree = ""; }; 2129BE1523948065006363A1 /* GraphQLRequest+Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLRequest+Model.swift"; sourceTree = ""; }; - 2129BE1623948065006363A1 /* GraphQLRequest+AnyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLRequest+AnyModel.swift"; sourceTree = ""; }; 2129BE1A2394806B006363A1 /* QueryPredicate+GraphQL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "QueryPredicate+GraphQL.swift"; sourceTree = ""; }; 2129BE1B2394806B006363A1 /* Model+GraphQL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+GraphQL.swift"; sourceTree = ""; }; 2129BE1D2394806B006363A1 /* ModelSchema+GraphQL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ModelSchema+GraphQL.swift"; sourceTree = ""; }; - 2129BE272394828A006363A1 /* GraphQLSubscriptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSubscriptionTests.swift; sourceTree = ""; }; - 2129BE282394828A006363A1 /* GraphQLMutationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLMutationTests.swift; sourceTree = ""; }; - 2129BE292394828A006363A1 /* GraphQLListQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLListQueryTests.swift; sourceTree = ""; }; - 2129BE2A2394828A006363A1 /* GraphQLDocumentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLDocumentTests.swift; sourceTree = ""; }; - 2129BE2B2394828A006363A1 /* GraphQLSyncQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSyncQueryTests.swift; sourceTree = ""; }; - 2129BE2C2394828A006363A1 /* GraphQLGetQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLGetQueryTests.swift; sourceTree = ""; }; - 2129BE2D2394828A006363A1 /* GraphQLSyncMutationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSyncMutationTests.swift; sourceTree = ""; }; 2129BE2F2394828A006363A1 /* QueryPredicateGraphQLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryPredicateGraphQLTests.swift; sourceTree = ""; }; - 2129BE312394828B006363A1 /* GraphQLRequestAnyModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLRequestAnyModelTests.swift; sourceTree = ""; }; 2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLRequestModelTests.swift; sourceTree = ""; }; 2129BE4123948924006363A1 /* MutationSync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutationSync.swift; sourceTree = ""; }; 2129BE4323948951006363A1 /* MutationSyncMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutationSyncMetadata.swift; sourceTree = ""; }; @@ -559,6 +554,25 @@ 2129BE502395A66F006363A1 /* AmplifyModelRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmplifyModelRegistration.swift; sourceTree = ""; }; 2129BE522395CA05006363A1 /* PaginatedListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedListTests.swift; sourceTree = ""; }; 2129BE542395CAEF006363A1 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; + 212CE6FB23E9E523007D8E71 /* SelectionSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionSet.swift; sourceTree = ""; }; + 212CE6FD23E9E5A2007D8E71 /* GraphQLDocumentnputValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLDocumentnputValue.swift; sourceTree = ""; }; + 212CE70023E9E967007D8E71 /* GraphQLMutation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLMutation.swift; sourceTree = ""; }; + 212CE70123E9E967007D8E71 /* GraphQLSubscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSubscription.swift; sourceTree = ""; }; + 212CE70223E9E967007D8E71 /* GraphQLQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLQuery.swift; sourceTree = ""; }; + 212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelIdDecorator.swift; sourceTree = ""; }; + 212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConflictResolutionDecorator.swift; sourceTree = ""; }; + 212CE70823E9E990007D8E71 /* PredicateDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredicateDecorator.swift; sourceTree = ""; }; + 212CE70923E9E991007D8E71 /* PaginationDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationDecorator.swift; sourceTree = ""; }; + 212CE70A23E9E991007D8E71 /* ModelDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelDecorator.swift; sourceTree = ""; }; + 212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelField+GraphQL.swift"; sourceTree = ""; }; + 212CE71223E9F2ED007D8E71 /* DirectiveNameDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectiveNameDecorator.swift; sourceTree = ""; }; + 212CE71423EA1838007D8E71 /* GraphQLDeleteMutationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLDeleteMutationTests.swift; sourceTree = ""; }; + 212CE71523EA1839007D8E71 /* GraphQLUpdateMutationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLUpdateMutationTests.swift; sourceTree = ""; }; + 212CE71623EA1839007D8E71 /* GraphQLCreateMutationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLCreateMutationTests.swift; sourceTree = ""; }; + 212CE71A23EA1847007D8E71 /* GraphQLGetQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLGetQueryTests.swift; sourceTree = ""; }; + 212CE71B23EA1847007D8E71 /* GraphQLListQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLListQueryTests.swift; sourceTree = ""; }; + 212CE71C23EA1847007D8E71 /* GraphQLSyncQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSyncQueryTests.swift; sourceTree = ""; }; + 212CE72023EA184F007D8E71 /* GraphQLSubscriptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSubscriptionTests.swift; sourceTree = ""; }; 21409C4C23847E41000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = ""; }; 21409C542384C55D000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = ""; }; 21409C572384C57D000A53C9 /* GraphQLMutationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLMutationType.swift; sourceTree = ""; }; @@ -591,11 +605,17 @@ 217855C2237F84D700A30D19 /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; 217856BA2383320900A30D19 /* GraphQLQueryType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQueryType.swift; sourceTree = ""; }; 217856BD2383322700A30D19 /* GraphQLMutationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLMutationType.swift; sourceTree = ""; }; + 219A887E23EB627100BBC5F2 /* ModelBasedGraphQLDocumentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelBasedGraphQLDocumentBuilder.swift; sourceTree = ""; }; + 219A888023EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelBasedGraphQLDocumentDecorator.swift; sourceTree = ""; }; + 219A888423EB897700BBC5F2 /* GraphQLRequest+AnyModelWithSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphQLRequest+AnyModelWithSync.swift"; sourceTree = ""; }; + 219A888623EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequestAnyModelWithSyncTests.swift; sourceTree = ""; }; + 219A88EC23F3309800BBC5F2 /* Tree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tree.swift; sourceTree = ""; }; + 219A88EE23F3358F00BBC5F2 /* TreeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeTests.swift; sourceTree = ""; }; + 219A88F023F3379900BBC5F2 /* GraphQLDocumentInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLDocumentInput.swift; sourceTree = ""; }; 21D1CE8B2334233F0003BAA8 /* AuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthError.swift; sourceTree = ""; }; 21D79FD9237617C60057D00D /* SubscriptionEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEvent.swift; sourceTree = ""; }; 21D79FE02377BF4B0057D00D /* AuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProvider.swift; sourceTree = ""; }; 21D79FE22377F4120057D00D /* SubscriptionConnectionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionConnectionState.swift; sourceTree = ""; }; - 21F40A3123A160FC0074678E /* GraphQLDocument+DeleteSyncMutation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLDocument+DeleteSyncMutation.swift"; sourceTree = ""; }; 21F40A3923A294770074678E /* TestConfigHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConfigHelper.swift; sourceTree = ""; }; 21F40A3B23A2952C0074678E /* AuthHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthHelper.swift; sourceTree = ""; }; 21F40A3D23A295390074678E /* AWSMobileClient+Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AWSMobileClient+Message.swift"; sourceTree = ""; }; @@ -827,7 +847,7 @@ B9FAA1262388BE91009414B4 /* List+LazyLoad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "List+LazyLoad.swift"; sourceTree = ""; }; B9FAA139238BBADE009414B4 /* List+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "List+Combine.swift"; sourceTree = ""; }; B9FAA13B238BBE67009414B4 /* DataStoreCallback+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStoreCallback+Combine.swift"; sourceTree = ""; }; - B9FAA174238EFC59009414B4 /* String+Casing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Casing.swift"; sourceTree = ""; }; + B9FAA174238EFC59009414B4 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; B9FAA17F238FBB5D009414B4 /* Model+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Model+Array.swift"; sourceTree = ""; }; B9FB05F72383740D00DE1FD4 /* DataStoreStatement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreStatement.swift; sourceTree = ""; }; BAFD88194E245D6B82399825 /* Pods-Amplify-AmplifyAWSPlugins-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AmplifyAWSPlugins-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig"; path = "Target Support Files/Pods-Amplify-AmplifyAWSPlugins-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests/Pods-Amplify-AmplifyAWSPlugins-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig"; sourceTree = ""; }; @@ -1177,9 +1197,10 @@ 2129BE0223947FA3006363A1 /* Model */ = { isa = PBXGroup; children = ( - 2129BE192394806B006363A1 /* Support */, - 2129BE1423948065006363A1 /* GraphQLRequest */, + 212CE6FF23E9E938007D8E71 /* Decorator */, 2129BE0523948005006363A1 /* GraphQLDocument */, + 2129BE1423948065006363A1 /* GraphQLRequest */, + 2129BE192394806B006363A1 /* Support */, ); path = Model; sourceTree = ""; @@ -1187,14 +1208,11 @@ 2129BE0523948005006363A1 /* GraphQLDocument */ = { isa = PBXGroup; children = ( - 2129BE0823948005006363A1 /* GraphQLDocument.swift */, - 21F40A3123A160FC0074678E /* GraphQLDocument+DeleteSyncMutation.swift */, - 2129BE0A23948005006363A1 /* GraphQLDocument+GetQuery.swift */, - 2129BE0B23948005006363A1 /* GraphQLDocument+ListQuery.swift */, - 2129BE0C23948005006363A1 /* GraphQLDocument+Mutation.swift */, - 2129BE0623948005006363A1 /* GraphQLDocument+Subscription.swift */, - 2129BE0723948005006363A1 /* GraphQLDocument+SyncMutation.swift */, - 2129BE0923948005006363A1 /* GraphQLDocument+SyncQuery.swift */, + 212CE70023E9E967007D8E71 /* GraphQLMutation.swift */, + 212CE70223E9E967007D8E71 /* GraphQLQuery.swift */, + 212CE70123E9E967007D8E71 /* GraphQLSubscription.swift */, + 219A887E23EB627100BBC5F2 /* ModelBasedGraphQLDocumentBuilder.swift */, + 2129BE0823948005006363A1 /* SingleDirectiveGraphQLDocument.swift */, ); path = GraphQLDocument; sourceTree = ""; @@ -1202,8 +1220,8 @@ 2129BE1423948065006363A1 /* GraphQLRequest */ = { isa = PBXGroup; children = ( + 219A888423EB897700BBC5F2 /* GraphQLRequest+AnyModelWithSync.swift */, 2129BE1523948065006363A1 /* GraphQLRequest+Model.swift */, - 2129BE1623948065006363A1 /* GraphQLRequest+AnyModel.swift */, ); path = GraphQLRequest; sourceTree = ""; @@ -1211,9 +1229,13 @@ 2129BE192394806B006363A1 /* Support */ = { isa = PBXGroup; children = ( - 2129BE1A2394806B006363A1 /* QueryPredicate+GraphQL.swift */, + 219A88F023F3379900BBC5F2 /* GraphQLDocumentInput.swift */, + 212CE6FD23E9E5A2007D8E71 /* GraphQLDocumentnputValue.swift */, 2129BE1B2394806B006363A1 /* Model+GraphQL.swift */, + 212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */, 2129BE1D2394806B006363A1 /* ModelSchema+GraphQL.swift */, + 2129BE1A2394806B006363A1 /* QueryPredicate+GraphQL.swift */, + 212CE6FB23E9E523007D8E71 /* SelectionSet.swift */, ); path = Support; sourceTree = ""; @@ -1231,13 +1253,13 @@ 2129BE262394828A006363A1 /* GraphQLDocument */ = { isa = PBXGroup; children = ( - 2129BE272394828A006363A1 /* GraphQLSubscriptionTests.swift */, - 2129BE282394828A006363A1 /* GraphQLMutationTests.swift */, - 2129BE292394828A006363A1 /* GraphQLListQueryTests.swift */, - 2129BE2A2394828A006363A1 /* GraphQLDocumentTests.swift */, - 2129BE2B2394828A006363A1 /* GraphQLSyncQueryTests.swift */, - 2129BE2C2394828A006363A1 /* GraphQLGetQueryTests.swift */, - 2129BE2D2394828A006363A1 /* GraphQLSyncMutationTests.swift */, + 212CE72023EA184F007D8E71 /* GraphQLSubscriptionTests.swift */, + 212CE71A23EA1847007D8E71 /* GraphQLGetQueryTests.swift */, + 212CE71B23EA1847007D8E71 /* GraphQLListQueryTests.swift */, + 212CE71C23EA1847007D8E71 /* GraphQLSyncQueryTests.swift */, + 212CE71623EA1839007D8E71 /* GraphQLCreateMutationTests.swift */, + 212CE71423EA1838007D8E71 /* GraphQLDeleteMutationTests.swift */, + 212CE71523EA1839007D8E71 /* GraphQLUpdateMutationTests.swift */, ); path = GraphQLDocument; sourceTree = ""; @@ -1253,8 +1275,8 @@ 2129BE302394828B006363A1 /* GraphQLRequest */ = { isa = PBXGroup; children = ( - 2129BE312394828B006363A1 /* GraphQLRequestAnyModelTests.swift */, 2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */, + 219A888623EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift */, ); path = GraphQLRequest; sourceTree = ""; @@ -1295,6 +1317,20 @@ path = MutationSync; sourceTree = ""; }; + 212CE6FF23E9E938007D8E71 /* Decorator */ = { + isa = PBXGroup; + children = ( + 212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */, + 212CE71223E9F2ED007D8E71 /* DirectiveNameDecorator.swift */, + 219A888023EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift */, + 212CE70A23E9E991007D8E71 /* ModelDecorator.swift */, + 212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */, + 212CE70923E9E991007D8E71 /* PaginationDecorator.swift */, + 212CE70823E9E990007D8E71 /* PredicateDecorator.swift */, + ); + path = Decorator; + sourceTree = ""; + }; 21409C562384C57D000A53C9 /* GraphQL */ = { isa = PBXGroup; children = ( @@ -2007,7 +2043,8 @@ 216879FD23636A0A004A056E /* RepeatingTimer.swift */, FAE4145E23999BC900CE94C2 /* Result+Void.swift */, FA56F72422B14B6A0039754A /* Resumable.swift */, - B9FAA174238EFC59009414B4 /* String+Casing.swift */, + B9FAA174238EFC59009414B4 /* String+Extensions.swift */, + 219A88EC23F3309800BBC5F2 /* Tree.swift */, ); path = Support; sourceTree = ""; @@ -2350,6 +2387,7 @@ FACD264A2386E8F10068FBE6 /* JSONValue+SubscriptTests.swift */, FA09B9422321CB0C000E064D /* JSONValueTests.swift */, FA176EDC2385943000C5C5F9 /* NotificationListeningAnalyticsPlugin.swift */, + 219A88EE23F3358F00BBC5F2 /* TreeTests.swift */, ); path = CoreTests; sourceTree = ""; @@ -3396,32 +3434,40 @@ 2129BE1E2394806B006363A1 /* QueryPredicate+GraphQL.swift in Sources */, 21420A8F237222A900FA140C /* AWSIAMConfiguration.swift in Sources */, 2129BE1723948065006363A1 /* GraphQLRequest+Model.swift in Sources */, + 219A888523EB897700BBC5F2 /* GraphQLRequest+AnyModelWithSync.swift in Sources */, + 219A88F123F3379900BBC5F2 /* GraphQLDocumentInput.swift in Sources */, 21420AA0237222A900FA140C /* AWSAuthorizationType.swift in Sources */, - 2129BE0D23948005006363A1 /* GraphQLDocument+Subscription.swift in Sources */, 21420A9F237222A900FA140C /* AWSMobileClientAdapter.swift in Sources */, + 212CE6FC23E9E523007D8E71 /* SelectionSet.swift in Sources */, 2129BE1F2394806B006363A1 /* Model+GraphQL.swift in Sources */, - 2129BE0E23948005006363A1 /* GraphQLDocument+SyncMutation.swift in Sources */, - 2129BE0F23948005006363A1 /* GraphQLDocument.swift in Sources */, + 212CE70323E9E967007D8E71 /* GraphQLMutation.swift in Sources */, + 2129BE0F23948005006363A1 /* SingleDirectiveGraphQLDocument.swift in Sources */, 21420A93237222A900FA140C /* CognitoUserPoolsConfiguration.swift in Sources */, - 2129BE1823948065006363A1 /* GraphQLRequest+AnyModel.swift in Sources */, 21D79FE12377BF4B0057D00D /* AuthProvider.swift in Sources */, 21420AA1237222A900FA140C /* AWSMobileClientBehavior.swift in Sources */, + 212CE71123E9EA6A007D8E71 /* ModelField+GraphQL.swift in Sources */, + 212CE70523E9E967007D8E71 /* GraphQLQuery.swift in Sources */, + 212CE70C23E9E991007D8E71 /* ConflictResolutionDecorator.swift in Sources */, + 212CE71323E9F2ED007D8E71 /* DirectiveNameDecorator.swift in Sources */, 21420A92237222A900FA140C /* OIDCConfiguration.swift in Sources */, 21420A95237222A900FA140C /* AWSAuthServiceBehavior.swift in Sources */, 21420A98237222A900FA140C /* AuthTokenProvider.swift in Sources */, 21420A91237222A900FA140C /* AWSAuthorizationConfiguration.swift in Sources */, + 212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */, 21420A97237222A900FA140C /* IAMCredentialProvider.swift in Sources */, - 21F40A3223A160FC0074678E /* GraphQLDocument+DeleteSyncMutation.swift in Sources */, + 212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */, + 219A888123EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift in Sources */, + 212CE70423E9E967007D8E71 /* GraphQLSubscription.swift in Sources */, 21420A99237222A900FA140C /* APIKeyProvider.swift in Sources */, 6BBECD7123ADA7E100C8DFBE /* AmplifyAWSServiceConfiguration.swift in Sources */, + 212CE6FE23E9E5A2007D8E71 /* GraphQLDocumentnputValue.swift in Sources */, 21420A90237222A900FA140C /* APIKeyConfiguration.swift in Sources */, + 219A887F23EB627100BBC5F2 /* ModelBasedGraphQLDocumentBuilder.swift in Sources */, 2129BE552395CAEF006363A1 /* PaginatedList.swift in Sources */, 2129BE212394806B006363A1 /* ModelSchema+GraphQL.swift in Sources */, - 2129BE1123948005006363A1 /* GraphQLDocument+GetQuery.swift in Sources */, + 212CE70B23E9E991007D8E71 /* ModelIdDecorator.swift in Sources */, 2129BE4223948924006363A1 /* MutationSync.swift in Sources */, - 2129BE1323948005006363A1 /* GraphQLDocument+Mutation.swift in Sources */, - 2129BE1223948005006363A1 /* GraphQLDocument+ListQuery.swift in Sources */, - 2129BE1023948005006363A1 /* GraphQLDocument+SyncQuery.swift in Sources */, + 212CE70E23E9E991007D8E71 /* PaginationDecorator.swift in Sources */, 21420A9C237222A900FA140C /* AWSAuthService.swift in Sources */, 2129BE4F23949F1B006363A1 /* MutationSyncMetadata+Schema.swift in Sources */, ); @@ -3431,19 +3477,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2183A56923EA4A9100232880 /* GraphQLUpdateMutationTests.swift in Sources */, 2129BE3A2394828B006363A1 /* QueryPredicateGraphQLTests.swift in Sources */, 2129BE48239489AC006363A1 /* MutationSyncMetadataTests.swift in Sources */, - 2129BE3B2394828B006363A1 /* GraphQLRequestAnyModelTests.swift in Sources */, + 2183A56823EA4A8E00232880 /* GraphQLDeleteMutationTests.swift in Sources */, + 2183A56323EA4A7800232880 /* GraphQLSubscriptionTests.swift in Sources */, 2129BE562395CAF9006363A1 /* PaginatedListTests.swift in Sources */, - 2129BE372394828B006363A1 /* GraphQLSyncQueryTests.swift in Sources */, + 2183A56723EA4A8B00232880 /* GraphQLCreateMutationTests.swift in Sources */, + 2183A56623EA4A8700232880 /* GraphQLSyncQueryTests.swift in Sources */, + 219A888723EB89C200BBC5F2 /* GraphQLRequestAnyModelWithSyncTests.swift in Sources */, + 2183A56423EA4A7F00232880 /* GraphQLGetQueryTests.swift in Sources */, 6BBECD7423ADA9D100C8DFBE /* AmplifyAWSServiceConfigurationTests.swift in Sources */, - 2129BE342394828B006363A1 /* GraphQLMutationTests.swift in Sources */, - 2129BE392394828B006363A1 /* GraphQLSyncMutationTests.swift in Sources */, - 2129BE332394828B006363A1 /* GraphQLSubscriptionTests.swift in Sources */, - 2129BE362394828B006363A1 /* GraphQLDocumentTests.swift in Sources */, - 2129BE352394828B006363A1 /* GraphQLListQueryTests.swift in Sources */, - 2129BE382394828B006363A1 /* GraphQLGetQueryTests.swift in Sources */, 2129BE3C2394828B006363A1 /* GraphQLRequestModelTests.swift in Sources */, + 2183A56523EA4A8400232880 /* GraphQLListQueryTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3477,6 +3523,7 @@ FAC23544227A055200424678 /* ConfigurationError.swift in Sources */, 95DAAB44237E639E0028544F /* ConvertResult.swift in Sources */, FAC428A4235F802A0000F221 /* AmplifyAPICategory+InterceptorBehavior.swift in Sources */, + 219A88ED23F3309800BBC5F2 /* Tree.swift in Sources */, 95DAAB47237E639E0028544F /* IdentifyLabelsResult.swift in Sources */, FAC23547227A055200424678 /* Plugin.swift in Sources */, B9FAA180238FBB5D009414B4 /* Model+Array.swift in Sources */, @@ -3495,7 +3542,7 @@ 95DAAB28237E63370028544F /* Celebrity.swift in Sources */, FA0933812384749A00C2FD5F /* LoggingCategory+Logger.swift in Sources */, FA6BC87F235F5DAE0001A882 /* APICategoryInterceptorBehavior.swift in Sources */, - B9FAA175238EFC5A009414B4 /* String+Casing.swift in Sources */, + B9FAA175238EFC5A009414B4 /* String+Extensions.swift in Sources */, 95DAAB25237E63370028544F /* Entity.swift in Sources */, FA09337C23844E9F00C2FD5F /* GraphQLOperationRequest.swift in Sources */, FAC23545227A055200424678 /* AmplifyConfiguration.swift in Sources */, @@ -3732,6 +3779,7 @@ FACD264D2386E8F10068FBE6 /* JSONValue+KeyPathTests.swift in Sources */, FA9FB782232AA26500C04D32 /* DefaultHubPluginCustomChannelTests.swift in Sources */, FAC23567227A056600424678 /* APICategoryClientRESTTests.swift in Sources */, + 219A88EF23F3358F00BBC5F2 /* TreeTests.swift in Sources */, FACA35EB2326B217000E74F6 /* AmplifyConfigurationInitializationTests.swift in Sources */, FAD3937A23820CDB00463F5E /* DataStoreCategoryClientAPITests.swift in Sources */, FAC23599227A598B00424678 /* DefaultHubPluginTests.swift in Sources */, diff --git a/Amplify/Categories/API/ClientBehavior/APICategoryGraphQLBehavior.swift b/Amplify/Categories/API/ClientBehavior/APICategoryGraphQLBehavior.swift index 3b29814e92..dd530c86b5 100644 --- a/Amplify/Categories/API/ClientBehavior/APICategoryGraphQLBehavior.swift +++ b/Amplify/Categories/API/ClientBehavior/APICategoryGraphQLBehavior.swift @@ -90,31 +90,4 @@ public protocol APICategoryGraphQLBehavior: class { func subscribe(request: GraphQLRequest, listener: GraphQLSubscriptionOperation.EventListener?) -> GraphQLSubscriptionOperation - - // MARK: - GraphQL operations without a specified type - - /// Performs a GraphQL mutate for the `AnyModel` item. This operation will be asynchronous, with the callback - /// accessible both locally and via the Hub. - /// - /// - Parameters: - /// - model: The instance of the `AnyModel`. - /// - type: The type of mutation to apply on the instance of `AnyModel`. - /// - listener: The event listener for the operation - /// - Returns: The AmplifyOperation being enqueued. - func mutate(ofAnyModel anyModel: AnyModel, - type: GraphQLMutationType, - listener: GraphQLOperation.EventListener?) -> GraphQLOperation - - /// An internal method used by Plugins to perform initial subscriptions on registered model types to keep them in - /// sync with DataStore. - /// - /// - Parameters: - /// - modelType: The type of the model to subscribe to, as the `Model` protocol rather than the concrete type - /// - subscriptionType: The type of subscription (onCreate, onUpdate, onDelete) to subscribe to - /// - Returns: The AmplifyOperation being enqueued - func subscribe(toAnyModelType modelType: Model.Type, - subscriptionType: GraphQLSubscriptionType, - listener: GraphQLSubscriptionOperation.EventListener?) - -> GraphQLSubscriptionOperation - } diff --git a/Amplify/Categories/API/ClientBehavior/AmplifyAPICategory+GraphQLBehavior.swift b/Amplify/Categories/API/ClientBehavior/AmplifyAPICategory+GraphQLBehavior.swift index e58055bf71..052b02bc61 100644 --- a/Amplify/Categories/API/ClientBehavior/AmplifyAPICategory+GraphQLBehavior.swift +++ b/Amplify/Categories/API/ClientBehavior/AmplifyAPICategory+GraphQLBehavior.swift @@ -51,20 +51,4 @@ extension AmplifyAPICategory: APICategoryGraphQLBehavior { -> GraphQLSubscriptionOperation { plugin.subscribe(request: request, listener: listener) } - - // MARK: - GraphQL operations without a specified type - - public func mutate(ofAnyModel anyModel: AnyModel, - type: GraphQLMutationType, - listener: GraphQLOperation.EventListener?) -> GraphQLOperation { - plugin.mutate(ofAnyModel: anyModel, type: type, listener: listener) - } - - public func subscribe(toAnyModelType modelType: Model.Type, - subscriptionType: GraphQLSubscriptionType, - listener: GraphQLSubscriptionOperation.EventListener?) - -> GraphQLSubscriptionOperation { - plugin.subscribe(toAnyModelType: modelType, subscriptionType: subscriptionType, listener: listener) - } - } diff --git a/Amplify/Categories/DataStore/GraphQL/GraphQLQueryType.swift b/Amplify/Categories/DataStore/GraphQL/GraphQLQueryType.swift index d7c1fe364f..80fe818fc5 100644 --- a/Amplify/Categories/DataStore/GraphQL/GraphQLQueryType.swift +++ b/Amplify/Categories/DataStore/GraphQL/GraphQLQueryType.swift @@ -5,10 +5,12 @@ // SPDX-License-Identifier: Apache-2.0 // -/// Defines the type of query, either a `list` which returns multiple results -/// and can optionally use filters or a `get`, which aims to fetch one result -/// identified by its `id`. +/// Defines the type of query, +/// `list` which returns multiple results and can optionally use filters +/// `get`, which aims to fetch one result identified by its `id`. +/// `sync`, similar to `list` and returns results with optionally specifically a point in time public enum GraphQLQueryType: String { case get case list + case sync } diff --git a/Amplify/Core/Support/String+Casing.swift b/Amplify/Core/Support/String+Extensions.swift similarity index 88% rename from Amplify/Core/Support/String+Casing.swift rename to Amplify/Core/Support/String+Extensions.swift index 91c409bad6..0bc5a25b38 100644 --- a/Amplify/Core/Support/String+Casing.swift +++ b/Amplify/Core/Support/String+Extensions.swift @@ -30,4 +30,9 @@ extension String { public func camelCased() -> String { return prefix(1).lowercased() + dropFirst() } + + /// Appends "s" to the end of the string to represent the pluralized form. + public func pluralize() -> String { + return self + "s" + } } diff --git a/Amplify/Core/Support/Tree.swift b/Amplify/Core/Support/Tree.swift new file mode 100644 index 0000000000..a4b5b4a750 --- /dev/null +++ b/Amplify/Core/Support/Tree.swift @@ -0,0 +1,25 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// A Tree data type with a `value` of some type `E` and `children` subtrees. +public class Tree { + public var value: E + public var children: [Tree] = [] + public weak var parent: Tree? + + public init(value: E) { + self.value = value + } + + /// Add a child to the tree's children and set a weak reference from the child to the parent (`self`) + public func addChild(settingParentOf child: Tree) { + children.append(child) + child.parent = self + } +} diff --git a/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+GraphQLBehavior.swift b/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+GraphQLBehavior.swift index f102e23a9b..b5e263eeb4 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+GraphQLBehavior.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+GraphQLBehavior.swift @@ -54,27 +54,6 @@ public extension AWSAPIPlugin { return operation } - func subscribe(toAnyModelType modelType: Model.Type, - subscriptionType: GraphQLSubscriptionType, - listener: GraphQLSubscriptionOperation.EventListener?) -> - GraphQLSubscriptionOperation { - let request = GraphQLRequest.subscription(toAnyModelType: modelType, - subscriptionType: subscriptionType) - - let operationRequest = getOperationRequest(request: request, - operationType: .subscription) - - let operation = AWSGraphQLSubscriptionOperation( - request: operationRequest, - pluginConfig: pluginConfig, - subscriptionConnectionFactory: subscriptionConnectionFactory, - authService: authService, - listener: listener) - - queue.addOperation(operation) - return operation - } - private func getOperationRequest(request: GraphQLRequest, operationType: GraphQLOperationType) -> GraphQLOperationRequest { diff --git a/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+Reachability.swift b/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+Reachability.swift index 6857c2618b..747786d801 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+Reachability.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin+Reachability.swift @@ -36,7 +36,7 @@ extension AWSAPIPlugin { private func determineHostName(apiName: String?) throws -> String { if let apiName = apiName { - guard let baseUrl = self.pluginConfig.endpoints[apiName]?.baseURL, + guard let baseUrl = pluginConfig.endpoints[apiName]?.baseURL, let host = baseUrl.host else { let error = APIError.invalidConfiguration("Invalid endpoint configuration for \(apiName)", """ @@ -59,7 +59,7 @@ extension AWSAPIPlugin { throw error } - guard let configEntry = self.pluginConfig.endpoints.first else { + guard let configEntry = pluginConfig.endpoints.first else { let error = APIError.invalidConfiguration("No API configurations found", """ Review how the API category is being instantiated and diff --git a/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/AnyModelIntegrationTests.swift b/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/AnyModelIntegrationTests.swift index 67b9754491..5607140578 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/AnyModelIntegrationTests.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/AnyModelIntegrationTests.swift @@ -50,7 +50,7 @@ class AnyModelIntegrationTests: XCTestCase { let callbackInvoked = expectation(description: "Callback invoked") var responseFromOperation: GraphQLResponse? - _ = Amplify.API.mutate(ofAnyModel: anyPost, type: .create) { response in + _ = Amplify.API.mutate(of: anyPost, type: .create) { response in defer { callbackInvoked.fulfill() } @@ -101,7 +101,7 @@ class AnyModelIntegrationTests: XCTestCase { let originalAnyPost = try originalPost.eraseToAnyModel() let createCallbackInvoked = expectation(description: "Create callback invoked") - _ = Amplify.API.mutate(ofAnyModel: originalAnyPost, type: .create) { _ in + _ = Amplify.API.mutate(of: originalAnyPost, type: .create) { _ in createCallbackInvoked.fulfill() } @@ -115,7 +115,7 @@ class AnyModelIntegrationTests: XCTestCase { let updateCallbackInvoked = expectation(description: "Update callback invoked") var responseFromOperation: GraphQLResponse? - _ = Amplify.API.mutate(ofAnyModel: updatedAnyPost, type: .update) { response in + _ = Amplify.API.mutate(of: updatedAnyPost, type: .update) { response in defer { updateCallbackInvoked.fulfill() } @@ -164,7 +164,7 @@ class AnyModelIntegrationTests: XCTestCase { let originalAnyPost = try originalPost.eraseToAnyModel() let createCallbackInvoked = expectation(description: "Create callback invoked") - _ = Amplify.API.mutate(ofAnyModel: originalAnyPost, type: .create) { _ in + _ = Amplify.API.mutate(of: originalAnyPost, type: .create) { _ in createCallbackInvoked.fulfill() } @@ -174,7 +174,7 @@ class AnyModelIntegrationTests: XCTestCase { let deleteCallbackInvoked = expectation(description: "Delete callback invoked") var responseFromOperation: GraphQLResponse? - _ = Amplify.API.mutate(ofAnyModel: originalAnyPost, type: .delete) { response in + _ = Amplify.API.mutate(of: originalAnyPost, type: .delete) { response in defer { deleteCallbackInvoked.fulfill() } diff --git a/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift b/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift index 730f573937..d1c0f4c4d0 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLSyncBased/GraphQLSyncBasedTests.swift @@ -55,18 +55,15 @@ class GraphQLSyncBasedTests: XCTestCase { let updatedTitle = title + "Updated" - let modifiedPost = Post(id: createdPost.model.id, + let modifiedPost = Post(id: createdPost.model["id"] as? String ?? "", title: updatedTitle, - content: createdPost.model.content, + content: createdPost.model["content"] as? String ?? "", createdAt: Date()) let completeInvoked = expectation(description: "request completed") var responseFromOperation: GraphQLResponse>? - let document = GraphQLSyncMutation(of: modifiedPost, type: .update, version: 1) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: MutationSync.self, - decodePath: document.decodePath) + + let request = GraphQLRequest.updateMutation(of: modifiedPost, version: 1) _ = Amplify.API.mutate(request: request) { event in defer { @@ -107,7 +104,7 @@ class GraphQLSyncBasedTests: XCTestCase { } XCTAssertEqual(mutationSync.model["title"] as? String, updatedTitle) - XCTAssertEqual(mutationSync.model["content"] as? String, createdPost.model.content) + XCTAssertEqual(mutationSync.model["content"] as? String, createdPost.model["content"] as? String) XCTAssertEqual(mutationSync.syncMetadata.version, 2) } @@ -132,11 +129,10 @@ class GraphQLSyncBasedTests: XCTestCase { var responseFromOperation: GraphQLResponse>? let post = Post.keys let predicate = post.title == title - let document = GraphQLSyncQuery(from: Post.self, predicate: predicate, limit: 1, lastSync: 123) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: PaginatedList.self, - decodePath: document.decodePath) + let request = GraphQLRequest.syncQuery(modelType: Post.self, + where: predicate, + limit: 1, + lastSync: 123) _ = Amplify.API.query(request: request) { event in defer { @@ -198,12 +194,8 @@ class GraphQLSyncBasedTests: XCTestCase { let disconnectedInvoked = expectation(description: "Connection disconnected") let completedInvoked = expectation(description: "Completed invoked") let progressInvoked = expectation(description: "Progress invoked") + let request = GraphQLRequest.subscription(to: Post.self, subscriptionType: .onCreate) - let document = GraphQLSubscription(of: Post.self, type: .onCreate, syncEnabled: true) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: MutationSync.self, - decodePath: document.decodePath) let operation = Amplify.API.subscribe(request: request) { event in switch event { case .inProcess(let graphQLResponse): @@ -252,20 +244,16 @@ class GraphQLSyncBasedTests: XCTestCase { // MARK: Helpers - func createPost(id: String, title: String) -> MutationSync? { + func createPost(id: String, title: String) -> MutationSyncResult? { let post = Post(id: id, title: title, content: "content", createdAt: Date()) return createPost(post: post) } - func createPost(post: AmplifyTestCommon.Post) -> MutationSync? { - var result: MutationSync? + func createPost(post: AmplifyTestCommon.Post) -> MutationSyncResult? { + var result: MutationSyncResult? let completeInvoked = expectation(description: "request completed") - let document = GraphQLSyncMutation(of: post, type: .create) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: MutationSync.self, - decodePath: document.decodePath) + let request = GraphQLRequest.createMutation(of: post) _ = Amplify.API.mutate(request: request, listener: { event in switch event { case .completed(let data): @@ -285,5 +273,4 @@ class GraphQLSyncBasedTests: XCTestCase { wait(for: [completeInvoked], timeout: TestCommonConstants.networkTimeout) return result } - } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift new file mode 100644 index 0000000000..f4a57fe6b3 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift @@ -0,0 +1,68 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +/// Adds conflict resolution information onto the document based on the operation type (query or mutation) +/// All selection sets are decorated with conflict resolution fields and inputs are added based on the values that it +/// was instantiated with. If `version` is passed, the input with key "input" will contain "_version" with the `version` +/// value. If `lastSync` is passed, the input will contain new key "lastSync" with the `lastSync` value. +public struct ConflictResolutionDecorator: ModelBasedGraphQLDocumentDecorator { + + private let version: Int? + private let lastSync: Int? + + public init(version: Int? = nil, lastSync: Int? = nil) { + self.version = version + self.lastSync = lastSync + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + var inputs = document.inputs + + if let version = version, + case .mutation = document.operationType, + var input = inputs["input"], + case var .object(value) = input.value { + + value["_version"] = version + input.value = .object(value) + inputs["input"] = input + } + + if let lastSync = lastSync, case .query = document.operationType { + inputs["lastSync"] = GraphQLDocumentInput(type: "AWSTimestamp", value: .scalar(lastSync)) + } + + if let selectionSet = document.selectionSet { + addConflictResolution(selectionSet: selectionSet) + return document.copy(inputs: inputs, selectionSet: selectionSet) + } + + return document.copy(inputs: inputs) + } + + /// Append the correct conflict resolution fields for `model` and `pagination` selection sets. + private func addConflictResolution(selectionSet: SelectionSet) { + switch selectionSet.value.fieldType { + case .value: + break + case .model: + selectionSet.addChild(settingParentOf: .init(value: .init(name: "_version", fieldType: .value))) + selectionSet.addChild(settingParentOf: .init(value: .init(name: "_deleted", fieldType: .value))) + selectionSet.addChild(settingParentOf: .init(value: .init(name: "_lastChangedAt", fieldType: .value))) + case .pagination: + selectionSet.addChild(settingParentOf: .init(value: .init(name: "startedAt", fieldType: .value))) + } + + selectionSet.children.forEach { child in + addConflictResolution(selectionSet: child) + } + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/DirectiveNameDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/DirectiveNameDecorator.swift new file mode 100644 index 0000000000..6c60a570ef --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/DirectiveNameDecorator.swift @@ -0,0 +1,55 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// Replaces the directive name of the GraphQL document based on Amplify GraphQL operation types such as "get", "list", +/// "sync", "create", "update", "delete", "onCreate", "onUpdate", and "onDelete". The GraphQL name is constructed based +/// on the data from the Model schema and the operation type. +public struct DirectiveNameDecorator: ModelBasedGraphQLDocumentDecorator { + + private let queryType: GraphQLQueryType? + private let mutationType: GraphQLMutationType? + private let subscriptionType: GraphQLSubscriptionType? + + public init(type: GraphQLQueryType) { + self.queryType = type + self.mutationType = nil + self.subscriptionType = nil + } + + public init(type: GraphQLMutationType) { + self.queryType = nil + self.mutationType = type + self.subscriptionType = nil + } + + public init(type: GraphQLSubscriptionType) { + self.queryType = nil + self.mutationType = nil + self.subscriptionType = type + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + + if let queryType = queryType { + return document.copy(name: modelType.schema.graphQLName(queryType: queryType)) + } + + if let mutationType = mutationType { + return document.copy(name: modelType.schema.graphQLName(mutationType: mutationType)) + } + + if let subscriptionType = subscriptionType { + return document.copy(name: modelType.schema.graphQLName(subscriptionType: subscriptionType)) + } + + return document + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelBasedGraphQLDocumentDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelBasedGraphQLDocumentDecorator.swift new file mode 100644 index 0000000000..4f582ef809 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelBasedGraphQLDocumentDecorator.swift @@ -0,0 +1,13 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +public protocol ModelBasedGraphQLDocumentDecorator { + func decorate(_ document: SingleDirectiveGraphQLDocument, modelType: Model.Type) -> SingleDirectiveGraphQLDocument +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift new file mode 100644 index 0000000000..6551de2eb6 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelDecorator.swift @@ -0,0 +1,29 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// Decorate the GraphQL document with the data from an instance of the model. This is added as a single parameter +/// called "input" that can be referenced by other decorators to append additional document inputs. This decorator +/// constructs the input's type using the document name. +public struct ModelDecorator: ModelBasedGraphQLDocumentDecorator { + + private let model: Model + + public init(model: Model) { + self.model = model + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + var inputs = document.inputs + inputs["input"] = GraphQLDocumentInput(type: "\(document.name.pascalCased())Input!", + value: .object(model.graphQLInput)) + return document.copy(inputs: inputs) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelIdDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelIdDecorator.swift new file mode 100644 index 0000000000..32a52812b5 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelIdDecorator.swift @@ -0,0 +1,33 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// Decorate the GraphQLDocument with the value of `Model.Identifier` for a "delete" mutation or "get" query. +public struct ModelIdDecorator: ModelBasedGraphQLDocumentDecorator { + + private let id: Model.Identifier + + public init(id: Model.Identifier) { + self.id = id + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + var inputs = document.inputs + + if case .mutation = document.operationType { + inputs["input"] = GraphQLDocumentInput(type: "\(document.name.pascalCased())Input!", + value: .object(["id": id])) + } else if case .query = document.operationType { + inputs["id"] = GraphQLDocumentInput(type: "ID!", value: .scalar(id)) + } + + return document.copy(inputs: inputs) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PaginationDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PaginationDecorator.swift new file mode 100644 index 0000000000..27f47e6d4b --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PaginationDecorator.swift @@ -0,0 +1,54 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// Decorate the document input with "limit" and "nextToken". Also paginates the selection set with pagination fields. +public struct PaginationDecorator: ModelBasedGraphQLDocumentDecorator { + + private let limit: Int? + private let nextToken: String? + + public init(limit: Int? = nil, nextToken: String? = nil) { + self.limit = limit + self.nextToken = nextToken + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + var inputs = document.inputs + + if let limit = limit { + inputs["limit"] = GraphQLDocumentInput(type: "Int", value: .scalar(limit)) + } else { + inputs["limit"] = GraphQLDocumentInput(type: "Int", value: .scalar(1_000)) + } + + if let nextToken = nextToken { + inputs["nextToken"] = GraphQLDocumentInput(type: "String", value: .scalar(nextToken)) + } + + if let selectionSet = document.selectionSet { + + return document.copy(inputs: inputs, + selectionSet: withPagination(selectionSet: selectionSet)) + } + + return document.copy(inputs: inputs) + } + + /// Wrap the selectionSet with a pagination selection set, + func withPagination(selectionSet: SelectionSet) -> SelectionSet { + let paginatedNode = SelectionSetField(fieldType: .pagination) + let newRoot = SelectionSet(value: paginatedNode) + selectionSet.value.name = "items" + newRoot.addChild(settingParentOf: selectionSet) + newRoot.addChild(settingParentOf: SelectionSet(value: SelectionSetField(name: "nextToken", fieldType: .value))) + return newRoot + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PredicateDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PredicateDecorator.swift new file mode 100644 index 0000000000..63b3b46562 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PredicateDecorator.swift @@ -0,0 +1,35 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +/// Decorates a GraphQL mutation with a "condition" input or a GraphQL query with a "filter" input. The value is the +/// data extracted from an instance of a `QueryPredicate` +public struct PredicateDecorator: ModelBasedGraphQLDocumentDecorator { + + private let predicate: QueryPredicate + + public init(predicate: QueryPredicate) { + self.predicate = predicate + } + + public func decorate(_ document: SingleDirectiveGraphQLDocument, + modelType: Model.Type) -> SingleDirectiveGraphQLDocument { + var inputs = document.inputs + let modelName = modelType.schema.name + if case .mutation = document.operationType { + inputs["condition"] = GraphQLDocumentInput(type: "Model\(modelName)ConditionInput", + value: .object(predicate.graphQLFilterVariables)) + } else if case .query = document.operationType { + inputs["filter"] = GraphQLDocumentInput(type: "Model\(modelName)FilterInput", + value: .object(predicate.graphQLFilterVariables)) + } + + return document.copy(inputs: inputs) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+DeleteSyncMutation.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+DeleteSyncMutation.swift deleted file mode 100644 index 0de9d281eb..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+DeleteSyncMutation.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -/// A convenience implementation of `GraphQLDocument` that represents a delete operation, requiring no model data -public class GraphQLDeleteSyncMutation: GraphQLDocument { - public let documentType = GraphQLDocumentType.mutation - public let mutationType = GraphQLMutationType.delete - - public let modelName: String - public let modelType: Model.Type - public let id: Model.Identifier - public let version: Int? - - public init(of modelName: String, id: Model.Identifier, version: Int?) throws { - self.modelName = modelName - self.id = id - self.version = version - - guard let modelType = ModelRegistry.modelType(from: modelName) else { - throw DataStoreError.invalidModelName(modelName) - } - self.modelType = modelType - } - - public var name: String { - mutationType.rawValue + modelName - } - - public var decodePath: String { - name - } - - public var hasSyncableModels: Bool { - true - } - - public var stringValue: String { - let mutationName = name.pascalCased() - let inputName = "input" - let inputType = "\(mutationName)Input!" - - let document = """ - \(documentType) \(mutationName)($\(inputName): \(inputType)) { - \(name)(\(inputName): $\(inputName)) { - \(selectionSetFields.joined(separator: "\n ")) - } - } - """ - - return document - } - - public var variables: [String: Any] { - var graphQLInput = ["id": id] as [String: Any?] - if let version = version { - graphQLInput.updateValue(version, forKey: "_version") - } - - return [ - "input": graphQLInput - ] - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+GetQuery.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+GetQuery.swift deleted file mode 100644 index 9b272973f0..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+GetQuery.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -/// A concrete implementation of `GraphQLDocument` that represents a query operation. -/// Queries can either return a single (`.get`) or mutiple (`.list`) results -/// as defined by `GraphQLQueryType`. -public struct GraphQLGetQuery: GraphQLDocument { - - public let documentType = GraphQLDocumentType.query - public let modelType: Model.Type - public let id: String - public let syncEnabled: Bool - - public init(from modelType: Model.Type, - id: String, - syncEnabled: Bool = false) { - self.modelType = modelType - self.id = id - self.syncEnabled = syncEnabled - } - - public var name: String { - return "get" + modelType.schema.graphQLName - } - - public var decodePath: String { - return name - } - - public var hasSyncableModels: Bool { - return syncEnabled - } - - public var stringValue: String { - - let input = "$id: ID!" - let inputName = "id: $id" - - let fields = selectionSetFields - let documentFields = fields.joined(separator: "\n ") - let queryName = name.pascalCased() - - return """ - \(documentType) \(queryName)(\(input)) { - \(name)(\(inputName)) { - \(documentFields) - } - } - """ - } - - public var variables: [String: Any] { - return ["id": id] - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+ListQuery.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+ListQuery.swift deleted file mode 100644 index 27758b5c6c..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+ListQuery.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -/// A concrete implementation of `GraphQLDocument` that represents a query operation. -/// Queries can either return a single (`.get`) or mutiple (`.list`) results -/// as defined by `GraphQLQueryType`. -public class GraphQLListQuery: GraphQLDocument { - - public let documentType = GraphQLDocumentType.query - public let modelType: Model.Type - public let predicate: QueryPredicate? - public let limit: Int? - public let nextToken: String? - public let syncEnabled: Bool - - public init(from modelType: Model.Type, - predicate: QueryPredicate? = nil, - limit: Int? = nil, - nextToken: String? = nil, - syncEnabled: Bool = false) { - self.modelType = modelType - self.predicate = predicate - self.limit = limit - self.nextToken = nextToken - self.syncEnabled = syncEnabled - } - - public var name: String { - return "list" + modelType.schema.graphQLName + "s" - } - - public var decodePath: String { - return name + ".items" - } - - public var hasSyncableModels: Bool { - return syncEnabled - } - - public var stringValue: String { - let schema = modelType.schema - - let input = "$filter: Model\(schema.graphQLName)FilterInput, $limit: Int, $nextToken: String" - let inputName = "filter: $filter, limit: $limit, nextToken: $nextToken" - - let fields = selectionSetFields - var documentFields = fields.joined(separator: "\n ") - documentFields = - """ - items { - \(fields.joined(separator: "\n ")) - } - nextToken - """ - - let queryName = name.pascalCased() - - return """ - \(documentType) \(queryName)(\(input)) { - \(name)(\(inputName)) { - \(documentFields) - } - } - """ - } - - public var variables: [String: Any] { - var variables = [String: Any]() - - if let predicate = predicate { - variables.updateValue(predicate.graphQLFilterVariables, forKey: "filter") - } - - if let limit = limit { - variables.updateValue(limit, forKey: "limit") - } else { - // TODO: Remove this once we support limit and nextToken passed in from the developer - variables.updateValue(1_000, forKey: "limit") - } - - if let nextToken = nextToken { - variables.updateValue(nextToken, forKey: "nextToken") - } - - return variables - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Mutation.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Mutation.swift deleted file mode 100644 index 34786c8c09..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Mutation.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -public extension GraphQLMutation { - convenience init(of anyModel: AnyModel, type mutationType: GraphQLMutationType) { - self.init(of: anyModel.instance, type: mutationType) - } -} - -/// A concrete implementation of `GraphQLDocument` that represents a data mutation operation. -/// The type of the operation is defined by `GraphQLMutationType`. -public class GraphQLMutation: GraphQLDocument { - - public let documentType = GraphQLDocumentType.mutation - public let model: Model - public let modelType: Model.Type - public let mutationType: GraphQLMutationType - - public init(of model: Model, - type mutationType: GraphQLMutationType) { - self.model = model - self.modelType = ModelRegistry.modelType(from: model.modelName) ?? Swift.type(of: model) - self.mutationType = mutationType - } - - public var name: String { - mutationType.rawValue + model.schema.graphQLName - } - - public var decodePath: String { - name - } - - public var hasSyncableModels: Bool { - false - } - - public var stringValue: String { - let mutationName = name.pascalCased() - let inputName = "input" - let inputType = "\(mutationName)Input!" - - let document = """ - \(documentType) \(mutationName)($\(inputName): \(inputType)) { - \(name)(\(inputName): $\(inputName)) { - \(selectionSetFields.joined(separator: "\n ")) - } - } - """ - - return document - } - - public var variables: [String: Any] { - if mutationType == .delete { - return [ - "input": [ - "id": model.id - ] - ] - } else { - return [ - "input": model.graphQLInput - ] - } - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Subscription.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Subscription.swift deleted file mode 100644 index dfbec06d72..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+Subscription.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -/// A concrete implementation of `GraphQLDocument` that represents a subscription operation. -/// Subscriptions are triggered when specific operations happen on the defined `Model`. -/// These operations are defined by `GraphQLSubscriptionType`. -public struct GraphQLSubscription: GraphQLDocument { - - public let documentType = GraphQLDocumentType.subscription - public let modelType: Model.Type - public let subscriptionType: GraphQLSubscriptionType - public let syncEnabled: Bool - - public init(of modelType: Model.Type, - type subscriptionType: GraphQLSubscriptionType, - syncEnabled: Bool = false) { - self.modelType = modelType - self.subscriptionType = subscriptionType - self.syncEnabled = syncEnabled - } - - public var name: String { - subscriptionType.rawValue + modelType.schema.graphQLName - } - - public var decodePath: String { - name - } - - public var hasSyncableModels: Bool { - return syncEnabled - } - - public var stringValue: String { - let subscriptionName = name.pascalCased() - - let document = """ - \(documentType) \(subscriptionName) { - \(name) { - \(selectionSetFields.joined(separator: "\n ")) - } - } - """ - - return document - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncMutation.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncMutation.swift deleted file mode 100644 index 61d88ecc34..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncMutation.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -public class GraphQLSyncMutation: GraphQLMutation { - - public let version: Int? - - public init(of model: Model, type mutationType: GraphQLMutationType, version: Int? = nil) { - self.version = version - super.init(of: model, type: mutationType) - } - - public override var hasSyncableModels: Bool { - return true - } - - public override var variables: [String: Any] { - - if mutationType == .delete { - var graphQLInput = ["id": model.id] as GraphQLInput - if let version = version { - graphQLInput.updateValue(version, forKey: "_version") - } - - return [ - "input": graphQLInput - ] - } else { - var graphQLInput = model.graphQLInput - if let version = version { - graphQLInput.updateValue(version, forKey: "_version") - } - return [ - "input": graphQLInput - ] - } - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncQuery.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncQuery.swift deleted file mode 100644 index 273abb70f0..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument+SyncQuery.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -public class GraphQLSyncQuery: GraphQLListQuery { - - public let lastSync: Int? - - public init(from modelType: Model.Type, - predicate: QueryPredicate? = nil, - limit: Int? = nil, - nextToken: String? = nil, - lastSync: Int? = nil) { - self.lastSync = lastSync - super.init(from: modelType, - predicate: predicate, - limit: limit, - nextToken: nextToken) - } - - public override var name: String { - // Right now plural is not consistent on the GraphQL transformer. - // `.sync` queries use proper plural resolution. - // Change this once the transformer fixes that behavior - var modelName = modelType.schema.graphQLName - modelName = modelType.schema.pluralName ?? (modelName + "s") - - return "sync" + modelName - } - - public override var decodePath: String { - return name - } - - public override var hasSyncableModels: Bool { - true - } - - public override var stringValue: String { - let schema = modelType.schema - - let input = """ - $filter: Model\(schema.graphQLName)FilterInput, $limit: Int, $nextToken: String, $lastSync: AWSTimestamp - """ - - let inputName = "filter: $filter, limit: $limit, nextToken: $nextToken, lastSync: $lastSync" - - let fields = selectionSetFields - var documentFields = fields.joined(separator: "\n ") - documentFields = - """ - items { - \(fields.joined(separator: "\n ")) - } - nextToken - startedAt - """ - - let queryName = name.pascalCased() - - return """ - \(documentType) \(queryName)(\(input)) { - \(name)(\(inputName)) { - \(documentFields) - } - } - """ - } - - public override var variables: [String: Any] { - var variables = super.variables - - if let lastSync = lastSync { - variables.updateValue(lastSync, forKey: "lastSync") - } - - return variables - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument.swift deleted file mode 100644 index 317009c715..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLDocument.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -public enum GraphQLDocumentType: String { - case mutation - case query - case subscription -} - -/// Represents a GraphQL document. Concrete types that conform to this protocol must -/// define a valid GraphQL operation document. -/// -/// This type aims to provide an integration between GraphQL and an Amplify `Model`. -/// Therefore, documents represented by concrete implementations provide a single GraphQL -/// operation based on a defined `Model`. -public protocol GraphQLDocument: DataStoreStatement where Variables == [String: Any] { - - /// The `GraphQLDocumentType` a concrete implementation represents the - /// GraphQL operation of the document - var documentType: GraphQLDocumentType { get } - - /// The name of the document. This is useful to inspect the response, since it will - /// contain the name of the document as the key to the response value. - var name: String { get } - - /// The state of the model's backend provisioning, whether it is provisioned with conflict resolution or not. - var hasSyncableModels: Bool { get } -} - -extension GraphQLDocument { - - /// Provides a default empty value to variables so that implementation - /// becomes optional to document types that don't need to pass variables. - public var variables: [String: Any] { - return [:] - } - - /// Resolve the fields that should be included in the selection set for the `modelType`. - /// Associated models will be included if they are required and they are the owning - /// side of the association. - /// - /// - Note: Currently implementation assumes the most common and efficient queries. - /// Future APIs might allow user customization of the selected fields. - public var selectionSetFields: [String] { - var fieldSet = [String]() - let schema = modelType.schema - - var indentSize = 0 - - func appendFields(_ fields: [ModelField]) { - let indent = indentSize == 0 ? "" : String(repeating: " ", count: indentSize) - fields.forEach { field in - let isRequiredAssociation = field.isRequired && field.isAssociationOwner - if isRequiredAssociation, let associatedModel = field.associatedModel { - fieldSet.append(indent + field.name + " {") - indentSize += 1 - appendFields(associatedModel.schema.graphQLFields) - indentSize -= 1 - fieldSet.append(indent + "}") - } else { - fieldSet.append(indent + field.graphQLName) - } - } - fieldSet.append(indent + "__typename") - if hasSyncableModels { - fieldSet.append(indent + "_version") - fieldSet.append(indent + "_deleted") - fieldSet.append(indent + "_lastChangedAt") - } - } - appendFields(schema.graphQLFields) - return fieldSet - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLMutation.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLMutation.swift new file mode 100644 index 0000000000..02132eeb79 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLMutation.swift @@ -0,0 +1,35 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// A concrete implementation of `SingleDirectiveGraphQLDocument` that represents a mutation operation. +public struct GraphQLMutation: SingleDirectiveGraphQLDocument { + + public init(operationType: GraphQLOperationType, + name: String, + inputs: [GraphQLParameterName: GraphQLDocumentInput], + selectionSet: SelectionSet?) { + self.operationType = operationType + self.name = name + self.inputs = inputs + self.selectionSet = selectionSet + } + + public init(modelType: Model.Type) { + self.selectionSet = SelectionSet(fields: modelType.schema.graphQLFields) + } + + public var name: String = "" + + public var operationType: GraphQLOperationType = .mutation + + public var inputs: [GraphQLParameterName: GraphQLDocumentInput] = [:] + + public var selectionSet: SelectionSet? +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLQuery.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLQuery.swift new file mode 100644 index 0000000000..e79fd4a10c --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLQuery.swift @@ -0,0 +1,35 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// A concrete implementation of `SingleDirectiveGraphQLDocument` that represents a query operation. +public struct GraphQLQuery: SingleDirectiveGraphQLDocument { + + public init(operationType: GraphQLOperationType, + name: String, + inputs: [GraphQLParameterName: GraphQLDocumentInput], + selectionSet: SelectionSet?) { + self.operationType = operationType + self.name = name + self.inputs = inputs + self.selectionSet = selectionSet + } + + public init(modelType: Model.Type) { + self.selectionSet = SelectionSet(fields: modelType.schema.graphQLFields) + } + + public var name: String = "" + + public var operationType: GraphQLOperationType = .query + + public var inputs: [GraphQLParameterName: GraphQLDocumentInput] = [:] + + public var selectionSet: SelectionSet? +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLSubscription.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLSubscription.swift new file mode 100644 index 0000000000..fab7791fd3 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/GraphQLSubscription.swift @@ -0,0 +1,35 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +/// A concrete implementation of `SingleDirectiveGraphQLDocument` that represents a subscription operation. +public struct GraphQLSubscription: SingleDirectiveGraphQLDocument { + + public init(operationType: GraphQLOperationType, + name: String, + inputs: [GraphQLParameterName: GraphQLDocumentInput], + selectionSet: SelectionSet?) { + self.operationType = operationType + self.name = name + self.inputs = inputs + self.selectionSet = selectionSet + } + + public init(modelType: Model.Type) { + self.selectionSet = SelectionSet(fields: modelType.schema.graphQLFields) + } + + public var operationType: GraphQLOperationType = .subscription + + public var name: String = "" + + public var inputs: [GraphQLParameterName: GraphQLDocumentInput] = [:] + + public var selectionSet: SelectionSet? +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/ModelBasedGraphQLDocumentBuilder.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/ModelBasedGraphQLDocumentBuilder.swift new file mode 100644 index 0000000000..845cecab2b --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/ModelBasedGraphQLDocumentBuilder.swift @@ -0,0 +1,50 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +/// Helps construct a `SingleDirectiveGraphQLDocument`. Collects instances of the decorators and applies the changes +/// on the document. +public struct ModelBasedGraphQLDocumentBuilder { + private var decorators = [ModelBasedGraphQLDocumentDecorator]() + private var document: SingleDirectiveGraphQLDocument + private let modelType: Model.Type + + public init(modelName: String, operationType: GraphQLOperationType) { + guard let modelType = ModelRegistry.modelType(from: modelName) else { + preconditionFailure("Missing ModelType in ModelRegistry for model name: \(modelName)") + } + + self.init(modelType: modelType, operationType: operationType) + } + + public init(modelType: Model.Type, operationType: GraphQLOperationType) { + self.modelType = modelType + switch operationType { + case .query: + self.document = GraphQLQuery(modelType: modelType) + case .mutation: + self.document = GraphQLMutation(modelType: modelType) + case .subscription: + self.document = GraphQLSubscription(modelType: modelType) + } + } + + public mutating func add(decorator: ModelBasedGraphQLDocumentDecorator) { + decorators.append(decorator) + } + + public mutating func build() -> SingleDirectiveGraphQLDocument { + + let decoratedDocument = decorators.reduce(document) { doc, decorator in + decorator.decorate(doc, modelType: self.modelType) + } + + return decoratedDocument + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift new file mode 100644 index 0000000000..11ada77fea --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift @@ -0,0 +1,109 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +public enum GraphQLOperationType: String { + case mutation + case query + case subscription +} + +public typealias GraphQLParameterName = String + +/// Represents a single directive GraphQL document. Concrete types that conform to this protocol must +/// define a valid GraphQL operation document. +/// +/// This type aims to provide a representation of a simple GraphQL document with its components that can be easily +/// decorated to extend the document. The components can then derive the standardized form of a GraphQL document +/// containing the query string and variables. +public protocol SingleDirectiveGraphQLDocument { + /// The `GraphQLOperationType` a concrete implementation represents the + /// GraphQL operation of the document + var operationType: GraphQLOperationType { get set } + + /// The name of the document. This is useful to inspect the response, since it will + /// contain the name of the document as the key to the response value. + var name: String { get set } + + /// Input parameters and its values on the GraphQL document + var inputs: [GraphQLParameterName: GraphQLDocumentInput] { get set } + + /// The selection set of the document, used to specify the response data returned by the service. + var selectionSet: SelectionSet? { get set } + + /// Simple constructor to be implemented by the concrete types, used by the `copy` method. + init(operationType: GraphQLOperationType, + name: String, + inputs: [GraphQLParameterName: GraphQLDocumentInput], + selectionSet: SelectionSet?) +} + +// Provides default implementation +extension SingleDirectiveGraphQLDocument { + + /// Method to create a deep copy of the document, useful for `ModelBasedGraphQLDocumentDecorator` decorators + /// when decorating a document and returning a new document. + public func copy(operationType: GraphQLOperationType? = nil, + name: String? = nil, + inputs: [GraphQLParameterName: GraphQLDocumentInput]? = nil, + selectionSet: SelectionSet? = nil) -> Self { + + return Self.init(operationType: operationType ?? self.operationType, + name: name ?? self.name, + inputs: inputs ?? self.inputs, + selectionSet: selectionSet ?? self.selectionSet) + } + + /// Returns an empty `variables` object when there are no `inputs` required, Otherwise, consolidates the `inputs` + /// into a single object that can be used for the GraphQL request. + public var variables: [String: Any] { + var variables = [String: Any]() + inputs.forEach { input in + switch input.value.value { + case .object(let values): + variables.updateValue(values, forKey: input.key) + case .scalar(let value): + variables.updateValue(value, forKey: input.key) + } + + } + + return variables + } + + /// Provides default construction of the graphQL document based on the components of the document. + public var stringValue: String { + + var selectionSetString = "" + if let selectionSet = selectionSet { + selectionSetString = selectionSet.stringValue() + } + + guard !inputs.isEmpty else { + return """ + \(operationType.rawValue) \(name.pascalCased()) { + \(name) { + \(selectionSetString) + } + } + """ + } + let sortedInputs = inputs.sorted { $0.0 < $1.0 } + let inputTypes = sortedInputs.map { "$\($0.key): \($0.value.type)" }.joined(separator: ", ") + let inputParameters = sortedInputs.map { "\($0.key): $\($0.key)" }.joined(separator: ", ") + + return """ + \(operationType.rawValue) \(name.pascalCased())(\(inputTypes)) { + \(name)(\(inputParameters)) { + \(selectionSetString) + } + } + """ + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModel.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModel.swift deleted file mode 100644 index 6d67349650..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModel.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Foundation - -public extension GraphQLRequest { - /// Gets a GraphQLRequest for an `AnyModel`'s underlying instance type. The generated GraphQL document will reflect - /// the structure and variables of `AnyModel.instance`, but the return value will be erased to `AnyModel`, allowing - /// it to be collected. - static func mutation(of anyModel: AnyModel, - type: GraphQLMutationType) -> GraphQLRequest { - let document = GraphQLMutation(of: anyModel, type: type) - - return GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: AnyModel.self, - decodePath: document.decodePath) - } - - static func subscription(toAnyModelType modelType: Model.Type, - subscriptionType: GraphQLSubscriptionType) -> GraphQLRequest { - let document = GraphQLSubscription(of: modelType, type: subscriptionType) - return GraphQLRequest(document: document.stringValue, - responseType: AnyModel.self, - decodePath: document.decodePath) - - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift new file mode 100644 index 0000000000..2ab51d1365 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift @@ -0,0 +1,102 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +public typealias SyncQueryResult = PaginatedList +public typealias MutationSyncResult = MutationSync + +/// Extension methods that are useful for `DataStore`. The methods consist of conflict resolution related fields such +/// as `version` and `lastSync` and returns a model that has been erased to `AnyModel`. +extension GraphQLRequest { + + public static func createMutation(of model: Model, + version: Int? = nil) -> GraphQLRequest { + createOrUpdateMutation(of: model, type: .create, version: version) + } + + public static func updateMutation(of model: Model, + where predicate: QueryPredicate? = nil, + version: Int? = nil) -> GraphQLRequest { + createOrUpdateMutation(of: model, type: .update, version: version) + } + + public static func deleteMutation(modelName: String, + id: Model.Identifier, + where predicate: QueryPredicate? = nil, + version: Int? = nil) -> GraphQLRequest { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: modelName, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete)) + documentBuilder.add(decorator: ModelIdDecorator(id: id)) + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + documentBuilder.add(decorator: ConflictResolutionDecorator(version: version)) + let document = documentBuilder.build() + + return GraphQLRequest(document: document.stringValue, + variables: document.variables, + responseType: MutationSyncResult.self, + decodePath: document.name) + } + + public static func subscription(to modelType: Model.Type, + subscriptionType: GraphQLSubscriptionType) -> GraphQLRequest { + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: subscriptionType)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() + + return GraphQLRequest(document: document.stringValue, + responseType: MutationSyncResult.self, + decodePath: document.name) + } + + public static func syncQuery(modelType: Model.Type, + where predicate: QueryPredicate? = nil, + limit: Int? = nil, + nextToken: String? = nil, + lastSync: Int? = nil) -> GraphQLRequest { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken)) + documentBuilder.add(decorator: ConflictResolutionDecorator(lastSync: lastSync)) + let document = documentBuilder.build() + + return GraphQLRequest(document: document.stringValue, + variables: document.variables, + responseType: SyncQueryResult.self, + decodePath: document.name) + } + + // MARK: Private methods + + private static func createOrUpdateMutation(of model: Model, + where predicate: QueryPredicate? = nil, + type: GraphQLMutationType, + version: Int? = nil) -> GraphQLRequest { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: model.modelName, + operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) + documentBuilder.add(decorator: ModelDecorator(model: model)) + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + documentBuilder.add(decorator: ConflictResolutionDecorator(version: version)) + let document = documentBuilder.build() + + return GraphQLRequest(document: document.stringValue, + variables: document.variables, + responseType: MutationSyncResult.self, + decodePath: document.name) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift index 6636ec53c6..e5333a2dfc 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift @@ -10,26 +10,44 @@ import Foundation /// Extension that provides an integration layer between `Model`, `GraphQLDocument` and `GraphQLRequest`. /// This is particularly useful when using the GraphQL API to interact with static types that conform to -/// the `Model` protocol, also used by `DataStore`. +/// the `Model` protocol. extension GraphQLRequest { /// Creates a `GraphQLRequest` that represents a mutation of a given `type` for a `model` instance. - /// The request will be created with the correct document based on the `ModelSchema` and - /// variables based on the model instance. /// /// - Parameters: /// - model: the model instance populated with values - /// - type: the mutation type, either `.create`, `.update` or `.delete` + /// - predicate: a predicate passed as the condition to apply the mutation + /// - type: the mutation type, either `.create`, `.update`, or `.delete` /// - Returns: the `GraphQLRequest` ready to be used - /// - /// - seealso: `GraphQLMutation`, `GraphQLMutationType` public static func mutation(of model: M, + where predicate: QueryPredicate? = nil, type: GraphQLMutationType) -> GraphQLRequest { - let document = GraphQLMutation(of: model, type: type) + let modelType = ModelRegistry.modelType(from: model.modelName) ?? Swift.type(of: model) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) + + switch type { + case .create: + documentBuilder.add(decorator: ModelDecorator(model: model)) + case .delete: + documentBuilder.add(decorator: ModelIdDecorator(id: model.id)) + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + case .update: + documentBuilder.add(decorator: ModelDecorator(model: model)) + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + } + + let document = documentBuilder.build() return GraphQLRequest(document: document.stringValue, variables: document.variables, responseType: M.self, - decodePath: document.decodePath) + decodePath: document.name) } /// Creates a `GraphQLRequest` that represents a query that expects a single value as a result. @@ -44,11 +62,15 @@ extension GraphQLRequest { /// - seealso: `GraphQLQuery`, `GraphQLQueryType.get` public static func query(from modelType: M.Type, byId id: String) -> GraphQLRequest { - let document = GraphQLGetQuery(from: modelType, id: id) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: id)) + let document = documentBuilder.build() + return GraphQLRequest(document: document.stringValue, variables: document.variables, responseType: M?.self, - decodePath: document.decodePath) + decodePath: document.name) } /// Creates a `GraphQLRequest` that represents a query that expects multiple values as a result. @@ -63,11 +85,20 @@ extension GraphQLRequest { /// - seealso: `GraphQLQuery`, `GraphQLQueryType.list` public static func query(from modelType: M.Type, where predicate: QueryPredicate? = nil) -> GraphQLRequest<[M]> { - let document = GraphQLListQuery(from: modelType, predicate: predicate) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .list)) + + if let predicate = predicate { + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + } + + documentBuilder.add(decorator: PaginationDecorator()) + let document = documentBuilder.build() + return GraphQLRequest<[M]>(document: document.stringValue, variables: document.variables, responseType: [M].self, - decodePath: document.decodePath) + decodePath: document.name + ".items") } /// Creates a `GraphQLRequest` that represents a subscription of a given `type` for a `model` type. @@ -81,10 +112,12 @@ extension GraphQLRequest { /// - seealso: `GraphQLSubscription`, `GraphQLSubscriptionType` public static func subscription(of modelType: M.Type, type: GraphQLSubscriptionType) -> GraphQLRequest { - let document = GraphQLSubscription(of: modelType, type: type) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) + let document = documentBuilder.build() + return GraphQLRequest(document: document.stringValue, responseType: modelType, - decodePath: document.decodePath) + decodePath: document.name) } - } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInput.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInput.swift new file mode 100644 index 0000000000..348f00733e --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInput.swift @@ -0,0 +1,21 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// Contains the `type` of the GraphQL document input parameter as a string value and `GraphQLDocumentInputValue` +public struct GraphQLDocumentInput { + + public var type: String + + public var value: GraphQLDocumentInputValue + + public init(type: String, value: GraphQLDocumentInputValue) { + self.type = type + self.value = value + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentnputValue.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentnputValue.swift new file mode 100644 index 0000000000..3f08a7e0e2 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentnputValue.swift @@ -0,0 +1,43 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// A container to hold either an object or a value, useful for storing document inputs and allowing manipulation at +/// the first level of the object +public enum GraphQLDocumentInputValue { + case scalar(GraphQLDocumentValueRepresentable) + case object([String: Any?]) +} + +public protocol GraphQLDocumentValueRepresentable { + var graphQLDocumentValue: String { get } +} + +extension Int: GraphQLDocumentValueRepresentable { + public var graphQLDocumentValue: String { + return "\(self)" + } +} + +extension String: GraphQLDocumentValueRepresentable { + public var graphQLDocumentValue: String { + return self + } +} + +extension Bool: GraphQLDocumentValueRepresentable { + public var graphQLDocumentValue: String { + return "\(self)" + } +} + +extension Decimal: GraphQLDocumentValueRepresentable { + public var graphQLDocumentValue: String { + return "\(self)" + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelField+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelField+GraphQL.swift new file mode 100644 index 0000000000..36a71e422f --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelField+GraphQL.swift @@ -0,0 +1,21 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +/// Extension that adds GraphQL specific utilities to `ModelField`. +extension ModelField { + + /// The GraphQL name of the field. + var graphQLName: String { + if isAssociationOwner, case let .belongsTo(_, targetName) = association { + return targetName ?? name.pascalCased() + "Id" + } + return name + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift index 15c6683d1f..faf2372e01 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelSchema+GraphQL.swift @@ -11,9 +11,33 @@ import Foundation /// Extension that adds GraphQL specific utilities to `ModelSchema`. extension ModelSchema { - /// The GraphQL name of the schema. - var graphQLName: String { - name + /// The GraphQL directive name translated from a GraphQL query operation and model schema data + func graphQLName(queryType: GraphQLQueryType) -> String { + let graphQLName: String + switch queryType { + case .list: + graphQLName = (queryType.rawValue + name).pluralize() + case .sync: + if let pluralName = pluralName { + graphQLName = queryType.rawValue + pluralName + } else { + graphQLName = (queryType.rawValue + name).pluralize() + } + case .get: + graphQLName = queryType.rawValue + name + } + + return graphQLName + } + + /// The GraphQL directive name translated from a GraphQL subsription operation and model schema name + func graphQLName(subscriptionType: GraphQLSubscriptionType) -> String { + subscriptionType.rawValue + name + } + + /// The GraphQL directive name translated from a GraphQL mutation operation and model schema name + func graphQLName(mutationType: GraphQLMutationType) -> String { + mutationType.rawValue + name } /// The list of fields formatted for GraphQL usage. @@ -22,17 +46,4 @@ extension ModelSchema { !field.hasAssociation || field.isAssociationOwner } } - -} - -/// Extension that adds GraphQL specific utilities to `ModelField`. -extension ModelField { - - /// The GraphQL name of the field. - var graphQLName: String { - if isAssociationOwner, case let .belongsTo(_, targetName) = association { - return targetName ?? name.pascalCased() + "Id" - } - return name - } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift index 1cd99d42cb..84529331da 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift @@ -14,6 +14,7 @@ protocol GraphQLFilterConvertible { var graphQLFilter: GraphQLFilter { get } } +/// Extension to translate a `QueryPredicate` into a GraphQL query variables object extension QueryPredicate { var graphQLFilterVariables: [String: Any] { if let operation = self as? QueryPredicateOperation { diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift new file mode 100644 index 0000000000..3ab118720b --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/SelectionSet.swift @@ -0,0 +1,94 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +public typealias SelectionSet = Tree + +public enum SelectionSetFieldType { + case pagination + case model + case value +} + +public class SelectionSetField { + var name: String? + var fieldType: SelectionSetFieldType + public init(name: String? = nil, fieldType: SelectionSetFieldType) { + self.name = name + self.fieldType = fieldType + } +} + +extension SelectionSet { + + /// Construct a `SelectionSet` with model fields + convenience init(fields: [ModelField]) { + self.init(value: SelectionSetField(fieldType: .model)) + withModelFields(fields) + } + + func withModelFields(_ fields: [ModelField]) { + fields.forEach { field in + let isRequiredAssociation = field.isRequired && field.isAssociationOwner + if isRequiredAssociation, let associatedModel = field.associatedModel { + let child = SelectionSet(value: .init(name: field.name, fieldType: .model)) + child.withModelFields(associatedModel.schema.graphQLFields) + self.addChild(settingParentOf: child) + } else { + self.addChild(settingParentOf: .init(value: .init(name: field.graphQLName, fieldType: .value))) + } + } + + addChild(settingParentOf: .init(value: .init(name: "__typename", fieldType: .value))) + } + + /// Generate the string value of the `SelectionSet` used in the GraphQL query document + /// + /// This method operates on `SelectionSet` with the root node containing a nil `value.name` and expects all inner + /// nodes to contain a value. It will generate a string with a nested and indented structure like: + /// ``` + /// items { + /// foo + /// bar + /// modelName { + /// foo + /// bar + /// } + /// } + /// nextToken + /// startAt + /// ``` + func stringValue(indentSize: Int = 0) -> String { + var result = [String]() + let indent = indentSize == 0 ? "" : String(repeating: " ", count: indentSize) + + // Account for the root node, + if let name = value.name { + result.append(indent + name) + } + + children.forEach { selectionSetField in + guard let name = selectionSetField.value.name else { + return + } + + if !selectionSetField.children.isEmpty { + result.append(indent + name + " {") + selectionSetField.children.forEach { innerSelectionSetField in + result.append(innerSelectionSetField.stringValue(indentSize: indentSize + 1)) + } + result.append(indent + "}") + } else { + result.append(indent + name) + } + } + + return result.joined(separator: "\n ") + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncMutationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLCreateMutationTests.swift similarity index 63% rename from AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncMutationTests.swift rename to AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLCreateMutationTests.swift index b281fa8e36..5e5399a311 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncMutationTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLCreateMutationTests.swift @@ -6,11 +6,12 @@ // import XCTest + @testable import Amplify @testable import AmplifyTestCommon @testable import AWSPluginsCore -class GraphQLSyncMutationTests: XCTestCase { +class GraphQLCreateMutationTests: XCTestCase { override func setUp() { ModelRegistry.register(modelType: Comment.self) @@ -33,7 +34,10 @@ class GraphQLSyncMutationTests: XCTestCase { /// - it has a list of fields with no nested models func testCreateGraphQLMutationFromSimpleModel() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLSyncMutation(of: post, type: .create, version: 5) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + let document = documentBuilder.build() let expectedQueryDocument = """ mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { @@ -45,14 +49,10 @@ class GraphQLSyncMutationTests: XCTestCase { title updatedAt __typename - _version - _deleted - _lastChangedAt } } """ XCTAssertEqual(document.name, "createPost") - XCTAssertEqual(document.decodePath, "createPost") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.name, "createPost") XCTAssertNotNil(document.variables["input"]) @@ -62,7 +62,6 @@ class GraphQLSyncMutationTests: XCTestCase { } XCTAssert(input["title"] as? String == post.title) XCTAssert(input["content"] as? String == post.content) - XCTAssert(input["_version"] as? Int == 5) } /// - Given: a `Model` instance @@ -78,7 +77,10 @@ class GraphQLSyncMutationTests: XCTestCase { func testCreateGraphQLMutationFromModelWithAssociation() { let post = Post(title: "title", content: "content", createdAt: Date()) let comment = Comment(content: "comment", createdAt: Date(), post: post) - let document = GraphQLSyncMutation(of: comment, type: .create, version: 5) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: comment)) + let document = documentBuilder.build() let expectedQueryDocument = """ mutation CreateComment($input: CreateCommentInput!) { createComment(input: $input) { @@ -94,19 +96,12 @@ class GraphQLSyncMutationTests: XCTestCase { title updatedAt __typename - _version - _deleted - _lastChangedAt } __typename - _version - _deleted - _lastChangedAt } } """ XCTAssertEqual(document.name, "createComment") - XCTAssertEqual(document.decodePath, "createComment") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.name, "createComment") guard let input = document.variables["input"] as? GraphQLInput else { @@ -114,25 +109,28 @@ class GraphQLSyncMutationTests: XCTestCase { return } XCTAssertEqual(input["commentPostId"] as? String, post.id) - XCTAssert(input["_version"] as? Int == 5) } /// - Given: a `Model` instance /// - When: /// - the model is of type `Post` /// - the model has no required associations - /// - the mutation is of type `.update` + /// - the mutation is of type `.create` /// - Then: /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `updatePost` - /// - it contains an `input` of type `UpdatePostInput` + /// - it is named `createPost` + /// - it contains an `input` of type `CreatePostInput` /// - it has a list of fields with no nested models - func testUpdateGraphQLMutationFromSimpleModel() { + func testCreateGraphQLMutationFromSimpleModelWithSyncEnabled() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLSyncMutation(of: post, type: .update, version: 5) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ - mutation UpdatePost($input: UpdatePostInput!) { - updatePost(input: $input) { + mutation CreatePost($input: CreatePostInput!) { + createPost(input: $input) { id content createdAt @@ -147,10 +145,9 @@ class GraphQLSyncMutationTests: XCTestCase { } } """ - XCTAssertEqual(document.name, "updatePost") - XCTAssertEqual(document.decodePath, "updatePost") + XCTAssertEqual(document.name, "createPost") XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "updatePost") + XCTAssertEqual(document.name, "createPost") XCTAssertNotNil(document.variables["input"]) guard let input = document.variables["input"] as? [String: Any] else { XCTFail("The document variables property doesn't contain a valid input") @@ -158,32 +155,46 @@ class GraphQLSyncMutationTests: XCTestCase { } XCTAssert(input["title"] as? String == post.title) XCTAssert(input["content"] as? String == post.content) - XCTAssert(input["_version"] as? Int == 5) } /// - Given: a `Model` instance /// - When: - /// - the model is of type `Post` - /// - the model has no required associations - /// - the mutation is of type `.delete` + /// - the model is of type `Comment` + /// - the model has required associations + /// - the mutation is of type `.create` /// - Then: /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `deletePost` - /// - it contains an `input` of type `ID!` - /// - it has a list of fields with no nested models - func testDeleteGraphQLMutationFromSimpleModel() { + /// - it is named `createComment` + /// - it contains an `input` of type `CreateCommentInput` + /// - it has a list of fields with a `postId` + func testCreateGraphQLMutationFromModelWithAssociationWithSyncEnabled() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLSyncMutation(of: post, type: .delete, version: 5) + let comment = Comment(content: "comment", createdAt: Date(), post: post) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: comment)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ - mutation DeletePost($input: DeletePostInput!) { - deletePost(input: $input) { + mutation CreateComment($input: CreateCommentInput!) { + createComment(input: $input) { id content createdAt - draft - rating - title - updatedAt + post { + id + content + createdAt + draft + rating + title + updatedAt + __typename + _version + _deleted + _lastChangedAt + } __typename _version _deleted @@ -191,16 +202,13 @@ class GraphQLSyncMutationTests: XCTestCase { } } """ - XCTAssertEqual(document.name, "deletePost") - XCTAssertEqual(document.decodePath, "deletePost") + XCTAssertEqual(document.name, "createComment") XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "deletePost") - XCTAssert(document.variables["input"] != nil) - guard let input = document.variables["input"] as? [String: Any] else { - XCTFail("Could not get object at `input`") + XCTAssertEqual(document.name, "createComment") + guard let input = document.variables["input"] as? GraphQLInput else { + XCTFail("Variables should contain a valid input") return } - XCTAssert(input["id"] as? String == post.id) - XCTAssert(input["_version"] as? Int == 5) + XCTAssertEqual(input["commentPostId"] as? String, post.id) } } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDeleteMutationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDeleteMutationTests.swift new file mode 100644 index 0000000000..081de08eb9 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDeleteMutationTests.swift @@ -0,0 +1,111 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest + +@testable import Amplify +@testable import AmplifyTestCommon +@testable import AWSPluginsCore + +class GraphQLDeleteMutationTests: XCTestCase { + + override func setUp() { + ModelRegistry.register(modelType: Comment.self) + ModelRegistry.register(modelType: Post.self) + } + + override func tearDown() { + ModelRegistry.reset() + } + + /// - Given: a `Model` instance + /// - When: + /// - the model is of type `Post` + /// - the model has no required associations + /// - the mutation is of type `.delete` + /// - Then: + /// - check if the generated GraphQL document is a valid mutation: + /// - it is named `deletePost` + /// - it contains an `input` of type `ID!` + /// - it has a list of fields with no nested models + func testDeleteGraphQLMutationFromSimpleModel() { + let post = Post(title: "title", content: "content", createdAt: Date()) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete)) + documentBuilder.add(decorator: ModelIdDecorator(id: post.id)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + mutation DeletePost($input: DeletePostInput!) { + deletePost(input: $input) { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "deletePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.name, "deletePost") + XCTAssert(document.variables["input"] != nil) + guard let input = document.variables["input"] as? [String: String] else { + XCTFail("Could not get object at `input`") + return + } + XCTAssertEqual(input["id"], post.id) + } + + /// - Given: a `Model` instance + /// - When: + /// - the model is of type `Post` + /// - the model has no required associations + /// - the mutation is of type `.delete` + /// - Then: + /// - check if the generated GraphQL document is a valid mutation: + /// - it is named `deletePost` + /// - it contains an `input` of type `ID!` + /// - it has a list of fields with no nested models + func testDeleteGraphQLMutationFromSimpleModelWithVersion() { + let post = Post(title: "title", content: "content", createdAt: Date()) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete)) + documentBuilder.add(decorator: ModelIdDecorator(id: post.id)) + documentBuilder.add(decorator: ConflictResolutionDecorator(version: 5)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + mutation DeletePost($input: DeletePostInput!) { + deletePost(input: $input) { + id + content + createdAt + draft + rating + title + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + } + """ + XCTAssertEqual(document.name, "deletePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.name, "deletePost") + XCTAssert(document.variables["input"] != nil) + guard let input = document.variables["input"] as? [String: Any] else { + XCTFail("Could not get object at `input`") + return + } + XCTAssert(input["id"] as? String == post.id) + XCTAssert(input["_version"] as? Int == 5) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDocumentTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDocumentTests.swift deleted file mode 100644 index 9499258bb1..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLDocumentTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import XCTest -@testable import Amplify -@testable import AmplifyTestCommon -@testable import AWSPluginsCore - -class GraphQLDocumentTests: XCTestCase { - - override func setUp() { - ModelRegistry.register(modelType: Comment.self) - ModelRegistry.register(modelType: Post.self) - } - - override func tearDown() { - ModelRegistry.reset() - } - - func testSelectionSetFieldsForNotSyncableModels() { - let document = GraphQLGetQuery(from: Post.self, id: "id", syncEnabled: false) - let expectedSelectionSet = ["id", - "content", - "createdAt", - "draft", - "rating", - "title", - "updatedAt", - "__typename"] - - XCTAssertEqual(document.selectionSetFields, expectedSelectionSet) - } - - func testSelectionSetFieldsForSyncableModels() { - let document = GraphQLGetQuery(from: Post.self, id: "id", syncEnabled: true) - let expectedSelectionSet = ["id", - "content", - "createdAt", - "draft", - "rating", - "title", - "updatedAt", - "__typename", - "_version", - "_deleted", - "_lastChangedAt"] - - XCTAssertEqual(document.selectionSetFields, expectedSelectionSet) - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLGetQueryTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLGetQueryTests.swift index d7f62fd438..8ac2c1c077 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLGetQueryTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLGetQueryTests.swift @@ -30,7 +30,35 @@ class GraphQLGetQueryTests: XCTestCase { /// - it has a list of fields with no nested models /// - it has variables containing `id` func testGetGraphQLQueryFromSimpleModel() { - let document = GraphQLGetQuery(from: Post.self, id: "id", syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: "id")) + let document = documentBuilder.build() + let expectedQueryDocument = """ + query GetPost($id: ID!) { + getPost(id: $id) { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "getPost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables["id"] as? String, "id") + } + + func testGetGraphQLQueryFromSimpleModelWithSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: "id")) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ query GetPost($id: ID!) { getPost(id: $id) { @@ -49,7 +77,6 @@ class GraphQLGetQueryTests: XCTestCase { } """ XCTAssertEqual(document.name, "getPost") - XCTAssertEqual(document.decodePath, "getPost") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables["id"] as? String, "id") } @@ -65,7 +92,41 @@ class GraphQLGetQueryTests: XCTestCase { /// - it is named `getComment` /// - it has a list of fields with a nested `post` func testGetGraphQLQueryFromModelWithAssociation() { - let document = GraphQLGetQuery(from: Comment.self, id: "id", syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: "id")) + let document = documentBuilder.build() + let expectedQueryDocument = """ + query GetComment($id: ID!) { + getComment(id: $id) { + id + content + createdAt + post { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + __typename + } + } + """ + XCTAssertEqual(document.name, "getComment") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables["id"] as? String, "id") + } + + func testGetGraphQLQueryFromModelWithAssociationAndSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: "id")) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ query GetComment($id: ID!) { getComment(id: $id) { @@ -93,9 +154,7 @@ class GraphQLGetQueryTests: XCTestCase { } """ XCTAssertEqual(document.name, "getComment") - XCTAssertEqual(document.decodePath, "getComment") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables["id"] as? String, "id") } - } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLListQueryTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLListQueryTests.swift index e60f7c5611..ea4f5b516d 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLListQueryTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLListQueryTests.swift @@ -36,10 +36,50 @@ class GraphQLListQueryTests: XCTestCase { func testListGraphQLQueryFromSimpleModel() { let post = Post.keys let predicate = post.id.eq("id") && (post.title.beginsWith("Title") || post.content.contains("content")) - let document = GraphQLListQuery(from: Post.self, predicate: predicate, syncEnabled: true) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .list)) + documentBuilder.add(decorator: PaginationDecorator()) + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + query ListPosts($filter: ModelPostFilterInput, $limit: Int) { + listPosts(filter: $filter, limit: $limit) { + items { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + nextToken + } + } + """ + XCTAssertEqual(document.name, "listPosts") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertNotNil(document.variables) + XCTAssertNotNil(document.variables["limit"]) + XCTAssertEqual(document.variables["limit"] as? Int, 1_000) + XCTAssertNotNil(document.variables["filter"]) + } + + func testListGraphQLQueryFromSimpleModelWithSyncEnabled() { + let post = Post.keys + let predicate = post.id.eq("id") && (post.title.beginsWith("Title") || post.content.contains("content")) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .list)) + documentBuilder.add(decorator: PaginationDecorator()) + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ - query ListPosts($filter: ModelPostFilterInput, $limit: Int, $nextToken: String) { - listPosts(filter: $filter, limit: $limit, nextToken: $nextToken) { + query ListPosts($filter: ModelPostFilterInput, $limit: Int) { + listPosts(filter: $filter, limit: $limit) { items { id content @@ -54,16 +94,15 @@ class GraphQLListQueryTests: XCTestCase { _lastChangedAt } nextToken + startedAt } } """ XCTAssertEqual(document.name, "listPosts") - XCTAssertEqual(document.decodePath, "listPosts.items") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertNotNil(document.variables) XCTAssertNotNil(document.variables["limit"]) XCTAssertEqual(document.variables["limit"] as? Int, 1_000) XCTAssertNotNil(document.variables["filter"]) } - } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLMutationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLMutationTests.swift deleted file mode 100644 index 1c10f97524..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLMutationTests.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import XCTest - -@testable import Amplify -@testable import AmplifyTestCommon -@testable import AWSPluginsCore - -class GraphQLMutationTests: XCTestCase { - - override func setUp() { - ModelRegistry.register(modelType: Comment.self) - ModelRegistry.register(modelType: Post.self) - } - - override func tearDown() { - ModelRegistry.reset() - } - - /// - Given: a `Model` instance - /// - When: - /// - the model is of type `Post` - /// - the model has no required associations - /// - the mutation is of type `.create` - /// - Then: - /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `createPost` - /// - it contains an `input` of type `CreatePostInput` - /// - it has a list of fields with no nested models - func testCreateGraphQLMutationFromSimpleModel() { - let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .create) - let expectedQueryDocument = """ - mutation CreatePost($input: CreatePostInput!) { - createPost(input: $input) { - id - content - createdAt - draft - rating - title - updatedAt - __typename - } - } - """ - XCTAssertEqual(document.name, "createPost") - XCTAssertEqual(document.decodePath, "createPost") - XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "createPost") - XCTAssertNotNil(document.variables["input"]) - guard let input = document.variables["input"] as? [String: Any] else { - XCTFail("The document variables property doesn't contain a valid input") - return - } - XCTAssert(input["title"] as? String == post.title) - XCTAssert(input["content"] as? String == post.content) - } - - /// - Given: a `Model` instance - /// - When: - /// - the model is of type `Comment` - /// - the model has required associations - /// - the mutation is of type `.create` - /// - Then: - /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `createComment` - /// - it contains an `input` of type `CreateCommentInput` - /// - it has a list of fields with a `postId` - func testCreateGraphQLMutationFromModelWithAssociation() { - let post = Post(title: "title", content: "content", createdAt: Date()) - let comment = Comment(content: "comment", createdAt: Date(), post: post) - let document = GraphQLMutation(of: comment, type: .create) - let expectedQueryDocument = """ - mutation CreateComment($input: CreateCommentInput!) { - createComment(input: $input) { - id - content - createdAt - post { - id - content - createdAt - draft - rating - title - updatedAt - __typename - } - __typename - } - } - """ - XCTAssertEqual(document.name, "createComment") - XCTAssertEqual(document.decodePath, "createComment") - XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "createComment") - guard let input = document.variables["input"] as? GraphQLInput else { - XCTFail("Variables should contain a valid input") - return - } - XCTAssertEqual(input["commentPostId"] as? String, post.id) - } - - /// - Given: a `Model` instance - /// - When: - /// - the model is of type `Post` - /// - the model has no required associations - /// - the mutation is of type `.update` - /// - Then: - /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `updatePost` - /// - it contains an `input` of type `UpdatePostInput` - /// - it has a list of fields with no nested models - func testUpdateGraphQLMutationFromSimpleModel() { - let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .update) - let expectedQueryDocument = """ - mutation UpdatePost($input: UpdatePostInput!) { - updatePost(input: $input) { - id - content - createdAt - draft - rating - title - updatedAt - __typename - } - } - """ - XCTAssertEqual(document.name, "updatePost") - XCTAssertEqual(document.decodePath, "updatePost") - XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "updatePost") - XCTAssertNotNil(document.variables["input"]) - guard let input = document.variables["input"] as? [String: Any] else { - XCTFail("The document variables property doesn't contain a valid input") - return - } - XCTAssert(input["title"] as? String == post.title) - XCTAssert(input["content"] as? String == post.content) - } - - /// - Given: a `Model` instance - /// - When: - /// - the model is of type `Post` - /// - the model has no required associations - /// - the mutation is of type `.delete` - /// - Then: - /// - check if the generated GraphQL document is a valid mutation: - /// - it is named `deletePost` - /// - it contains an `input` of type `ID!` - /// - it has a list of fields with no nested models - func testDeleteGraphQLMutationFromSimpleModel() { - let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .delete) - let expectedQueryDocument = """ - mutation DeletePost($input: DeletePostInput!) { - deletePost(input: $input) { - id - content - createdAt - draft - rating - title - updatedAt - __typename - } - } - """ - XCTAssertEqual(document.name, "deletePost") - XCTAssertEqual(document.decodePath, "deletePost") - XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.name, "deletePost") - XCTAssert(document.variables["input"] != nil) - guard let input = document.variables["input"] as? [String: String] else { - XCTFail("Could not get object at `input`") - return - } - XCTAssertEqual(input["id"], post.id) - } -} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSubscriptionTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSubscriptionTests.swift index 7f6b109240..1bb96fd15d 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSubscriptionTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSubscriptionTests.swift @@ -32,7 +32,33 @@ class GraphQLSubscriptionTests: XCTestCase { /// - check if the generated GraphQL document is a valid subscription /// - it has a list of fields with no nested models func testOnCreateGraphQLSubscriptionFromSimpleModel() { - let document = GraphQLSubscription(of: Post.self, type: .onCreate, syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + subscription OnCreatePost { + onCreatePost { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "onCreatePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables.count, 0) + } + + func testOnCreateGraphQLSubscriptionFromSimpleModelWithSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ subscription OnCreatePost { onCreatePost { @@ -51,10 +77,8 @@ class GraphQLSubscriptionTests: XCTestCase { } """ XCTAssertEqual(document.name, "onCreatePost") - XCTAssertEqual(document.decodePath, "onCreatePost") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables.count, 0) - } /// - Given: a `Model` type @@ -66,7 +90,39 @@ class GraphQLSubscriptionTests: XCTestCase { /// - check if the generated GraphQL document is a valid subscription /// - it has a list of fields with no nested models func testOnCreateGraphQLSubscriptionFromModelWithAssociation() { - let document = GraphQLSubscription(of: Comment.self, type: .onCreate, syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + subscription OnCreateComment { + onCreateComment { + id + content + createdAt + post { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + __typename + } + } + """ + XCTAssertEqual(document.name, "onCreateComment") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables.count, 0) + } + + func testOnCreateGraphQLSubscriptionFromModelWithAssociationWithSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Comment.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ subscription OnCreateComment { onCreateComment { @@ -94,7 +150,6 @@ class GraphQLSubscriptionTests: XCTestCase { } """ XCTAssertEqual(document.name, "onCreateComment") - XCTAssertEqual(document.decodePath, "onCreateComment") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables.count, 0) } @@ -107,7 +162,34 @@ class GraphQLSubscriptionTests: XCTestCase { /// - check if the generated GraphQL document is a valid subscription /// - it has a list of fields with no nested models func testOnUpdateGraphQLSubscriptionFromSimpleModel() { - let document = GraphQLSubscription(of: Post.self, type: .onUpdate, syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onUpdate)) + let document = documentBuilder.build() + + let expectedQueryDocument = """ + subscription OnUpdatePost { + onUpdatePost { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "onUpdatePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables.count, 0) + } + + func testOnUpdateGraphQLSubscriptionFromSimpleModelWithSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onUpdate)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ subscription OnUpdatePost { onUpdatePost { @@ -126,7 +208,6 @@ class GraphQLSubscriptionTests: XCTestCase { } """ XCTAssertEqual(document.name, "onUpdatePost") - XCTAssertEqual(document.decodePath, "onUpdatePost") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables.count, 0) } @@ -139,7 +220,33 @@ class GraphQLSubscriptionTests: XCTestCase { /// - check if the generated GraphQL document is a valid subscription /// - it has a list of fields with no nested models func testOnDeleteGraphQLSubscriptionFromSimpleModel() { - let document = GraphQLSubscription(of: Post.self, type: .onDelete, syncEnabled: true) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onDelete)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + subscription OnDeletePost { + onDeletePost { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "onDeletePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.variables.count, 0) + } + + func testOnDeleteGraphQLSubscriptionFromSimpleModelWithSyncEnabled() { + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onDelete)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() let expectedQueryDocument = """ subscription OnDeletePost { onDeletePost { @@ -158,9 +265,7 @@ class GraphQLSubscriptionTests: XCTestCase { } """ XCTAssertEqual(document.name, "onDeletePost") - XCTAssertEqual(document.decodePath, "onDeletePost") XCTAssertEqual(document.stringValue, expectedQueryDocument) XCTAssertEqual(document.variables.count, 0) } - } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift index 6540be2e15..8d6d0094a4 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift @@ -35,14 +35,16 @@ class GraphQLSyncQueryTests: XCTestCase { func testSyncGraphQLQueryFromSimpleModel() { let post = Post.keys let predicate = post.id.eq("id") && (post.title.beginsWith("Title") || post.content.contains("content")) - let document = GraphQLSyncQuery(from: Post.self, - predicate: predicate, - limit: 100, - nextToken: "token", - lastSync: 123) + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + documentBuilder.add(decorator: PaginationDecorator(limit: 100, nextToken: "token")) + documentBuilder.add(decorator: ConflictResolutionDecorator(lastSync: 123)) + let document = documentBuilder.build() let expectedQueryDocument = """ - query SyncPosts($filter: ModelPostFilterInput, $limit: Int, $nextToken: String, $lastSync: AWSTimestamp) { - syncPosts(filter: $filter, limit: $limit, nextToken: $nextToken, lastSync: $lastSync) { + query SyncPosts($filter: ModelPostFilterInput, $lastSync: AWSTimestamp, $limit: Int, $nextToken: String) { + syncPosts(filter: $filter, lastSync: $lastSync, limit: $limit, nextToken: $nextToken) { items { id content @@ -62,7 +64,7 @@ class GraphQLSyncQueryTests: XCTestCase { } """ XCTAssertEqual(document.stringValue, expectedQueryDocument) - XCTAssertEqual(document.decodePath, "syncPosts") + XCTAssertEqual(document.name, "syncPosts") XCTAssertNotNil(document.variables) XCTAssertNotNil(document.variables["limit"]) XCTAssertEqual(document.variables["limit"] as? Int, 100) diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLUpdateMutationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLUpdateMutationTests.swift new file mode 100644 index 0000000000..03bec73957 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLUpdateMutationTests.swift @@ -0,0 +1,113 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest + +@testable import Amplify +@testable import AmplifyTestCommon +@testable import AWSPluginsCore + +class GraphQLUpdateMutationTests: XCTestCase { + + override func setUp() { + ModelRegistry.register(modelType: Comment.self) + ModelRegistry.register(modelType: Post.self) + } + + override func tearDown() { + ModelRegistry.reset() + } + + /// - Given: a `Model` instance + /// - When: + /// - the model is of type `Post` + /// - the model has no required associations + /// - the mutation is of type `.update` + /// - Then: + /// - check if the generated GraphQL document is a valid mutation: + /// - it is named `updatePost` + /// - it contains an `input` of type `UpdatePostInput` + /// - it has a list of fields with no nested models + func testUpdateGraphQLMutationFromSimpleModel() { + let post = Post(title: "title", content: "content", createdAt: Date()) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .update)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + mutation UpdatePost($input: UpdatePostInput!) { + updatePost(input: $input) { + id + content + createdAt + draft + rating + title + updatedAt + __typename + } + } + """ + XCTAssertEqual(document.name, "updatePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.name, "updatePost") + XCTAssertNotNil(document.variables["input"]) + guard let input = document.variables["input"] as? [String: Any] else { + XCTFail("The document variables property doesn't contain a valid input") + return + } + XCTAssert(input["title"] as? String == post.title) + XCTAssert(input["content"] as? String == post.content) + } + + /// - Given: a `Model` instance + /// - When: + /// - the model is of type `Post` + /// - the model has no required associations + /// - the mutation is of type `.update` + /// - Then: + /// - check if the generated GraphQL document is a valid mutation: + /// - it is named `updatePost` + /// - it contains an `input` of type `UpdatePostInput` + /// - it has a list of fields with no nested models + func testUpdateGraphQLMutationFromSimpleModelWithVersion() { + let post = Post(title: "title", content: "content", createdAt: Date()) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .update)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + documentBuilder.add(decorator: ConflictResolutionDecorator(version: 5)) + let document = documentBuilder.build() + let expectedQueryDocument = """ + mutation UpdatePost($input: UpdatePostInput!) { + updatePost(input: $input) { + id + content + createdAt + draft + rating + title + updatedAt + __typename + _version + _deleted + _lastChangedAt + } + } + """ + XCTAssertEqual(document.name, "updatePost") + XCTAssertEqual(document.stringValue, expectedQueryDocument) + XCTAssertEqual(document.name, "updatePost") + XCTAssertNotNil(document.variables["input"]) + guard let input = document.variables["input"] as? [String: Any] else { + XCTFail("The document variables property doesn't contain a valid input") + return + } + XCTAssert(input["title"] as? String == post.title) + XCTAssert(input["content"] as? String == post.content) + XCTAssert(input["_version"] as? Int == 5) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelTests.swift deleted file mode 100644 index 9e1014a3a0..0000000000 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelTests.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import XCTest -@testable import Amplify -@testable import AmplifyTestCommon -@testable import AWSPluginsCore - -class GraphQLRequestAnyModelTests: XCTestCase { - - override func setUp() { - ModelRegistry.register(modelType: Comment.self) - ModelRegistry.register(modelType: Post.self) - } - - override func tearDown() { - ModelRegistry.reset() - } - - // MARK: - Mutations - - func testCreateGraphQLMutationFromSimpleModel() throws { - let originalPost = Post(title: "title", content: "content", createdAt: Date()) - let anyPost = try originalPost.eraseToAnyModel() - - let document = GraphQLMutation(of: anyPost, type: .create) - - XCTAssertEqual(document.name, "createPost") - XCTAssertEqual(document.decodePath, "createPost") - XCTAssertNotNil(document.stringValue) - XCTAssertNotNil(document.variables["input"]) - } - - func testUpdateGraphQLMutationFromSimpleModel() throws { - let originalPost = Post(title: "title", content: "content", createdAt: Date()) - let anyPost = try originalPost.eraseToAnyModel() - - let document = GraphQLMutation(of: anyPost, type: .update) - - XCTAssertEqual(document.name, "updatePost") - XCTAssertEqual(document.decodePath, "updatePost") - XCTAssertNotNil(document.stringValue) - XCTAssertEqual(document.name, "updatePost") - XCTAssertNotNil(document.variables["input"]) - } - - func testDeleteGraphQLMutationFromSimpleModel() throws { - let originalPost = Post(title: "title", content: "content", createdAt: Date()) - let anyPost = try originalPost.eraseToAnyModel() - let document = GraphQLMutation(of: anyPost, type: .delete) - - XCTAssertEqual(document.name, "deletePost") - XCTAssertEqual(document.decodePath, "deletePost") - XCTAssertNotNil(document.stringValue) - XCTAssertNotNil(document.variables["input"]) - XCTAssert(document.variables["input"] != nil) - guard let input = document.variables["input"] as? [String: String] else { - XCTFail("Could not get object at `input`") - return - } - XCTAssertEqual(input["id"], originalPost.id) - } - - // MARK: - GraphQLRequest+AnyModel - - func testCreateMutationGraphQLRequest() throws { - let originalPost = Post(title: "title", content: "content", createdAt: Date()) - let anyPost = try originalPost.eraseToAnyModel() - let document = GraphQLMutation(of: anyPost, type: .create) - let request = GraphQLRequest.mutation(of: anyPost, type: .create) - - XCTAssertEqual(document.stringValue, request.document) - XCTAssert(request.responseType == AnyModel.self) - - // test the input - XCTAssert(request.variables != nil) - } - - func testCreateSubscriptionGraphQLRequest() throws { - let modelType = Post.self as Model.Type - let document = GraphQLSubscription(of: modelType, type: .onCreate) - let request = GraphQLRequest.subscription(toAnyModelType: modelType, - subscriptionType: .onCreate) - - XCTAssertEqual(document.stringValue, request.document) - XCTAssert(request.responseType == AnyModel.self) - - // test the input - XCTAssertNil(request.variables) - } - -} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift new file mode 100644 index 0000000000..7977a9ddd1 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift @@ -0,0 +1,110 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import Amplify +@testable import AmplifyTestCommon +@testable import AWSPluginsCore + +class GraphQLRequestAnyModelWithSyncTests: XCTestCase { + + override func setUp() { + ModelRegistry.register(modelType: Comment.self) + ModelRegistry.register(modelType: Post.self) + } + + override func tearDown() { + ModelRegistry.reset() + } + + func testCreateMutationGraphQLRequest() throws { + let originalPost = Post(title: "title", content: "content", createdAt: Date()) + let anyPost = try originalPost.eraseToAnyModel() + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: anyPost.modelName, + operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: anyPost)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() + + let request = GraphQLRequest.createMutation(of: anyPost) + + XCTAssertEqual(document.stringValue, request.document) + XCTAssert(request.responseType == MutationSyncResult.self) + XCTAssert(request.variables != nil) + } + + func testUpdateMutationGraphQLRequest() throws { + let originalPost = Post(title: "title", content: "content", createdAt: Date()) + let anyPost = try originalPost.eraseToAnyModel() + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: anyPost.modelName, + operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .update)) + documentBuilder.add(decorator: ModelDecorator(model: anyPost)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() + + let request = GraphQLRequest.updateMutation(of: anyPost) + + XCTAssertEqual(document.stringValue, request.document) + XCTAssert(request.responseType == MutationSyncResult.self) + XCTAssert(request.variables != nil) + } + + func testDeleteMutationGraphQLRequest() throws { + let originalPost = Post(title: "title", content: "content", createdAt: Date()) + let anyPost = try originalPost.eraseToAnyModel() + + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: anyPost.modelName, + operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete)) + documentBuilder.add(decorator: ModelIdDecorator(id: anyPost.id)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() + + let request = GraphQLRequest.deleteMutation(modelName: anyPost.modelName, id: anyPost.id) + + XCTAssertEqual(document.stringValue, request.document) + XCTAssert(request.responseType == MutationSyncResult.self) + XCTAssert(request.variables != nil) + } + + func testCreateSubscriptionGraphQLRequest() throws { + let modelType = Post.self as Model.Type + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + documentBuilder.add(decorator: ConflictResolutionDecorator()) + let document = documentBuilder.build() + + let request = GraphQLRequest.subscription(to: modelType, + subscriptionType: .onCreate) + + XCTAssertEqual(document.stringValue, request.document) + XCTAssert(request.responseType == MutationSyncResult.self) + XCTAssertNil(request.variables) + } + + func testSyncQueryGraphQLRequest() throws { + let modelType = Post.self as Model.Type + let nextToken = "nextToken" + let limit = 100 + let lastSync = 123 + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) + documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken)) + documentBuilder.add(decorator: ConflictResolutionDecorator(lastSync: lastSync)) + let document = documentBuilder.build() + + let request = GraphQLRequest.syncQuery(modelType: modelType, + nextToken: nextToken, + lastSync: lastSync) + + XCTAssertEqual(document.stringValue, request.document) + XCTAssert(request.responseType == SyncQueryResult.self) + XCTAssert(request.variables != nil) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestModelTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestModelTests.swift index 6591eda80a..1c7b957eff 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestModelTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestModelTests.swift @@ -33,7 +33,10 @@ class GraphQLRequestModelTests: XCTestCase { /// - the `variables` is non-nil func testCreateMutationGraphQLRequest() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .create) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .create)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + let document = documentBuilder.build() let request = GraphQLRequest.mutation(of: post, type: .create) @@ -44,7 +47,10 @@ class GraphQLRequestModelTests: XCTestCase { func testUpdateMutationGraphQLRequest() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .update) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .update)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + let document = documentBuilder.build() let request = GraphQLRequest.mutation(of: post, type: .update) @@ -55,7 +61,10 @@ class GraphQLRequestModelTests: XCTestCase { func testDeleteMutationGraphQLRequest() { let post = Post(title: "title", content: "content", createdAt: Date()) - let document = GraphQLMutation(of: post, type: .delete) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .mutation) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete)) + documentBuilder.add(decorator: ModelDecorator(model: post)) + let document = documentBuilder.build() let request = GraphQLRequest.mutation(of: post, type: .delete) @@ -65,7 +74,10 @@ class GraphQLRequestModelTests: XCTestCase { } func testQueryByIdGraphQLRequest() { - let document = GraphQLGetQuery(from: Post.self, id: "id") + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) + documentBuilder.add(decorator: ModelIdDecorator(id: "id")) + let document = documentBuilder.build() let request = GraphQLRequest.query(from: Post.self, byId: "id") @@ -78,14 +90,23 @@ class GraphQLRequestModelTests: XCTestCase { let post = Post.keys let predicate = post.id.eq("id") && (post.title.beginsWith("Title") || post.content.contains("content")) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .query) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .list)) + documentBuilder.add(decorator: PredicateDecorator(predicate: predicate)) + documentBuilder.add(decorator: PaginationDecorator()) + let document = documentBuilder.build() + let request = GraphQLRequest.query(from: Post.self, where: predicate) + XCTAssertEqual(document.stringValue, request.document) XCTAssert(request.responseType == [Post].self) XCTAssertNotNil(request.variables) } func testOnCreateSubscriptionGraphQLRequest() { - let document = GraphQLSubscription(of: Post.self, type: .onCreate) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate)) + let document = documentBuilder.build() let request = GraphQLRequest.subscription(of: Post.self, type: .onCreate) @@ -95,7 +116,9 @@ class GraphQLRequestModelTests: XCTestCase { } func testOnUpdateSubscriptionGraphQLRequest() { - let document = GraphQLSubscription(of: Post.self, type: .onUpdate) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onUpdate)) + let document = documentBuilder.build() let request = GraphQLRequest.subscription(of: Post.self, type: .onUpdate) @@ -104,7 +127,9 @@ class GraphQLRequestModelTests: XCTestCase { } func testOnDeleteSubscriptionGraphQLRequest() { - let document = GraphQLSubscription(of: Post.self, type: .onDelete) + var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: Post.self, operationType: .subscription) + documentBuilder.add(decorator: DirectiveNameDecorator(type: .onDelete)) + let document = documentBuilder.build() let request = GraphQLRequest.subscription(of: Post.self, type: .onDelete) diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift index cd2ede7948..dbcd3d0994 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -86,14 +86,9 @@ final class InitialSyncOperation: AsynchronousOperation { return } - let document = GraphQLSyncQuery(from: modelType, - nextToken: nextToken, - lastSync: lastSyncTime) - - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: SyncQueryResult.self, - decodePath: document.decodePath) + let request = GraphQLRequest.syncQuery(modelType: modelType, + nextToken: nextToken, + lastSync: lastSyncTime) _ = api.query(request: request) { asyncEvent in switch asyncEvent { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift index 81df246d5b..f842fee584 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift @@ -92,10 +92,17 @@ class SyncMutationToCloudOperation: Operation { func createAPIRequest(mutationType: GraphQLMutationType) -> GraphQLRequest>? { let request: GraphQLRequest> do { - if mutationType == .delete { - request = try deleteRequest(for: mutationEvent) - } else { - request = try createOrUpdateRequest(for: mutationEvent, mutationType: mutationType) + switch mutationType { + case .delete: + request = GraphQLRequest.deleteMutation(modelName: mutationEvent.modelName, + id: mutationEvent.id, + version: mutationEvent.version) + case .update: + let model = try mutationEvent.decodeModel() + request = GraphQLRequest.updateMutation(of: model, version: mutationEvent.version) + case .create: + let model = try mutationEvent.decodeModel() + request = GraphQLRequest.createMutation(of: model, version: mutationEvent.version) } } catch { let apiError = APIError.unknown("Couldn't decode model", "", error) @@ -120,31 +127,6 @@ class SyncMutationToCloudOperation: Operation { } } - private func deleteRequest(for mutationEvent: MutationEvent) - throws -> GraphQLRequest> { - let document = try GraphQLDeleteSyncMutation(of: mutationEvent.modelName, - id: mutationEvent.modelId, - version: mutationEvent.version) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: MutationSync.self, - decodePath: document.decodePath) - return request - } - - private func createOrUpdateRequest(for mutationEvent: MutationEvent, mutationType: GraphQLMutationType) - throws -> GraphQLRequest> { - let model = try mutationEvent.decodeModel() - let document = GraphQLSyncMutation(of: model, - type: mutationType, - version: mutationEvent.version) - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: MutationSync.self, - decodePath: document.decodePath) - return request - } - private func validateResponseFromCloud(asyncEvent: AsyncEvent>, APIError>, request: GraphQLRequest>) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift index 9059571c62..e2d7e32a14 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift @@ -77,15 +77,9 @@ final class IncomingAsyncSubscriptionEventPublisher { api: APICategoryGraphQLBehavior, listener: @escaping GraphQLSubscriptionOperation.EventListener) -> GraphQLSubscriptionOperation { - let document = GraphQLSubscription(of: modelType, - type: subscriptionType, - syncEnabled: true) - - let request = GraphQLRequest(document: document.stringValue, - variables: document.variables, - responseType: Payload.self, - decodePath: document.decodePath) + let request = GraphQLRequest.subscription(to: modelType, + subscriptionType: subscriptionType) let operation = api.subscribe(request: request, listener: listener) return operation } diff --git a/AmplifyPlugins/Storage/AWSS3StoragePluginTests/Mocks/MockAWSS3TransferUtility.swift b/AmplifyPlugins/Storage/AWSS3StoragePluginTests/Mocks/MockAWSS3TransferUtility.swift index a0700a0b79..c4a96ad1b6 100644 --- a/AmplifyPlugins/Storage/AWSS3StoragePluginTests/Mocks/MockAWSS3TransferUtility.swift +++ b/AmplifyPlugins/Storage/AWSS3StoragePluginTests/Mocks/MockAWSS3TransferUtility.swift @@ -29,7 +29,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { downloadDataCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -45,7 +45,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, nil, nil, error) } else { completionHandler(task, nil, Data(), nil) @@ -65,7 +65,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { downloadToURLCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -77,7 +77,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, nil, nil, error) } else { completionHandler(task, nil, Data(), nil) @@ -99,7 +99,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { uploadDataCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -111,7 +111,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, error) } else { completionHandler(task, nil) @@ -131,7 +131,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { -> AWSTask { uploadFileCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -143,7 +143,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, error) } else { completionHandler(task, nil) @@ -165,7 +165,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { multiPartUploadFileCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -177,7 +177,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, error) } else { completionHandler(task, nil) @@ -198,7 +198,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { multiPartUploadDataCalled += 1 - if let error = self.errorOnContinuation { + if let error = errorOnContinuation { let resultWithError = AWSTask.init(error: error) return resultWithError } @@ -210,7 +210,7 @@ public class MockAWSS3TransferUtility: AWSS3TransferUtilityBehavior { } if let completionHandler = completionHandler { - if let error = self.errorOnCompletion { + if let error = errorOnCompletion { completionHandler(task, error) } else { completionHandler(task, nil) diff --git a/AmplifyTestCommon/Mocks/MockPredictionsCategoryPlugin.swift b/AmplifyTestCommon/Mocks/MockPredictionsCategoryPlugin.swift index 1ea045f2db..6672b1db72 100644 --- a/AmplifyTestCommon/Mocks/MockPredictionsCategoryPlugin.swift +++ b/AmplifyTestCommon/Mocks/MockPredictionsCategoryPlugin.swift @@ -35,14 +35,14 @@ class MockPredictionsCategoryPlugin: MessageReporter, PredictionsCategoryPlugin return MockPredictionsTranslateTextOperation(request: request) } - + func convert(speechToText: URL, options: PredictionsSpeechToTextRequest.Options?, listener: PredictionsSpeechToTextOperation.EventListener?) -> PredictionsSpeechToTextOperation { notify("speechToText") let request = PredictionsSpeechToTextRequest(speechToText: speechToText, options: options ?? PredictionsSpeechToTextRequest.Options()) return MockPredictionsSpeechToTextOperation(request: request) - + } func identify(type: IdentifyAction, diff --git a/AmplifyTests/CoreTests/TreeTests.swift b/AmplifyTests/CoreTests/TreeTests.swift new file mode 100644 index 0000000000..896480d3f9 --- /dev/null +++ b/AmplifyTests/CoreTests/TreeTests.swift @@ -0,0 +1,29 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Amplify + +class TreeTests: XCTestCase { + + func testTreeWithChildren() { + let tree = Tree(value: 0) + let child1 = Tree(value: 1) + let child2 = Tree(value: 2) + + tree.addChild(settingParentOf: child1) + tree.addChild(settingParentOf: child2) + + XCTAssertNotNil(tree) + XCTAssertEqual(tree.value, 0) + XCTAssertEqual(tree.children.count, 2) + XCTAssertNotNil(child1.parent) + XCTAssertEqual(child1.parent?.value, tree.value) + XCTAssertNotNil(child2.parent) + XCTAssertEqual(child2.parent?.value, tree.value) + } +} diff --git a/Podfile.lock b/Podfile.lock index c479b47b32..c9fa54f336 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,14 +1,14 @@ PODS: - - AWSAuthCore (2.12.2): - - AWSCore (= 2.12.2) - - AWSCognitoIdentityProvider (2.12.2): + - AWSAuthCore (2.12.6): + - AWSCore (= 2.12.6) + - AWSCognitoIdentityProvider (2.12.6): - AWSCognitoIdentityProviderASF (= 1.0.1) - - AWSCore (= 2.12.2) + - AWSCore (= 2.12.6) - AWSCognitoIdentityProviderASF (1.0.1) - - AWSCore (2.12.2) - - AWSMobileClient (2.12.2): - - AWSAuthCore (= 2.12.2) - - AWSCognitoIdentityProvider (= 2.12.2) + - AWSCore (2.12.6) + - AWSMobileClient (2.12.6): + - AWSAuthCore (= 2.12.6) + - AWSCognitoIdentityProvider (= 2.12.6) - CwlCatchException (1.0.2) - CwlPreconditionTesting (1.1.1): - CwlCatchException @@ -16,14 +16,14 @@ PODS: - SwiftLint (0.37.0) DEPENDENCIES: - - AWSMobileClient (~> 2.12.2) + - AWSMobileClient (~> 2.12.6) - CwlCatchException (from `https://github.com/mattgallagher/CwlCatchException.git`, tag `1.2.0`) - CwlPreconditionTesting (from `https://github.com/mattgallagher/CwlPreconditionTesting.git`, tag `1.2.0`) - SwiftFormat/CLI - SwiftLint SPEC REPOS: - trunk: + https://cdn.cocoapods.org/: - AWSAuthCore - AWSCognitoIdentityProvider - AWSCognitoIdentityProviderASF @@ -49,16 +49,16 @@ CHECKOUT OPTIONS: :tag: 1.2.0 SPEC CHECKSUMS: - AWSAuthCore: a67ecbfe4dfdb8470f85c31f9594476d5ed988a5 - AWSCognitoIdentityProvider: 5ac7b7af7877e7a47585c08d6a0ef58bcd6314b6 + AWSAuthCore: d981abe8fb987a1caa7ac6c76c428de12387dcf4 + AWSCognitoIdentityProvider: 9502438e528185a2c61b97baf66e2fd2ac6833f3 AWSCognitoIdentityProviderASF: f94f1a502e72ef3d0a1de93e10bf7a79c8698118 - AWSCore: bdc0933bcb1aca4a9af3352a9e6ec75b339faf71 - AWSMobileClient: ed6cd3c6ecb935406bf392df81fc5b40f4660845 + AWSCore: 48bb8d477d8137377e86b2ade9eb34a761f42df5 + AWSMobileClient: d752ed540a75d45516c8d5b2054b3f3b6b9e7e65 CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 5faa819600268dfaa5c19f1359730883db151678 SwiftLint: c078a14d7d7ade75e5507795d185e3da41d844d2 -PODFILE CHECKSUM: 83398094b5ea63b7c181cccf545ad368c69588ec +PODFILE CHECKSUM: 052b557087c76e48d6c09130d48e484e6b7ed701 COCOAPODS: 1.8.4 diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index c479b47b32..c9fa54f336 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -1,14 +1,14 @@ PODS: - - AWSAuthCore (2.12.2): - - AWSCore (= 2.12.2) - - AWSCognitoIdentityProvider (2.12.2): + - AWSAuthCore (2.12.6): + - AWSCore (= 2.12.6) + - AWSCognitoIdentityProvider (2.12.6): - AWSCognitoIdentityProviderASF (= 1.0.1) - - AWSCore (= 2.12.2) + - AWSCore (= 2.12.6) - AWSCognitoIdentityProviderASF (1.0.1) - - AWSCore (2.12.2) - - AWSMobileClient (2.12.2): - - AWSAuthCore (= 2.12.2) - - AWSCognitoIdentityProvider (= 2.12.2) + - AWSCore (2.12.6) + - AWSMobileClient (2.12.6): + - AWSAuthCore (= 2.12.6) + - AWSCognitoIdentityProvider (= 2.12.6) - CwlCatchException (1.0.2) - CwlPreconditionTesting (1.1.1): - CwlCatchException @@ -16,14 +16,14 @@ PODS: - SwiftLint (0.37.0) DEPENDENCIES: - - AWSMobileClient (~> 2.12.2) + - AWSMobileClient (~> 2.12.6) - CwlCatchException (from `https://github.com/mattgallagher/CwlCatchException.git`, tag `1.2.0`) - CwlPreconditionTesting (from `https://github.com/mattgallagher/CwlPreconditionTesting.git`, tag `1.2.0`) - SwiftFormat/CLI - SwiftLint SPEC REPOS: - trunk: + https://cdn.cocoapods.org/: - AWSAuthCore - AWSCognitoIdentityProvider - AWSCognitoIdentityProviderASF @@ -49,16 +49,16 @@ CHECKOUT OPTIONS: :tag: 1.2.0 SPEC CHECKSUMS: - AWSAuthCore: a67ecbfe4dfdb8470f85c31f9594476d5ed988a5 - AWSCognitoIdentityProvider: 5ac7b7af7877e7a47585c08d6a0ef58bcd6314b6 + AWSAuthCore: d981abe8fb987a1caa7ac6c76c428de12387dcf4 + AWSCognitoIdentityProvider: 9502438e528185a2c61b97baf66e2fd2ac6833f3 AWSCognitoIdentityProviderASF: f94f1a502e72ef3d0a1de93e10bf7a79c8698118 - AWSCore: bdc0933bcb1aca4a9af3352a9e6ec75b339faf71 - AWSMobileClient: ed6cd3c6ecb935406bf392df81fc5b40f4660845 + AWSCore: 48bb8d477d8137377e86b2ade9eb34a761f42df5 + AWSMobileClient: d752ed540a75d45516c8d5b2054b3f3b6b9e7e65 CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 5faa819600268dfaa5c19f1359730883db151678 SwiftLint: c078a14d7d7ade75e5507795d185e3da41d844d2 -PODFILE CHECKSUM: 83398094b5ea63b7c181cccf545ad368c69588ec +PODFILE CHECKSUM: 052b557087c76e48d6c09130d48e484e6b7ed701 COCOAPODS: 1.8.4 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 2b53dea446..4a4ae085c1 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -2585,6 +2585,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; productRefGroup = 8B4308E9F48A6891A7915CA1BC478FDA /* Products */; diff --git a/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.markdown b/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.markdown index a403839026..bbb57fa878 100644 --- a/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.markdown +++ b/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.markdown @@ -30,7 +30,7 @@ SOFTWARE. The MIT License (MIT) -Copyright (c) 2020 Realm Inc. +Copyright (c) 2015 Realm Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.plist b/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.plist index 457d2bbd2d..d63d76fd88 100644 --- a/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.plist +++ b/Pods/Target Support Files/Pods-Amplify/Pods-Amplify-acknowledgements.plist @@ -47,7 +47,7 @@ SOFTWARE. FooterText The MIT License (MIT) -Copyright (c) 2020 Realm Inc. +Copyright (c) 2015 Realm Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal