-
Notifications
You must be signed in to change notification settings - Fork 357
Improve GraphQL naming conflict error to identify conflicting entities and operations #3722
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
base: main
Are you sure you want to change the base?
Changes from all commits
65cacb3
7803d3d
0de482c
7d9ce09
644df24
b270de9
3dd7c0e
9c16988
1557e34
58139ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -910,6 +910,9 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(DatabaseType | |
| { | ||
| HashSet<string> graphQLOperationNames = new(); | ||
|
|
||
| // Tracks which entity registered each operation name, used only for building conflict error messages. | ||
| Dictionary<string, string> operationOwner = new(); | ||
|
|
||
| foreach ((string entityName, Entity entity) in entityCollection) | ||
| { | ||
| if (!entity.GraphQL.Enabled) | ||
|
|
@@ -918,6 +921,7 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(DatabaseType | |
| } | ||
|
|
||
| bool containsDuplicateOperationNames = false; | ||
| string conflictingEntityName = string.Empty; | ||
| if (entity.Source.Type is EntitySourceType.StoredProcedure) | ||
| { | ||
| // For Stored Procedures a single query/mutation is generated. | ||
|
|
@@ -926,6 +930,11 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(DatabaseType | |
| if (!graphQLOperationNames.Add(storedProcedureQueryName)) | ||
| { | ||
| containsDuplicateOperationNames = true; | ||
| conflictingEntityName = operationOwner.GetValueOrDefault(storedProcedureQueryName, string.Empty); | ||
| } | ||
| else | ||
| { | ||
| operationOwner[storedProcedureQueryName] = entityName; | ||
| } | ||
| } | ||
| else | ||
|
|
@@ -952,13 +961,44 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(DatabaseType | |
| || ((databaseType is DatabaseType.CosmosDB_NoSQL) && !graphQLOperationNames.Add(patchMutationName))) | ||
| { | ||
| containsDuplicateOperationNames = true; | ||
| conflictingEntityName = | ||
| operationOwner.GetValueOrDefault(pkQueryName) ?? | ||
| operationOwner.GetValueOrDefault(listQueryName) ?? | ||
| operationOwner.GetValueOrDefault(createMutationName) ?? | ||
| operationOwner.GetValueOrDefault(updateMutationName) ?? | ||
| operationOwner.GetValueOrDefault(deleteMutationName) ?? | ||
| operationOwner.GetValueOrDefault(patchMutationName) ?? | ||
| string.Empty; | ||
| } | ||
| else | ||
| { | ||
| operationOwner[pkQueryName] = entityName; | ||
| operationOwner[listQueryName] = entityName; | ||
| operationOwner[createMutationName] = entityName; | ||
| operationOwner[updateMutationName] = entityName; | ||
| operationOwner[deleteMutationName] = entityName; | ||
| if (databaseType is DatabaseType.CosmosDB_NoSQL) | ||
| { | ||
| operationOwner[patchMutationName] = entityName; | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+973
to
985
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should not be possible since the if statement that is used previously to this already checks all the possible operationOwners with the name
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate only mode can lose operation ownership after an earlier conflict. The || chain mutates graphQLOperationNames incrementally, but operationOwner is populated only in the else block after every Add succeeds. If an earlier Add succeeds and a later Add fails, the successfully added operation remains in graphQLOperationNames without an owner. Because validate-only mode continues processing, a later entity can collide with that operation and the resulting message omits the actual conflicting entity. You can repro this with First (Alpha/Shared), Second (Beta/Shared), and Third (Beta/Thirds). Second successfully adds beta_by_pk before failing on Shared, but beta_by_pk is never assigned an owner. Third then conflicts on beta_by_pk, and its recorded error lists only Third instead of identifying Second. I think we should record ownership immediately for each successful Add, or possibly use the operation-to-owner dictionary as the authoritative duplicate check. A validate-only regression test should cover this sequence. |
||
|
|
||
| if (containsDuplicateOperationNames) | ||
| { | ||
| string entitiesStr = string.IsNullOrEmpty(conflictingEntityName) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new message can report non-conflicting names as shared. Isn't this incorrect for singular-only and plural-only conflicts, where the other configured name can be different? Likewise for stored-procedure/table conflicts, where the generated operation fields can collide even though the entities singular and plural type names differ. I'm thinking we should probably track and display only the generated names that actually conflict, and if the implementation can not retain that information, this section should be removed or reworded so it does not claim both entities generate both displayed names. |
||
| ? $" {entityName}" | ||
| : $" {conflictingEntityName}{Environment.NewLine} {entityName}"; | ||
|
|
||
| string entityNamesStr = $" {GraphQLNaming.GetDefinedSingularName(entityName, entity)}{Environment.NewLine} {GraphQLNaming.GetDefinedPluralName(entityName, entity)}"; | ||
|
|
||
| string message = $"{Environment.NewLine}GraphQL naming conflict detected." | ||
| + $"{Environment.NewLine}{Environment.NewLine}Entities:{Environment.NewLine}{entitiesStr}" | ||
| + $"{Environment.NewLine}{Environment.NewLine}Both entities generate the following GraphQL names:{Environment.NewLine}{entityNamesStr}" | ||
| + $"{Environment.NewLine}{Environment.NewLine}Configure distinct GraphQL singular and plural names for one of the entities to resolve this conflict."; | ||
|
RubenCerna2079 marked this conversation as resolved.
|
||
|
|
||
| HandleOrRecordException(new DataApiBuilderException( | ||
| message: $"Entity {entityName} generates queries/mutation that already exist", | ||
| message: message, | ||
| statusCode: HttpStatusCode.ServiceUnavailable, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError)); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lookup reports only the first conflicting owner, but one entity can conflict with different prior entities on different generated names.
For example, if A uses Alpha/Shared, B uses Beta/Betas, and C uses Beta/Shared, C conflicts with B through its singular-derived operations and with A through its plural list query. The current null-coalescing chain reports only B and omits A, so the user may fix the reported conflict only to encounter another startup failure for the same entity.
Could we collect all distinct owners of the conflicting generated names and include every conflicting entity in the diagnostic instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the first conflicting is enough. The developers can walk through all errors one by one, until they succeed. It would be a lot of heavy lifting to check for all conflicting errors.