Skip to content

Improve GraphQL error handling by mapping all DataApiBuilderException types to appropriate HTTP status codes #2692

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
71 changes: 65 additions & 6 deletions src/Core/Services/DabGraphQLResultSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,79 @@ namespace Azure.DataApiBuilder.Core.Services;
/// <summary>
/// The DabGraphQLResultSerializer inspects the IExecutionResult created by HotChocolate
/// and determines the appropriate HTTP error code to return based on the errors in the result.
/// By Default, without this serializer, HotChocolate will return a 500 status code when database errors
/// exist. However, there is a specific error code we check for that should return a 400 status code:
/// - DatabaseInputError. This indicates that the client can make a change to request contents to influence
/// a change in the response.
///
/// By default, without this serializer, HotChocolate will return a 500 status code for errors.
/// This serializer maps DataApiBuilderException.SubStatusCodes to their appropriate HTTP status codes.
///
/// For example:
/// - Authentication/Authorization errors will return 401/403
/// - Database input validation errors will return 400 BadRequest
/// - Entity not found errors will return 404 NotFound
///
/// This ensures that GraphQL endpoints return appropriate and consistent HTTP status codes
/// for all types of DataApiBuilderException errors.
/// </summary>
public class DabGraphQLResultSerializer : DefaultHttpResultSerializer
{
public override HttpStatusCode GetStatusCode(IExecutionResult result)
{
if (result is IQueryResult queryResult && queryResult.Errors?.Count > 0)
{
if (queryResult.Errors.Any(error => error.Code == DataApiBuilderException.SubStatusCodes.DatabaseInputError.ToString()))
// Check if any of the errors are from DataApiBuilderException by looking at error.Code
foreach (var error in queryResult.Errors)
{
return HttpStatusCode.BadRequest;
if (error.Code != null &&
Enum.TryParse<DataApiBuilderException.SubStatusCodes>(error.Code, out var subStatusCode))
{
// Map SubStatusCodes to appropriate HTTP status codes
switch (subStatusCode)
{
// Authentication/Authorization errors
case DataApiBuilderException.SubStatusCodes.AuthenticationChallenge:
return HttpStatusCode.Unauthorized; // 401
case DataApiBuilderException.SubStatusCodes.AuthorizationCheckFailed:
case DataApiBuilderException.SubStatusCodes.DatabasePolicyFailure:
case DataApiBuilderException.SubStatusCodes.AuthorizationCumulativeColumnCheckFailed:
return HttpStatusCode.Forbidden; // 403

// Not Found errors
case DataApiBuilderException.SubStatusCodes.EntityNotFound:
case DataApiBuilderException.SubStatusCodes.ItemNotFound:
case DataApiBuilderException.SubStatusCodes.RelationshipNotFound:
case DataApiBuilderException.SubStatusCodes.RelationshipFieldNotFound:
case DataApiBuilderException.SubStatusCodes.DataSourceNotFound:
return HttpStatusCode.NotFound; // 404

// Bad Request errors
case DataApiBuilderException.SubStatusCodes.BadRequest:
case DataApiBuilderException.SubStatusCodes.DatabaseInputError:
case DataApiBuilderException.SubStatusCodes.InvalidIdentifierField:
case DataApiBuilderException.SubStatusCodes.ErrorProcessingData:
case DataApiBuilderException.SubStatusCodes.ExposedColumnNameMappingError:
case DataApiBuilderException.SubStatusCodes.UnsupportedClaimValueType:
case DataApiBuilderException.SubStatusCodes.ErrorProcessingEasyAuthHeader:
return HttpStatusCode.BadRequest; // 400

// Not Supported errors
case DataApiBuilderException.SubStatusCodes.NotSupported:
case DataApiBuilderException.SubStatusCodes.GlobalRestEndpointDisabled:
return HttpStatusCode.NotImplemented; // 501

// Conflict errors
case DataApiBuilderException.SubStatusCodes.OpenApiDocumentAlreadyExists:
return HttpStatusCode.Conflict; // 409

// Server errors - Internal Server Error
case DataApiBuilderException.SubStatusCodes.ConfigValidationError:
case DataApiBuilderException.SubStatusCodes.ErrorInInitialization:
case DataApiBuilderException.SubStatusCodes.DatabaseOperationFailed:
case DataApiBuilderException.SubStatusCodes.GraphQLMapping:
case DataApiBuilderException.SubStatusCodes.UnexpectedError:
case DataApiBuilderException.SubStatusCodes.OpenApiDocumentCreationFailure:
default:
return HttpStatusCode.InternalServerError; // 500
}
}
}
}

Expand Down
Loading