Skip to content

Prevent empty input node generation in mutation builder. #2729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 47 additions & 40 deletions src/Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public static class CreateMutationBuilder
/// <param name="databaseType">Database type of the relational database to generate input type for.</param>
/// <param name="entities">Runtime config information.</param>
/// <param name="IsMultipleCreateOperationEnabled">Indicates whether multiple create operation is enabled</param>
/// <returns>A GraphQL input type with all expected fields mapped as GraphQL inputs.</returns>
private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationalDb(
/// <returns>An optional GraphQL input type with all expected fields mapped as GraphQL inputs.</returns>
private static InputObjectTypeDefinitionNode? GenerateCreateInputTypeForRelationalDb(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, I am not entirely sure how the mutation builder works for non-relational databases. So do you think this might also be a bug that could be found there? If so then it would also be a good idea to change the GenerateCreateInputTypeForNonRelationalDb function.

Dictionary<NameNode, InputObjectTypeDefinitionNode> inputs,
ObjectTypeDefinitionNode objectTypeDefinitionNode,
string entityName,
Expand All @@ -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))
{
Expand All @@ -54,32 +55,35 @@ 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<InputValueDefinitionNode> inputFields = new();
List<InputValueDefinitionNode?> inputFields = new();

// 1. Scalar input fields.
IEnumerable<InputValueDefinitionNode> scalarInputFields = objectTypeDefinitionNode.Fields
.Where(field => IsBuiltInType(field.Type) && !IsAutoGeneratedField(field))
.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<DirectiveNode>(),
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<DirectiveNode>(),
inputFields!
);
Comment on lines +78 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
inputFields!
);
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.
Expand All @@ -88,7 +92,7 @@ private static InputObjectTypeDefinitionNode GenerateCreateInputTypeForRelationa
{
// 2. Complex input fields.
// Evaluate input objects for related entities.
IEnumerable<InputValueDefinitionNode> complexInputFields =
IEnumerable<InputValueDefinitionNode?> complexInputFields =
objectTypeDefinitionNode.Fields
.Where(field => !IsBuiltInType(field.Type) && IsComplexFieldAllowedForCreateInputInRelationalDb(field, definitions))
.Select(field =>
Expand Down Expand Up @@ -148,7 +152,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);
}
Expand Down Expand Up @@ -307,8 +311,8 @@ private static InputValueDefinitionNode GenerateScalarInputType(NameNode name, F
/// <param name="objectTypeDefinitionNode">The GraphQL object type to create the input type for.</param>
/// <param name="databaseType">Database type to generate the input type for.</param>
/// <param name="entities">Runtime configuration information for entities.</param>
/// <returns>A GraphQL input type value.</returns>
private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
/// <returns>An Optional GraphQL input type value.</returns>
private static InputValueDefinitionNode? GenerateComplexInputTypeForRelationalDb(
string entityName,
Dictionary<NameNode, InputObjectTypeDefinitionNode> inputs,
IEnumerable<HotChocolate.Language.IHasName> definitions,
Expand All @@ -320,7 +324,7 @@ private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
RuntimeEntities entities,
bool IsMultipleCreateOperationEnabled)
{
InputObjectTypeDefinitionNode node;
InputObjectTypeDefinitionNode? node;
NameNode inputTypeName = GenerateInputTypeName(typeName);
if (!inputs.ContainsKey(inputTypeName))
{
Expand All @@ -340,7 +344,7 @@ private static InputValueDefinitionNode GenerateComplexInputTypeForRelationalDb(
node = inputs[inputTypeName];
}

return GetComplexInputType(field, node, inputTypeName, IsMultipleCreateOperationEnabled);
return node == null ? null : GetComplexInputType(field, node, inputTypeName, IsMultipleCreateOperationEnabled);
}

/// <summary>
Expand Down Expand Up @@ -487,7 +491,7 @@ public static IEnumerable<FieldDefinitionNode> Build(
{
List<FieldDefinitionNode> createMutationNodes = new();
Entity entity = entities[dbEntityName];
InputObjectTypeDefinitionNode input;
InputObjectTypeDefinitionNode? input;
if (!IsRelationalDb(databaseType))
{
input = GenerateCreateInputTypeForNonRelationalDb(
Expand Down Expand Up @@ -528,28 +532,31 @@ public static IEnumerable<FieldDefinitionNode> 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<InputValueDefinitionNode> {
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<InputValueDefinitionNode> {
new(
location : null,
new NameNode(MutationBuilder.ITEM_INPUT_ARGUMENT_NAME),
new StringValueNode($"Input representing all the fields for creating {name}"),
new NonNullTypeNode(new NamedTypeNode(input.Name)),
defaultValue: null,
new List<DirectiveNode>())
},
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(
Expand Down
53 changes: 31 additions & 22 deletions src/Service.GraphQLBuilder/Mutations/MutationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,31 +152,40 @@ 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);

if (cosmosMutationField != null)
{
mutationFields.Add(cosmosMutationField);
}

}

break;
Expand Down
Loading
Loading