diff --git a/src/Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs b/src/Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
index c36ec96511..c2a9b0a9ac 100644
--- a/src/Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
+++ b/src/Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
@@ -31,8 +31,8 @@ public static class CreateMutationBuilder
/// Database type of the relational database to generate input type for.
/// Runtime config information.
/// Indicates whether multiple create operation is enabled
- /// A GraphQL input type with all expected fields mapped as GraphQL inputs.
- private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationalDb(
+ /// An optional GraphQL input type with all expected fields mapped as GraphQL inputs.
+ private static InputObjectTypeDefinitionNode? GenerateCreateInputTypeForRelationalDb(
Dictionary inputs,
ObjectTypeDefinitionNode objectTypeDefinitionNode,
string entityName,
@@ -44,6 +44,7 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
bool IsMultipleCreateOperationEnabled)
{
NameNode inputName = GenerateInputTypeName(name.Value);
+ InputObjectTypeDefinitionNode? input = null;
if (inputs.TryGetValue(inputName, out InputObjectTypeDefinitionNode? db))
{
@@ -54,7 +55,7 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
// 1. Scalar input fields corresponding to columns which belong to the table.
// 2. Complex input fields corresponding to related (target) entities (table backed entities, for now)
// which are defined in the runtime config.
- List inputFields = new();
+ List inputFields = new();
// 1. Scalar input fields.
IEnumerable scalarInputFields = objectTypeDefinitionNode.Fields
@@ -62,24 +63,26 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
.Select(field => GenerateScalarInputType(name, field, IsMultipleCreateOperationEnabled));
// Add scalar input fields to list of input fields for current input type.
- inputFields.AddRange(scalarInputFields);
-
- // Create input object for this entity.
- InputObjectTypeDefinitionNode input =
- new(
- location: null,
- inputName,
- new StringValueNode($"Input type for creating {name}"),
- new List(),
- inputFields
- );
-
- // Add input object to the dictionary of entities for which input object has already been created.
- // This input object currently holds only scalar fields.
- // The complex fields (for related entities) would be added later when we return from recursion.
- // Adding the input object to the dictionary ensures that we don't go into infinite recursion and return whenever
- // we find that the input object has already been created for the entity.
- inputs.Add(input.Name, input);
+ // Generate the create input type only if there are any scalar fields that are not auto-generated fields.
+ if (scalarInputFields.Any())
+ {
+ inputFields.AddRange(scalarInputFields);
+
+ // Create input object for this entity.
+ input =
+ new(
+ location: null,
+ inputName,
+ new StringValueNode($"Input type for creating {name}"),
+ new List(),
+ inputFields!);
+ // Add input object to the dictionary of entities for which input object has already been created.
+ // This input object currently holds only scalar fields.
+ // The complex fields (for related entities) would be added later when we return from recursion.
+ // Adding the input object to the dictionary ensures that we don't go into infinite recursion and return whenever
+ // we find that the input object has already been created for the entity.
+ inputs.Add(input.Name, input);
+ }
// Generate fields for related entities when
// 1. Multiple mutation operations are supported for the database type.
@@ -88,7 +91,7 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
{
// 2. Complex input fields.
// Evaluate input objects for related entities.
- IEnumerable complexInputFields =
+ IEnumerable complexInputFields =
objectTypeDefinitionNode.Fields
.Where(field => !IsBuiltInType(field.Type) && IsComplexFieldAllowedForCreateInputInRelationalDb(field, definitions))
.Select(field =>
@@ -148,7 +151,7 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
databaseType: databaseType,
entities: entities,
IsMultipleCreateOperationEnabled: IsMultipleCreateOperationEnabled);
- });
+ }).Where(complexInputType => complexInputType != null);
// Append relationship fields to the input fields.
inputFields.AddRange(complexInputFields);
}
@@ -307,8 +310,8 @@ private static InputValueDefinitionNode GenerateScalarInputType(NameNode name, F
/// The GraphQL object type to create the input type for.
/// Database type to generate the input type for.
/// Runtime configuration information for entities.
- /// A GraphQL input type value.
- private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
+ /// An Optional GraphQL input type value.
+ private static InputValueDefinitionNode? GenerateComplexInputTypeForRelationalDb(
string entityName,
Dictionary inputs,
IEnumerable definitions,
@@ -320,7 +323,7 @@ private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
RuntimeEntities entities,
bool IsMultipleCreateOperationEnabled)
{
- InputObjectTypeDefinitionNode node;
+ InputObjectTypeDefinitionNode? node;
NameNode inputTypeName = GenerateInputTypeName(typeName);
if (!inputs.ContainsKey(inputTypeName))
{
@@ -340,7 +343,7 @@ private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
node = inputs[inputTypeName];
}
- return GetComplexInputType(field, node, inputTypeName, IsMultipleCreateOperationEnabled);
+ return node == null ? null : GetComplexInputType(field, node, inputTypeName, IsMultipleCreateOperationEnabled);
}
///
@@ -487,7 +490,7 @@ public static IEnumerable Build(
{
List createMutationNodes = new();
Entity entity = entities[dbEntityName];
- InputObjectTypeDefinitionNode input;
+ InputObjectTypeDefinitionNode? input;
if (!IsRelationalDb(databaseType))
{
input = GenerateCreateInputTypeForNonRelationalDb(
@@ -528,12 +531,14 @@ public static IEnumerable Build(
string singularName = GetDefinedSingularName(name.Value, entity);
- // Create one node.
- FieldDefinitionNode createOneNode = new(
- location: null,
- name: new NameNode(GetPointCreateMutationNodeName(name.Value, entity)),
- description: new StringValueNode($"Creates a new {singularName}"),
- arguments: new List {
+ if (input != null)
+ {
+ // Create one node.
+ FieldDefinitionNode createOneNode = new(
+ location: null,
+ name: new NameNode(GetPointCreateMutationNodeName(name.Value, entity)),
+ description: new StringValueNode($"Creates a new {singularName}"),
+ arguments: new List {
new(
location : null,
new NameNode(MutationBuilder.ITEM_INPUT_ARGUMENT_NAME),
@@ -541,15 +546,16 @@ public static IEnumerable Build(
new NonNullTypeNode(new NamedTypeNode(input.Name)),
defaultValue: null,
new List())
- },
- type: new NamedTypeNode(returnEntityName),
- directives: fieldDefinitionNodeDirectives
- );
+ },
+ type: new NamedTypeNode(returnEntityName),
+ directives: fieldDefinitionNodeDirectives
+ );
- createMutationNodes.Add(createOneNode);
+ createMutationNodes.Add(createOneNode);
+ }
// Multiple create node is created in the schema only when multiple create operation is enabled.
- if (IsMultipleCreateOperationEnabled)
+ if (IsMultipleCreateOperationEnabled && input != null)
{
// Create multiple node.
FieldDefinitionNode createMultipleNode = new(
diff --git a/src/Service.GraphQLBuilder/Mutations/MutationBuilder.cs b/src/Service.GraphQLBuilder/Mutations/MutationBuilder.cs
index 35c6e5e3a8..6ceb4445d3 100644
--- a/src/Service.GraphQLBuilder/Mutations/MutationBuilder.cs
+++ b/src/Service.GraphQLBuilder/Mutations/MutationBuilder.cs
@@ -152,31 +152,42 @@ private static void AddMutations(
break;
case EntityActionOperation.Update:
// Generate Mutation operation for Patch and Update both for CosmosDB
- mutationFields.Add(UpdateAndPatchMutationBuilder.Build(
- name,
- inputs,
- objectTypeDefinitionNode,
- root,
- entities,
- dbEntityName,
- databaseType,
- returnEntityName,
- rolesAllowedForMutation));
+ FieldDefinitionNode? mutationField = UpdateAndPatchMutationBuilder.Build(
+ name,
+ inputs,
+ objectTypeDefinitionNode,
+ root,
+ entities,
+ dbEntityName,
+ databaseType,
+ returnEntityName,
+ rolesAllowedForMutation);
+
+ if (mutationField != null)
+ {
+ mutationFields.Add(mutationField);
+ }
if (databaseType is DatabaseType.CosmosDB_NoSQL)
{
- mutationFields.Add(UpdateAndPatchMutationBuilder.Build(
- name,
- inputs,
- objectTypeDefinitionNode,
- root,
- entities,
- dbEntityName,
- databaseType,
- returnEntityName,
- rolesAllowedForMutation,
- EntityActionOperation.Patch,
- operationNamePrefix: "patch"));
+ FieldDefinitionNode? cosmosMutationField = UpdateAndPatchMutationBuilder.Build(
+ name,
+ inputs,
+ objectTypeDefinitionNode,
+ root,
+ entities,
+ dbEntityName,
+ databaseType,
+ returnEntityName,
+ rolesAllowedForMutation,
+ EntityActionOperation.Patch,
+ operationNamePrefix: "patch");
+
+ if (cosmosMutationField != null)
+ {
+ mutationFields.Add(cosmosMutationField);
+ }
+
}
break;
diff --git a/src/Service.GraphQLBuilder/Mutations/UpdateAndPatchMutationBuilder.cs b/src/Service.GraphQLBuilder/Mutations/UpdateAndPatchMutationBuilder.cs
index 7755e015d8..8916864a37 100644
--- a/src/Service.GraphQLBuilder/Mutations/UpdateAndPatchMutationBuilder.cs
+++ b/src/Service.GraphQLBuilder/Mutations/UpdateAndPatchMutationBuilder.cs
@@ -55,7 +55,7 @@ private static bool FieldAllowedOnUpdateInput(FieldDefinitionNode field,
return true;
}
- private static InputObjectTypeDefinitionNode GenerateUpdateInputType(
+ private static InputObjectTypeDefinitionNode? GenerateUpdateInputType(
Dictionary inputs,
ObjectTypeDefinitionNode objectTypeDefinitionNode,
NameNode name,
@@ -65,13 +65,14 @@ private static InputObjectTypeDefinitionNode GenerateUpdateInputType(
EntityActionOperation operation)
{
NameNode inputName = GenerateInputTypeName(operation, name.Value);
+ InputObjectTypeDefinitionNode? input;
if (inputs.ContainsKey(inputName))
{
return inputs[inputName];
}
- IEnumerable inputFields =
+ IEnumerable inputFields =
objectTypeDefinitionNode.Fields
.Where(f => FieldAllowedOnUpdateInput(f, databaseType, definitions, operation, objectTypeDefinitionNode))
.Select(f =>
@@ -89,17 +90,26 @@ private static InputObjectTypeDefinitionNode GenerateUpdateInputType(
return GenerateSimpleInputType(name, f, databaseType, operation);
});
- InputObjectTypeDefinitionNode input =
+ if (inputFields.Any())
+ {
+ List inputFieldsList = inputFields
+ .Where(i => i != null)
+ .Select(i => i!)
+ .ToList();
+ input =
new(
location: null,
inputName,
new StringValueNode($"Input type for updating {name}"),
new List(),
- inputFields.ToList()
+ inputFieldsList
);
- inputs.Add(input.Name, input);
- return input;
+ inputs.Add(input.Name, input);
+ return input;
+ }
+
+ return null;
}
private static InputValueDefinitionNode GenerateSimpleInputType(NameNode name, FieldDefinitionNode f, DatabaseType databaseType, EntityActionOperation operation)
@@ -117,7 +127,7 @@ private static InputValueDefinitionNode GenerateSimpleInputType(NameNode name, F
);
}
- private static InputValueDefinitionNode GetComplexInputType(
+ private static InputValueDefinitionNode? GetComplexInputType(
Dictionary inputs,
IEnumerable definitions,
FieldDefinitionNode f,
@@ -127,7 +137,7 @@ private static InputValueDefinitionNode GetComplexInputType(
DatabaseType databaseType,
EntityActionOperation operation)
{
- InputObjectTypeDefinitionNode node;
+ InputObjectTypeDefinitionNode? node;
NameNode inputTypeName = GenerateInputTypeName(operation, typeName);
if (!inputs.ContainsKey(inputTypeName))
@@ -139,35 +149,40 @@ private static InputValueDefinitionNode GetComplexInputType(
node = inputs[inputTypeName];
}
- ITypeNode type = new NamedTypeNode(node.Name);
-
- // For a type like [Bar!]! we have to first unpack the outer non-null
- if (f.Type.IsNonNullType())
+ if ((node != null))
{
- // The innerType is the raw List, scalar or object type without null settings
- ITypeNode innerType = f.Type.InnerType();
+ ITypeNode type = new NamedTypeNode(node.Name);
+
+ // For a type like [Bar!]! we have to first unpack the outer non-null
+ if (f.Type.IsNonNullType())
+ {
+ // The innerType is the raw List, scalar or object type without null settings
+ ITypeNode innerType = f.Type.InnerType();
- if (innerType.IsListType())
+ if (innerType.IsListType())
+ {
+ type = GenerateListType(type, innerType);
+ }
+
+ // Wrap the input with non-null to match the field definition
+ type = new NonNullTypeNode((INullableTypeNode)type);
+ }
+ else if (f.Type.IsListType())
{
- type = GenerateListType(type, innerType);
+ type = GenerateListType(type, f.Type);
}
- // Wrap the input with non-null to match the field definition
- type = new NonNullTypeNode((INullableTypeNode)type);
- }
- else if (f.Type.IsListType())
- {
- type = GenerateListType(type, f.Type);
+ return new(
+ location: null,
+ f.Name,
+ new StringValueNode($"Input for field {f.Name} on type {inputTypeName}"),
+ type,
+ defaultValue: null,
+ f.Directives
+ );
}
- return new(
- location: null,
- f.Name,
- new StringValueNode($"Input for field {f.Name} on type {inputTypeName}"),
- type,
- defaultValue: null,
- f.Directives
- );
+ return null;
}
private static ITypeNode GenerateListType(ITypeNode type, ITypeNode fieldType)
@@ -201,7 +216,7 @@ private static NameNode GenerateInputTypeName(EntityActionOperation operation, s
/// Runtime config information for the object type.
/// Collection of role names allowed for action, to be added to authorize directive.
/// A update*ObjectName* field to be added to the Mutation type.
- public static FieldDefinitionNode Build(
+ public static FieldDefinitionNode? Build(
NameNode name,
Dictionary inputs,
ObjectTypeDefinitionNode objectTypeDefinitionNode,
@@ -214,7 +229,7 @@ public static FieldDefinitionNode Build(
EntityActionOperation operation = EntityActionOperation.Update,
string operationNamePrefix = UPDATE_MUTATION_PREFIX)
{
- InputObjectTypeDefinitionNode input = GenerateUpdateInputType(
+ InputObjectTypeDefinitionNode? input = GenerateUpdateInputType(
inputs,
objectTypeDefinitionNode,
name,
@@ -234,19 +249,21 @@ public static FieldDefinitionNode Build(
description = "The ID of the item being updated.";
}
- List inputValues = new();
- foreach (FieldDefinitionNode idField in idFields)
+ if (input != null)
{
- inputValues.Add(new InputValueDefinitionNode(
- location: null,
- idField.Name,
- new StringValueNode(description),
- new NonNullTypeNode(idField.Type.NamedType()),
- defaultValue: null,
- new List()));
- }
+ List inputValues = new();
+ foreach (FieldDefinitionNode idField in idFields)
+ {
+ inputValues.Add(new InputValueDefinitionNode(
+ location: null,
+ idField.Name,
+ new StringValueNode(description),
+ new NonNullTypeNode(idField.Type.NamedType()),
+ defaultValue: null,
+ new List()));
+ }
- inputValues.Add(new InputValueDefinitionNode(
+ inputValues.Add(new InputValueDefinitionNode(
location: null,
new NameNode(INPUT_ARGUMENT_NAME),
new StringValueNode($"Input representing all the fields for updating {name}"),
@@ -254,30 +271,33 @@ public static FieldDefinitionNode Build(
defaultValue: null,
new List()));
- // Create authorize directive denoting allowed roles
- List fieldDefinitionNodeDirectives = new()
- {
- new DirectiveNode(
- ModelDirective.Names.MODEL,
- new ArgumentNode(ModelDirective.Names.NAME_ARGUMENT, dbEntityName))
- };
-
- if (CreateAuthorizationDirectiveIfNecessary(
- rolesAllowedForMutation,
- out DirectiveNode? authorizeDirective))
- {
- fieldDefinitionNodeDirectives.Add(authorizeDirective!);
+ // Create authorize directive denoting allowed roles
+ List fieldDefinitionNodeDirectives = new()
+ {
+ new DirectiveNode(
+ ModelDirective.Names.MODEL,
+ new ArgumentNode(ModelDirective.Names.NAME_ARGUMENT, dbEntityName))
+ };
+
+ if (CreateAuthorizationDirectiveIfNecessary(
+ rolesAllowedForMutation,
+ out DirectiveNode? authorizeDirective))
+ {
+ fieldDefinitionNodeDirectives.Add(authorizeDirective!);
+ }
+
+ string singularName = GetDefinedSingularName(name.Value, entities[dbEntityName]);
+ return new(
+ location: null,
+ name: new NameNode($"{operationNamePrefix}{singularName}"),
+ description: new StringValueNode($"Updates a {singularName}"),
+ arguments: inputValues,
+ type: new NamedTypeNode(returnEntityName),
+ directives: fieldDefinitionNodeDirectives
+ );
}
- string singularName = GetDefinedSingularName(name.Value, entities[dbEntityName]);
- return new(
- location: null,
- name: new NameNode($"{operationNamePrefix}{singularName}"),
- description: new StringValueNode($"Updates a {singularName}"),
- arguments: inputValues,
- type: new NamedTypeNode(returnEntityName),
- directives: fieldDefinitionNodeDirectives
- );
+ return null;
}
}
}
diff --git a/src/Service.Tests/GraphQLBuilder/MutationBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
index 8a75724b62..4ebe842c36 100644
--- a/src/Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
+++ b/src/Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
@@ -194,6 +194,41 @@ type Foo @model(name:""Foo"") {
Assert.AreEqual("bar", argType.Fields[0].Name.Value);
}
+ [TestMethod]
+ [TestCategory("Mutation Builder - Create")]
+ [TestCategory("Mutation Builder - Update")]
+ [TestCategory("Mutation Builder - Delete")]
+ public void MutationExcludedForAllAutogeneratedFields()
+ {
+ string gql =
+ @"
+type Foo @model(name:""Foo"") {
+ id: ID! @autoGenerated
+}
+ ";
+
+ DocumentNode root = Utf8GraphQLParser.Parse(gql);
+
+ Dictionary entityNameToDatabasetype = new()
+ {
+ { "Foo", DatabaseType.MSSQL }
+ };
+
+ DocumentNode mutationRoot = MutationBuilder.Build(
+ root,
+ entityNameToDatabasetype,
+ new(new Dictionary { { "Foo", GenerateEmptyEntity() } }),
+ entityPermissionsMap: _entityPermissions);
+
+ ObjectTypeDefinitionNode query = GetMutationNode(mutationRoot);
+ List fieldNames = query.Fields.Select(f => f.Name.Value).ToList();
+
+ // Assert that "createFoo" and "updateFoo" are not present
+ Assert.IsFalse(fieldNames.Contains("createFoo"), "createFoo should not be present");
+ Assert.IsFalse(fieldNames.Contains("updateFoo"), "updateFoo should not be present");
+ Assert.IsTrue(fieldNames.Contains("deleteFoo"), "deleteFoo should be present");
+ }
+
[TestMethod]
[TestCategory("Mutation Builder - Create")]
[TestCategory("Schema Builder - Simple Type")]