// // Copyright Amazon.com Inc. or its affiliates. // All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // import Amplify import Foundation public typealias IncludedAssociations<M: Model> = (ModelPath<M>) -> [PropertyContainerPath] // MARK: - Protocol /// Protocol that represents the integration between `GraphQLRequest` and `Model`. /// /// The methods defined here are used to build a valid `GraphQLRequest` from types /// conforming to `Model`. protocol ModelGraphQLRequestFactory { // MARK: Query /// Creates a `GraphQLRequest` that represents a query that expects multiple values as a result. /// The request will be created with the correct document based on the `ModelSchema` and /// variables based on the the predicate. /// /// - Parameters: /// - modelType: the metatype of the model /// - predicate: an optional predicate containing the criteria for the query /// - includes: the closure to determine which associations should be included in the selection set /// - limit: the maximum number of results to be retrieved. The result list may be less than the `limit` /// - Returns: a valid `GraphQLRequest` instance /// /// - seealso: `GraphQLQuery`, `GraphQLQueryType.list` static func list<M: Model>(_ modelType: M.Type, where predicate: QueryPredicate?, includes: IncludedAssociations<M>, limit: Int?) -> GraphQLRequest<List<M>> /// Creates a `GraphQLRequest` that represents a query that expects a single value as a result. /// The request will be created with the correct correct document based on the `ModelSchema` and /// variables based on given `id`. /// /// - Parameters: /// - modelType: the metatype of the model /// - id: the model identifier /// - includes: the closure to determine which associations should be included in the selection set /// - Returns: a valid `GraphQLRequest` instance /// /// - seealso: `GraphQLQuery`, `GraphQLQueryType.get` static func get<M: Model>(_ modelType: M.Type, byId id: String, includes: IncludedAssociations<M>) -> GraphQLRequest<M?> static func get<M: Model>(_ modelType: M.Type, byIdentifier id: String, includes: IncludedAssociations<M>) -> GraphQLRequest<M?> where M: ModelIdentifiable, M.IdentifierFormat == ModelIdentifierFormat.Default static func get<M: Model>(_ modelType: M.Type, byIdentifier id: ModelIdentifier<M, M.IdentifierFormat>, includes: IncludedAssociations<M>) -> GraphQLRequest<M?> where M: ModelIdentifiable // MARK: Mutation /// Creates a `GraphQLRequest` that represents a mutation of a given `type` for a `model` instance. /// /// - Parameters: /// - model: the model instance populated with values /// - modelSchema: the model schema of the model /// - predicate: a predicate passed as the condition to apply the mutation /// - type: the mutation type, either `.create`, `.update`, or `.delete` /// - Returns: a valid `GraphQLRequest` instance static func mutation<M: Model>(of model: M, modelSchema: ModelSchema, where predicate: QueryPredicate?, includes: IncludedAssociations<M>, type: GraphQLMutationType) -> GraphQLRequest<M> /// Creates a `GraphQLRequest` that represents a create mutation /// for a given `model` instance. /// /// - Parameters: /// - model: the model instance populated with values /// - Returns: a valid `GraphQLRequest` instance /// - seealso: `GraphQLRequest.mutation(of:where:type:)` static func create<M: Model>(_ model: M, includes: IncludedAssociations<M>) -> GraphQLRequest<M> /// Creates a `GraphQLRequest` that represents an update mutation /// for a given `model` instance. /// /// - Parameters: /// - model: the model instance populated with values /// - predicate: a predicate passed as the condition to apply the mutation /// - Returns: a valid `GraphQLRequest` instance /// - seealso: `GraphQLRequest.mutation(of:where:type:)` static func update<M: Model>(_ model: M, where predicate: QueryPredicate?, includes: IncludedAssociations<M>) -> GraphQLRequest<M> /// Creates a `GraphQLRequest` that represents a delete mutation /// for a given `model` instance. /// /// - Parameters: /// - model: the model instance populated with values /// - predicate: a predicate passed as the condition to apply the mutation /// - Returns: a valid `GraphQLRequest` instance /// - seealso: `GraphQLRequest.mutation(of:where:type:)` static func delete<M: Model>(_ model: M, where predicate: QueryPredicate?, includes: IncludedAssociations<M>) -> GraphQLRequest<M> // MARK: Subscription /// Creates a `GraphQLRequest` that represents a subscription of a given `type` for a `model` type. /// The request will be created with the correct document based on the `ModelSchema`. /// /// - Parameters: /// - modelType: the metatype of the model /// - type: the subscription type, either `.onCreate`, `.onUpdate` or `.onDelete` /// - includes: the closure to determine which associations should be included in the selection set /// - Returns: a valid `GraphQLRequest` instance /// /// - seealso: `GraphQLSubscription`, `GraphQLSubscriptionType` static func subscription<M: Model>(of: M.Type, type: GraphQLSubscriptionType, includes: IncludedAssociations<M>) -> GraphQLRequest<M> } // MARK: - Extension /// Extension that provides an integration layer between `Model`, /// `GraphQLDocument` and `GraphQLRequest` by conforming to `ModelGraphQLRequestFactory`. /// /// This is particularly useful when using the GraphQL API to interact /// with static types that conform to the `Model` protocol. extension GraphQLRequest: ModelGraphQLRequestFactory { private static func modelSchema<M: Model>(for model: M) -> ModelSchema { let modelType = ModelRegistry.modelType(from: model.modelName) ?? Swift.type(of: model) return modelType.schema } public static func create<M: Model>(_ model: M, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return create(model, modelSchema: modelSchema(for: model), includes: includes) } public static func update<M: Model>(_ model: M, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return update(model, modelSchema: modelSchema(for: model), where: predicate, includes: includes) } public static func delete<M: Model>(_ model: M, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return delete(model, modelSchema: modelSchema(for: model), where: predicate, includes: includes) } public static func create<M: Model>(_ model: M, modelSchema: ModelSchema, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return mutation(of: model, modelSchema: modelSchema, includes: includes, type: .create) } public static func update<M: Model>(_ model: M, modelSchema: ModelSchema, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return mutation(of: model, modelSchema: modelSchema, where: predicate, includes: includes, type: .update) } public static func delete<M: Model>(_ model: M, modelSchema: ModelSchema, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { return mutation(of: model, modelSchema: modelSchema, where: predicate, includes: includes, type: .delete) } public static func mutation<M: Model>(of model: M, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }, type: GraphQLMutationType) -> GraphQLRequest<M> { mutation(of: model, modelSchema: model.schema, where: predicate, includes: includes, type: type) } public static func mutation<M: Model>(of model: M, modelSchema: ModelSchema, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }, type: GraphQLMutationType) -> GraphQLRequest<M> { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelSchema, operationType: .mutation) documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) if let modelPath = M.rootPath as? ModelPath<M> { let associations = includes(modelPath) documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) } switch type { case .create: documentBuilder.add(decorator: ModelDecorator(model: model, mutationType: type)) case .delete: documentBuilder.add(decorator: ModelIdDecorator(model: model, schema: modelSchema)) if let predicate = predicate { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelSchema))) } case .update: documentBuilder.add(decorator: ModelDecorator(model: model, mutationType: type)) if let predicate = predicate { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelSchema))) } } let document = documentBuilder.build() return GraphQLRequest<M>(document: document.stringValue, variables: document.variables, responseType: M.self, decodePath: document.name) } public static func get<M: Model>(_ modelType: M.Type, byId id: String, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M?> { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) if let modelPath = modelType.rootPath as? ModelPath<M> { let associations = includes(modelPath) documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) } documentBuilder.add(decorator: ModelIdDecorator(id: id)) let document = documentBuilder.build() return GraphQLRequest<M?>(document: document.stringValue, variables: document.variables, responseType: M?.self, decodePath: document.name) } public static func get<M: Model>(_ modelType: M.Type, byIdentifier id: String, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M?> where M: ModelIdentifiable, M.IdentifierFormat == ModelIdentifierFormat.Default { return .get(modelType, byId: id, includes: includes) } public static func get<M: Model>(_ modelType: M.Type, byIdentifier id: ModelIdentifier<M, M.IdentifierFormat>, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M?> where M: ModelIdentifiable { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .get)) if let modelPath = modelType.rootPath as? ModelPath<M> { let associations = includes(modelPath) documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) } documentBuilder.add(decorator: ModelIdDecorator(identifierFields: id.fields)) let document = documentBuilder.build() return GraphQLRequest<M?>(document: document.stringValue, variables: document.variables, responseType: M?.self, decodePath: document.name) } public static func list<M: Model>(_ modelType: M.Type, where predicate: QueryPredicate? = nil, includes: IncludedAssociations<M> = { _ in [] }, limit: Int? = nil) -> GraphQLRequest<List<M>> { let primaryKeysOnly = (M.rootPath != nil) ? true : false var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .list)) if let modelPath = modelType.rootPath as? ModelPath<M> { let associations = includes(modelPath) documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) } if let predicate = predicate { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelType.schema))) } documentBuilder.add(decorator: PaginationDecorator(limit: limit)) let document = documentBuilder.build() return GraphQLRequest<List<M>>(document: document.stringValue, variables: document.variables, responseType: List<M>.self, decodePath: document.name) } public static func subscription<M: Model>(of modelType: M.Type, type: GraphQLSubscriptionType, includes: IncludedAssociations<M> = { _ in [] }) -> GraphQLRequest<M> { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .subscription) documentBuilder.add(decorator: DirectiveNameDecorator(type: type)) if let modelPath = modelType.rootPath as? ModelPath<M> { let associations = includes(modelPath) documentBuilder.add(decorator: IncludeAssociationDecorator(associations)) } let document = documentBuilder.build() return GraphQLRequest<M>(document: document.stringValue, variables: document.variables, responseType: modelType, decodePath: document.name) } }