diff --git a/Amplify.xcodeproj/project.pbxproj b/Amplify.xcodeproj/project.pbxproj index 6cdc329864..d2425614a7 100755 --- a/Amplify.xcodeproj/project.pbxproj +++ b/Amplify.xcodeproj/project.pbxproj @@ -295,6 +295,7 @@ B4A19DAD24101F7100DE2E55 /* AuthSignUpOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4A19DAC24101F7100DE2E55 /* AuthSignUpOperation.swift */; }; B4ADE8F4241063820007E86C /* AuthCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4ADE8F3241063820007E86C /* AuthCategory.swift */; }; B4B1E0E524733687007F3261 /* AuthEventName.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1E0E424733687007F3261 /* AuthEventName.swift */; }; + B4B34C55255B725F00033033 /* ModelFieldAssociationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B34C54255B725F00033033 /* ModelFieldAssociationTests.swift */; }; B4B5CC812457B0690019C783 /* AuthFetchUserAttributesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B5CC802457B0690019C783 /* AuthFetchUserAttributesRequest.swift */; }; B4B5CC872457B2470019C783 /* AuthUpdateUserAttributeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B5CC862457B2470019C783 /* AuthUpdateUserAttributeRequest.swift */; }; B4B5CC8B2457B32D0019C783 /* AuthConfirmUserAttributeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B5CC8A2457B32D0019C783 /* AuthConfirmUserAttributeRequest.swift */; }; @@ -1112,6 +1113,7 @@ B4A19DAC24101F7100DE2E55 /* AuthSignUpOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSignUpOperation.swift; sourceTree = ""; }; B4ADE8F3241063820007E86C /* AuthCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCategory.swift; sourceTree = ""; }; B4B1E0E424733687007F3261 /* AuthEventName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthEventName.swift; sourceTree = ""; }; + B4B34C54255B725F00033033 /* ModelFieldAssociationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelFieldAssociationTests.swift; sourceTree = ""; }; B4B5CC802457B0690019C783 /* AuthFetchUserAttributesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFetchUserAttributesRequest.swift; sourceTree = ""; }; B4B5CC862457B2470019C783 /* AuthUpdateUserAttributeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthUpdateUserAttributeRequest.swift; sourceTree = ""; }; B4B5CC8A2457B32D0019C783 /* AuthConfirmUserAttributeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmUserAttributeRequest.swift; sourceTree = ""; }; @@ -3396,11 +3398,12 @@ children = ( FAD3937923820CDB00463F5E /* DataStoreCategoryClientAPITests.swift */, FAD3937C23820D0200463F5E /* DataStoreCategoryConfigurationTests.swift */, + B4944D51251C141200BF0BFE /* JSONValueHolderTest.swift */, + B4B34C54255B725F00033033 /* ModelFieldAssociationTests.swift */, FAE414602399A6A500CE94C2 /* ModelRegistryTests.swift */, B99EF4B423DB020C00D821BC /* TemporalComparableTests.swift */, B9AF547D23F37DF20059E6C4 /* TemporalOperationTests.swift */, B91A87A323D64B0F0049A12F /* TemporalTests.swift */, - B4944D51251C141200BF0BFE /* JSONValueHolderTest.swift */, ); path = DataStore; sourceTree = ""; @@ -4796,6 +4799,7 @@ FACD264D2386E8F10068FBE6 /* JSONValue+KeyPathTests.swift in Sources */, FA9FB782232AA26500C04D32 /* DefaultHubPluginCustomChannelTests.swift in Sources */, FAC23567227A056600424678 /* APICategoryClientRESTTests.swift in Sources */, + B4B34C55255B725F00033033 /* ModelFieldAssociationTests.swift in Sources */, 219A88EF23F3358F00BBC5F2 /* TreeTests.swift in Sources */, FACA35EB2326B217000E74F6 /* AmplifyConfigurationInitializationTests.swift in Sources */, FAD3937A23820CDB00463F5E /* DataStoreCategoryClientAPITests.swift in Sources */, diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/Model+Schema.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/Model+Schema.swift index b9395c5fc5..1fe37a36d0 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/Model+Schema.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/Model+Schema.swift @@ -40,7 +40,7 @@ extension Model { /// - attributes: model attributes (aka "directives" or "annotations") /// - define: the closure used to define the model attributes and fields /// - Returns: a valid `ModelSchema` instance - /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly + /// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public static func defineSchema(name: String? = nil, attributes: ModelAttribute..., @@ -51,7 +51,7 @@ extension Model { return definition.build() } - /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly + /// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public static func rule(allow: AuthStrategy, ownerField: String? = nil, diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift index 556680e60d..234c323c91 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelField+Association.swift @@ -84,17 +84,29 @@ import Foundation /// } /// ``` /// -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelAssociation { - case hasMany(associatedWith: String?) - case hasOne(associatedWith: String?) - case belongsTo(associatedWith: String?, targetName: String?) + case hasMany(associatedFieldName: String?) + case hasOne(associatedFieldName: String?) + case belongsTo(associatedFieldName: String?, targetName: String?) - public static let belongsTo: ModelAssociation = .belongsTo(associatedWith: nil, targetName: nil) + public static let belongsTo: ModelAssociation = .belongsTo(associatedFieldName: nil, targetName: nil) public static func belongsTo(targetName: String? = nil) -> ModelAssociation { - return .belongsTo(associatedWith: nil, targetName: nil) + return .belongsTo(associatedFieldName: nil, targetName: nil) + } + + public static func hasMany(associatedWith: CodingKey?) -> ModelAssociation { + return .hasMany(associatedFieldName: associatedWith?.stringValue) + } + + public static func hasOne(associatedWith: CodingKey?) -> ModelAssociation { + return .hasOne(associatedFieldName: associatedWith?.stringValue) + } + + public static func belongsTo(associatedWith: CodingKey?, targetName: String?) -> ModelAssociation { + return .belongsTo(associatedFieldName: associatedWith?.stringValue, targetName: targetName) } } @@ -107,6 +119,23 @@ extension ModelField { return association != nil } + /// If the field represents an association returns the `Model.Type`. + /// - seealso: `ModelFieldType` + /// - seealso: `ModelFieldAssociation` + /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly + /// by host applications. The behavior of this may change without warning. + @available(*, deprecated, message: """ + Use of associated model type is deprecated, use `associatedModelName` instead. + """) + public var associatedModel: Model.Type? { + switch type { + case .model(let modelName), .collection(let modelName): + return ModelRegistry.modelType(from: modelName) + default: + return nil + } + } + /// If the field represents an association returns the `ModelName`. /// - seealso: `ModelFieldType` /// - seealso: `ModelFieldAssociation` @@ -130,7 +159,30 @@ extension ModelField { /// allows (i.e. the field is a valid relationship, such as foreign keys). /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly /// by host applications. The behavior of this may change without warning. - public var requiredAssociatedModel: ModelName { + @available(*, deprecated, message: """ + Use of requiredAssociatedModel with Model.Type is deprecated, use `requiredAssociatedModelName` + that return ModelName instead. + """) + public var requiredAssociatedModel: Model.Type { + guard let modelType = associatedModel else { + preconditionFailure(""" + Model fields that are foreign keys must be connected to another Model. + Check the `ModelSchema` section of your "\(name)+Schema.swift" file. + """) + } + return modelType + } + + /// This calls `associatedModelName` but enforces that the field must represent an association. + /// In case the field type is not a `Model` it calls `preconditionFailure`. Consumers + /// should fix their models in order to recover from it, since associations are only + /// possible between two `Model`. + /// + /// - Note: as a maintainer, make sure you use this computed property only when context + /// allows (i.e. the field is a valid relationship, such as foreign keys). + /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly + /// by host applications. The behavior of this may change without warning. + public var requiredAssociatedModelName: ModelName { guard let modelName = associatedModelName else { preconditionFailure(""" Model fields that are foreign keys must be connected to another Model. @@ -153,7 +205,7 @@ extension ModelField { /// by host applications. The behavior of this may change without warning. public var associatedField: ModelField? { if hasAssociation { - let associatedModel = requiredAssociatedModel + let associatedModel = requiredAssociatedModelName switch association { case .belongsTo(let associatedKey, _): // TODO handle modelName casing (convert to camelCase) @@ -203,6 +255,23 @@ extension ModelField { return false } + /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly + /// by host applications. The behavior of this may change without warning. + @available(*, deprecated, message: """ + Use `embeddedType` is deprecated, use `embeddedTypeSchema` instead. + """) + public var embeddedType: Embeddable.Type? { + switch type { + case .embedded(let type, _), .embeddedCollection(let type, _): + if let embeddedType = type as? Embeddable.Type { + return embeddedType + } + return nil + default: + return nil + } + } + /// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly /// by host applications. The behavior of this may change without warning. public var embeddedTypeSchema: ModelSchema? { diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema+Definition.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema+Definition.swift index 80d285edc3..98426ca799 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema+Definition.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema+Definition.swift @@ -8,7 +8,7 @@ import Foundation /// Defines the type of a `Model` field. -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelFieldType { @@ -21,11 +21,33 @@ public enum ModelFieldType { case timestamp case bool case `enum`(type: EnumPersistable.Type) - case embedded(type: Codable.Type, schema: ModelSchema? = nil) - case embeddedCollection(of: Codable.Type, schema: ModelSchema? = nil) + case embedded(type: Codable.Type, schema: ModelSchema?) + case embeddedCollection(of: Codable.Type, schema: ModelSchema?) case model(name: ModelName) case collection(of: ModelName) + public static func model(type: Model.Type) -> ModelFieldType { + .model(name: type.modelName) + } + + public static func collection(of type: Model.Type) -> ModelFieldType { + .collection(of: type.modelName) + } + + public static func embedded(type: Codable.Type) -> ModelFieldType { + guard let embeddedType = type as? Embeddable.Type else { + return .embedded(type: type, schema: nil) + } + return .embedded(type: type, schema: embeddedType.schema) + } + + public static func embeddedCollection(of type: Codable.Type) -> ModelFieldType { + guard let embeddedType = type as? Embeddable.Type else { + return .embedded(type: type, schema: nil) + } + return .embeddedCollection(of: type, schema: embeddedType.schema) + } + public var isArray: Bool { switch self { case .collection, .embeddedCollection: @@ -72,16 +94,16 @@ public enum ModelFieldType { return .enum(type: enumType) } if let modelType = type as? Model.Type { - return .model(name: modelType.modelName) + return .model(type: modelType) } if let embeddedType = type as? Codable.Type { - return .embedded(type: embeddedType, schema: nil) + return .embedded(type: embeddedType) } preconditionFailure("Could not create a ModelFieldType from \(String(describing: type)) MetaType") } } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelFieldNullability { case optional @@ -97,7 +119,7 @@ public enum ModelFieldNullability { } } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public struct ModelSchemaDefinition { @@ -138,7 +160,7 @@ public struct ModelSchemaDefinition { } } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelFieldDefinition { @@ -155,21 +177,8 @@ public enum ModelFieldDefinition { attributes: [ModelFieldAttribute] = [], association: ModelAssociation? = nil, authRules: AuthRules = []) -> ModelFieldDefinition { - var modifiedType: ModelFieldType = type - switch type { - case .embedded(let codable, let schema): - if schema == nil, let embeddedType = codable as? Embeddable.Type { - modifiedType = .embedded(type: codable, schema: embeddedType.schema) - } - case .embeddedCollection(let codable, let schema): - if schema == nil, let embeddedType = codable as? Embeddable.Type { - modifiedType = .embeddedCollection(of: codable, schema: embeddedType.schema) - } - default: - modifiedType = type - } return .field(name: key.stringValue, - type: modifiedType, + type: type, nullability: nullability, association: association, attributes: attributes, @@ -195,8 +204,8 @@ public enum ModelFieldDefinition { associatedWith associatedKey: CodingKey) -> ModelFieldDefinition { return .field(key, is: nullability, - ofType: .collection(of: type.modelName), - association: .hasMany(associatedWith: associatedKey.stringValue)) + ofType: .collection(of: type), + association: .hasMany(associatedWith: associatedKey)) } public static func hasOne(_ key: CodingKey, @@ -205,8 +214,8 @@ public enum ModelFieldDefinition { associatedWith associatedKey: CodingKey) -> ModelFieldDefinition { return .field(key, is: nullability, - ofType: .model(name: type.modelName), - association: .hasOne(associatedWith: associatedKey.stringValue)) + ofType: .model(type: type), + association: .hasOne(associatedWith: associatedKey)) } public static func belongsTo(_ key: CodingKey, @@ -216,8 +225,8 @@ public enum ModelFieldDefinition { targetName: String? = nil) -> ModelFieldDefinition { return .field(key, is: nullability, - ofType: .model(name: type.modelName), - association: .belongsTo(associatedWith: associatedKey?.stringValue, targetName: targetName)) + ofType: .model(type: type), + association: .belongsTo(associatedWith: associatedKey, targetName: targetName)) } public var modelField: ModelField { diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema.swift index ef9b454da3..3fdd54c91f 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelAttribute { @@ -16,13 +16,13 @@ public enum ModelAttribute { case isSystem } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public enum ModelFieldAttribute { case primaryKey } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public struct ModelField { @@ -55,15 +55,15 @@ public struct ModelField { } } -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public typealias ModelFields = [String: ModelField] -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public typealias ModelName = String -/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly +/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used directly /// by host applications. The behavior of this may change without warning. public struct ModelSchema { diff --git a/AmplifyPlugins/API/Podfile.lock b/AmplifyPlugins/API/Podfile.lock index 61fd0d2969..f5481af283 100644 --- a/AmplifyPlugins/API/Podfile.lock +++ b/AmplifyPlugins/API/Podfile.lock @@ -40,7 +40,7 @@ PODS: - ReachabilitySwift (5.0.0) - Starscream (3.1.1) - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -108,7 +108,7 @@ SPEC CHECKSUMS: ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 31880f143dde88382b42400953bbeb67ca4f2ae3 diff --git a/AmplifyPlugins/Analytics/Podfile.lock b/AmplifyPlugins/Analytics/Podfile.lock index 63e5d89ee5..0d643e3f5c 100644 --- a/AmplifyPlugins/Analytics/Podfile.lock +++ b/AmplifyPlugins/Analytics/Podfile.lock @@ -38,7 +38,7 @@ PODS: - CwlPreconditionTesting (1.1.1): - CwlCatchException - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -100,7 +100,7 @@ SPEC CHECKSUMS: CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 48d1574dddce5cef7bdb7b05b06fc588ee22956e diff --git a/AmplifyPlugins/Auth/Podfile.lock b/AmplifyPlugins/Auth/Podfile.lock index 52c13228a6..2cfc8fd3cb 100644 --- a/AmplifyPlugins/Auth/Podfile.lock +++ b/AmplifyPlugins/Auth/Podfile.lock @@ -29,7 +29,7 @@ PODS: - CwlPreconditionTesting (1.1.1): - CwlCatchException - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -85,7 +85,7 @@ SPEC CHECKSUMS: CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 371cf67fe35ebb5167d0880bad12b01618a0fb0e diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift index 9dae0c817b..5fc15e1624 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift @@ -64,6 +64,39 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory { decodePath: document.name) } + public static func createMutation(of model: Model, version: Int?) -> GraphQLRequest { + createMutation(of: model, modelSchema: model.schema, version: version) + } + + public static func updateMutation(of model: Model, + where filter: GraphQLFilter?, + version: Int?) -> GraphQLRequest { + updateMutation(of: model, modelSchema: model.schema, where: filter, version: version) + } + + public static func subscription(to modelType: Model.Type, + subscriptionType: GraphQLSubscriptionType) -> GraphQLRequest { + subscription(to: modelType.schema, subscriptionType: subscriptionType) + } + + public static func subscription(to modelType: Model.Type, + subscriptionType: GraphQLSubscriptionType, + claims: IdentityClaimsDictionary) -> GraphQLRequest { + subscription(to: modelType.schema, subscriptionType: subscriptionType, claims: claims) + } + + public static func syncQuery(modelType: Model.Type, + where predicate: QueryPredicate?, + limit: Int?, + nextToken: String?, + lastSync: Int?) -> GraphQLRequest { + syncQuery(modelSchema: modelType.schema, + where: predicate, + limit: limit, + nextToken: nextToken, + lastSync: lastSync) + } + public static func createMutation(of model: Model, modelSchema: ModelSchema, version: Int? = nil) -> GraphQLRequest { diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift index b54d7e542b..a912be1c78 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift @@ -149,6 +149,12 @@ extension GraphQLRequest: ModelGraphQLRequestFactory { return mutation(of: model, modelSchema: modelSchema, where: predicate, type: .delete) } + public static func mutation(of model: M, + where predicate: QueryPredicate? = nil, + type: GraphQLMutationType) -> GraphQLRequest { + mutation(of: model, modelSchema: model.schema, where: predicate, type: type) + } + public static func mutation(of model: M, modelSchema: ModelSchema, where predicate: QueryPredicate? = nil, diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift index 866210d89a..f4ba3ee0f9 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift @@ -28,13 +28,13 @@ extension Model { let name = field.graphQLName let fieldOptionalValue: Any?? - + if let jsonModel = self as? JSONValueHolder { fieldOptionalValue = jsonModel.jsonValue(for: field.name, modelSchema: modelSchema) ?? nil } else { fieldOptionalValue = self[field.name] ?? nil } - + // Since the returned value is Any?? we need to do the following: // - `guard` to make sure the field name exists on the model // - `guard` to ensure the returned value isn't nil diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Model+SQLite.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Model+SQLite.swift index 3a657728f6..fe62282a08 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Model+SQLite.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Model+SQLite.swift @@ -160,10 +160,10 @@ extension Array where Element == ModelSchema { let associatedModelSchemas = schema.sortedFields .filter { $0.isForeignKey } .map { (schema) -> ModelSchema in - guard let associatedSchema = ModelRegistry.modelSchema(from: schema.requiredAssociatedModel) + guard let associatedSchema = ModelRegistry.modelSchema(from: schema.requiredAssociatedModelName) else { preconditionFailure(""" - Could not retrieve schema for the model \(schema.requiredAssociatedModel), verify that + Could not retrieve schema for the model \(schema.requiredAssociatedModelName), verify that datastore is initialized. """) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+CreateTable.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+CreateTable.swift index 1dcb5542ff..cb41b2ade4 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+CreateTable.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+CreateTable.swift @@ -49,7 +49,7 @@ struct CreateTableStatement: SQLStatement { for (index, foreignKey) in foreignKeys.enumerated() { statement += " foreign key(\"\(foreignKey.sqlName)\") " - let associatedModel = foreignKey.requiredAssociatedModel + let associatedModel = foreignKey.requiredAssociatedModelName guard let schema = ModelRegistry.modelSchema(from: associatedModel) else { preconditionFailure(""" Could not retrieve schema for the model \(associatedModel), verify that datastore is initialized. diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+Select.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+Select.swift index 0e5ec185af..e9c94da498 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+Select.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+Select.swift @@ -93,7 +93,7 @@ struct SelectStatementMetadata { func visitAssociations(node: ModelSchema, namespace: String = "root") { for foreignKey in node.foreignKeys { - let associatedModelName = foreignKey.requiredAssociatedModel + let associatedModelName = foreignKey.requiredAssociatedModelName guard let associatedSchema = ModelRegistry.modelSchema(from: associatedModelName) else { preconditionFailure(""" diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift index 83fc86591c..3aaaf531e2 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift @@ -127,6 +127,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { "Save failed due to condition did not match existing model instance.", "The save will continue to fail until the model instance is updated.") completion(.failure(causedBy: dataStoreError)) + return } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/TestModelRegistration.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/TestModelRegistration.swift index fc103a3cc7..1d7cd87683 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/TestModelRegistration.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/TestModelRegistration.swift @@ -44,7 +44,7 @@ struct TestJsonModelRegistration: AmplifyModelRegistration { let comments = ModelField(name: "comments", type: .collection(of: "Comment"), isRequired: false, - association: .hasMany(associatedWith: "post")) + association: .hasMany(associatedFieldName: "post")) let postSchema = ModelSchema(name: "Post", pluralName: "Posts", fields: [id.name: id, diff --git a/AmplifyPlugins/DataStore/Podfile.lock b/AmplifyPlugins/DataStore/Podfile.lock index 9a25490d54..f2b2765e80 100644 --- a/AmplifyPlugins/DataStore/Podfile.lock +++ b/AmplifyPlugins/DataStore/Podfile.lock @@ -48,7 +48,7 @@ PODS: - SQLite.swift/standard (0.12.2) - Starscream (3.1.1) - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -118,7 +118,7 @@ SPEC CHECKSUMS: SQLite.swift: d2b4642190917051ce6bd1d49aab565fe794eea3 Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 04860e414d616b67d24ed3346a60700f427764b9 diff --git a/AmplifyPlugins/Predictions/Podfile.lock b/AmplifyPlugins/Predictions/Podfile.lock index 9f8cfa8004..ba82a0422a 100644 --- a/AmplifyPlugins/Predictions/Podfile.lock +++ b/AmplifyPlugins/Predictions/Podfile.lock @@ -48,7 +48,7 @@ PODS: - CwlPreconditionTesting (1.1.1): - CwlCatchException - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -125,7 +125,7 @@ SPEC CHECKSUMS: CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 65cca76c6059f8ccc8628936971fb9e4b035fb10 diff --git a/AmplifyPlugins/Storage/Podfile.lock b/AmplifyPlugins/Storage/Podfile.lock index 1597fc6096..a3253385c4 100644 --- a/AmplifyPlugins/Storage/Podfile.lock +++ b/AmplifyPlugins/Storage/Podfile.lock @@ -38,7 +38,7 @@ PODS: - CwlPreconditionTesting (1.1.1): - CwlCatchException - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - Amplify (from `../../`) @@ -100,7 +100,7 @@ SPEC CHECKSUMS: CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: 23c3028505a2f56c001d01c66c1622dff6f8dd8e diff --git a/AmplifyTests/CategoryTests/DataStore/ModelFieldAssociationTests.swift b/AmplifyTests/CategoryTests/DataStore/ModelFieldAssociationTests.swift new file mode 100644 index 0000000000..d6fca8d920 --- /dev/null +++ b/AmplifyTests/CategoryTests/DataStore/ModelFieldAssociationTests.swift @@ -0,0 +1,94 @@ +// +// 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 + +class ModelFieldAssociationTests: XCTestCase { + + override func setUp() { + ModelRegistry.register(modelType: Post.self) + ModelRegistry.register(modelType: Comment.self) + } + + func testBelongsToWithCodingKeys() { + let belongsTo = ModelAssociation.belongsTo(associatedWith: Comment.keys.post, targetName: "postID") + guard case .belongsTo(let fieldName, let target) = belongsTo else { + XCTFail("Should create belongsTo association") + return + } + XCTAssertEqual(fieldName, Comment.keys.post.stringValue) + XCTAssertEqual(target, "postID") + } + + func testHasManyWithCodingKeys() { + let hasMany = ModelAssociation.hasMany(associatedWith: Comment.keys.post) + guard case .hasMany(let fieldName) = hasMany else { + XCTFail("Should create hasMany association") + return + } + XCTAssertEqual(fieldName, Comment.keys.post.stringValue) + } + + func testHasOneWithCodingKeys() { + let hasOne = ModelAssociation.hasOne(associatedWith: Comment.keys.post) + guard case .hasOne(let fieldName) = hasOne else { + XCTFail("Should create hasOne association") + return + } + XCTAssertEqual(fieldName, Comment.keys.post.stringValue) + } + + func testBelongsToWithTargetName() { + let belongsTo = ModelAssociation.belongsTo(targetName: "postID") + guard case .belongsTo(let fieldName, let target) = belongsTo else { + XCTFail("Should create belongsTo association") + return + } + XCTAssertNil(fieldName) + XCTAssertNil(target) + } + + func testModelFieldWithBelongsToAssociation() { + let belongsTo = ModelAssociation.belongsTo(associatedWith: nil, targetName: "commentPostId") + let field = ModelField.init(name: "post", + type: .model(type: Post.self), + association: belongsTo) + + XCTAssertEqual("Post", field.associatedModelName) + XCTAssertTrue(field.hasAssociation) + XCTAssertTrue(field.isAssociationOwner) + } + + func testModelFieldWithHasManyAssociation() { + let hasMany = ModelAssociation.hasMany(associatedWith: Comment.keys.post) + let field = ModelField.init(name: "comments", + type: .collection(of: Comment.self), + isArray: true, + association: hasMany) + + XCTAssertEqual("Comment", field.associatedModelName) + XCTAssertTrue(field.hasAssociation) + XCTAssertFalse(field.isAssociationOwner) + XCTAssertNotNil(field.associatedField) + } + + func testModelFieldWithHasOneAssociation() { + let hasOne = ModelAssociation.hasOne(associatedWith: Comment.keys.post) + let field = ModelField.init(name: "comment", + type: .model(type: Comment.self), + association: hasOne) + + XCTAssertEqual("Comment", field.associatedModelName) + XCTAssertTrue(field.hasAssociation) + XCTAssertFalse(field.isAssociationOwner) + XCTAssertTrue(field.isOneToOne) + XCTAssertNotNil(field.associatedField) + } + +} diff --git a/Podfile.lock b/Podfile.lock index d87b647832..24433a3492 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,7 +15,7 @@ PODS: - CwlPreconditionTesting (1.1.1): - CwlCatchException - SwiftFormat/CLI (0.44.17) - - SwiftLint (0.40.3) + - SwiftLint (0.41.0) DEPENDENCIES: - AWSMobileClient (~> 2.18.0) @@ -59,7 +59,7 @@ SPEC CHECKSUMS: CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 SwiftFormat: 3b5caa6389b2b9adbc00e133b3ccc8c6e687a6a4 - SwiftLint: dfd554ff0dff17288ee574814ccdd5cea85d76f7 + SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e PODFILE CHECKSUM: a2acde35ab1bc6b80b362106cfda70905a6a840f