How to control presence of OData error details - asp.net-web-api2

I'm trying to control the presence of details in error responses from a Web API 2 OData v4 service. When I hit the OData service hosted on my local IIS, I get something like this:
{
"error": {
"code": "Error code",
"message": "Message from exception filter",
"details": [
{
"code": "Detail code",
"message": "Details here"
}
],
"innererror": {
"message": "Exception message here",
"type": "Exception type",
"stacktrace": "Stack trace here"
}
}
}
When I take the same service and deploy it on a remote server, and hit it with the same message, I get this:
{
"error": {
"code": "Error code",
"message": "Message from exception filter"
}
}
I'm guessing that the "innererror" and "details" sections are suppressed because I'm calling the service remotely? I'm happy that the "innererror" section is suppressed - I don't want to leak those details - but I want to expose the "details" section so that I can provide some more feedback on certain errors. Is there a simple way to achieve this?
Thanks!

I was creating my OData error responses using Request.CreateErrorResponse(myHttpStatusCode, myODataError). Looking at the source code of System.Web.Http.OData.Extensions.HttpRequestMessageExtensions.CreateErrorResponse, it appears that if Request.ShouldIncludeErrorDetail is false, then the ODataError is recreated with just the "code" and "message" items. My solution was to create another overload/extension of CreateErrorResponse which accepts a parameter that controls whether the details section should be included:
public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
HttpStatusCode statusCode, ODataError oDataError, bool includeDetails)
{
if (request.ShouldIncludeErrorDetail())
{
return request.CreateResponse(statusCode, oDataError);
}
else
{
return request.CreateResponse(
statusCode,
new ODataError()
{
ErrorCode = oDataError.ErrorCode,
Message = oDataError.Message,
Details = includeDetails ? oDataError.Details : null
});
}
}
This allows the "innererror" section to be suppressed independently of the "details" section.

Related

Why doesn't GraphQL.NET honour the errors.extensions schema?

I recently rewrote some GraphQL services from Java to .NET Core.
In Java, I was able to provide custom error messages to the clients using the errors.extensions in the response, ie:
{
"data": {
"someMutation": null
},
"errors": [{
"cause": null,
"message": "Unauthorized",
"httpStatusCode": 0,
"extensions": {
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
"httpStatusDescription": "Unauthorized",
"httpStatusCode": 401
},
"errorType": "ValidationError",
"path": null,
"localizedMessage": "Unauthorized",
"suppressed": []
}
]
}
However, in .NET, I don't seem to be able to replicate this format.
ErrorInfo.Extensions is added to the root of the response, not to the the Errors object itself, eg:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Auth token not provided"
}
],
"extensions": {
"httpStatusCode": 401,
"httpStatusDescription": null,
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
}
}
The GraphQL spec reads (ref https://spec.graphql.org/October2021/#sec-Errors, https://spec.graphql.org/October2021/#example-8b658):
GraphQL services may provide an additional entry to errors with key
extensions. This entry, if set, must have a map as its value. This
entry is reserved for implementors to add additional information to
errors however they see fit, and there are no additional restrictions
on its contents.
eg:
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
I created a new test project (.NET Core 3.1) using the latest versions of the libraries (GraphQL 7.1.1 et al) but am still unable to add custom properties to errors.extensions.
This is the test mutation which intentionally throws an exception:
Field<StringGraphType>("greet")
.Argument<NonNullGraphType<StringGraphType>>("name")
.Resolve(context => {
try {
throw new Exception("Invalid input");
return "Hello " + context.GetArgument<String>("name");
} catch(Exception ex) {
// This doesn't seem to get returned anywhere in the response
Dictionary<String, object> extraData = new Dictionary<string, object>();
extraData.Add("error1", "message1");
// Add the error to the response using the overloaded constructor
context.Errors.Add(new ExecutionError("Oh dear, that went wrong", extraData));
// This gets added to the root of the response
context.OutputExtensions.Add("error2", "message2");
return null;
}
});
the mutation to invoke it:
mutation {greet(name:"Chewbacca")}
and the response (I don't know where errors.extensions.details comes from):
{
"errors": [
{
"message": "Oh dear, that went wrong",
"extensions": {
"details": "GraphQL.ExecutionError: Oh dear, that went wrong"
}
}
],
"data": {
"greet": null
},
"extensions": {
"error2": "message2"
}
}
I would imagine that the GraphQL.NET library would expose an Extensions dictionary on the ExecutionError object so one could add custom values in the usual manner, eg:
ExecutionError executionError = new ExecutionError("Oh dear, that went horribly wrong");
executionError.Extensions.Add("customError", "Your custom error here")
context.Errors.Add(executionError);
Which would result in a response similar to this:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Oh dear, that went horribly wrong",
"extensions": {
"customError": "Your custom error here"
}
}
]
}
I am hopeful that some bright individual in the community can (slap me upside the head and) point me in the right direction.

Handle Reactor errors by mapping to new return value without dropping any elements

In a scenario where an input value is mapped to a response object which contains either a success message or a failure message along with an error message, how can I handle errors correctly without dropping any elements from the publisher?
I have a domain object which follows the Builder pattern, and it performs validation on build, throwing an Exception which includes the object's ID.
To process this I've tried the following 2 attempts:
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorResume(e ->
Flux.just(GenericResponse.error(((BadRequestException)e).getId(), e.getMessage()))
);
}
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.concatMap(c ->
Flux.just(GenericResponse.accepted(c.getId()))
.onErrorResume(e ->
Flux.just(GenericResponse.error(((BadRequestException)e).getId(), e.getMessage()))
)
);
}
With the first, if I send through 5 inputs and the third is expected to throw an error, I get 2 success messages, and one failure message as expected:
[
{ "id": 1, "code": "ACCEPTED" },
{ "id": 2, "code": "ACCEPTED" },
{ "id": 3, "code": "ERROR", "description": "Some error message" }
]
However with the second attempt using the same inputs, I get an actual stack trace with no results:
{
"timestamp": 1627880204616,
"path": "/",
"status": 500,
"error": "Internal Server Error",
"message": "Some error message",
"requestId": "2917b3af-1",
"trace": "com.example.BadRequestException: Some error message..."
}
What can I do to get a response for all inputs as below:
[
{ "id": 1, "code": "ACCEPTED" },
{ "id": 2, "code": "ACCEPTED" },
{ "id": 3, "code": "ERROR", "description": "Some error message" },
{ "id": 4, "code": "ACCEPTED" },
{ "id": 5, "code": "ACCEPTED" }
]
If you want to manage error element per element, your best option is to use an intermediate Mono to manage errors like so :
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.flatMap(c -> Mono.fromCallable(() -> c.toDomain())
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorResume(BadRequestException.class, e
-> Mono.just(GenericResponse.error(e.getId(), e.getMessage())));
}
Another option is to use the onErrorContinue operator. It is very close to your original code. However, as stated by the documentation, it is not always safe to use, because in case an error has broken the upstream pipeline, it cannot properly "continue" the flow operations.
Example using onErrorContinue:
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorContinue(BadRequestException.class, e ->
Flux.just(GenericResponse.error(e.getId(), e.getMessage()))
);
}

CREATE EDUCATIONCLASS TEAMS ERROR - Team Definition. Visibility' should be equal to 'HiddenMembership'

When I want to create a team with EducationClass template, I get an error like this. I have not been receiving such an error before. All the same. what could be the problem?
{
"error": {
"code": "BadRequest",
"message": "Failed to execute Templates backend request CreateTeamFromTemplateRequest. Request Url: https://teams.microsoft.com/fabric/emea/templates/api/team, Request Method: POST, Response Status Code: BadRequest, Response Headers: Strict-Transport-Security: max-age=2592000\r\nx-operationid: 2230c436382c754aadfda14e58e7b308\r\nx-telemetryid: 00-2230c436382c754aadfda14e58e7b308-ae2a19a55b3ea34c-00\r\nX-MSEdge-Ref: Ref A: CA7D2D550A204688B37F03E163F4E43C Ref B: LON21EDGE1213 Ref C: 2020-11-23T07:20:16Z\r\nDate: Mon, 23 Nov 2020 07:20:16 GMT\r\n, ErrorMessage : {\"errors\":[{\"message\":\"'Team Definition. Visibility' should be equal to 'HiddenMembership'.\"}],\"operationId\":\"2230c436382c754aadfda14e58e7b308\"}",
"innerError": {
"date": "2020-11-23T07:20:17",
"request-id": "c67774cf-b900-4b77-9850-26f40b82e9b4",
"client-request-id": "c67774cf-b900-4b77-9850-26f40b82e9b4"
}
}
}
POST: https://graph.microsoft.com/beta/teams
{
"template#odata.bind": "https://graph.microsoft.com/beta/teamsTemplates('educationClass')",
"displayName": "My Class Team",
"description": "My Class Team’s Description"
}
Maybe they made a breaking change in the beta version, try using v1.0 and make the request in the following way:
{
"template#odata.bind":"https://graph.microsoft.com/v1.0/teamsTemplates('educationClass')",
"displayName":"My Sample Team",
"description":"My Sample Team’s Description",
"members":[
{
"#odata.type":"#microsoft.graph.aadUserConversationMember",
"roles":["owner"],
"user#odata.bind":"https://graph.microsoft.com/v1.0/users('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')"
}
]
}

self-service APIs error response has multiple formats

When I test self-service APIs, I found that there are 2 different formats for the error response.
case 1: when there is something wrong with token
I got such response
{
"errors": [
{
"code": "38190",
"title": "Invalid access token",
"detail": "The access token provided in the Authorization header is invalid",
"status": "401"
}
]
}
case 2: for other scenario such as INVALID DATE, MANDATORY DATA MISSING etc.
I got the error with another format
{
"errors": [
{
"code": 4926,
"title": "INVALID DATA RECEIVED",
"detail": "travelerType value is not in the allowed enumeration",
"source": {
"pointer": "/travelers[0]/travelerType",
"example": "ADULT"
},
"status": 400
}
]
}
The value of "code" and "status" have type string in case 1, but have type int in case 2.
Is it normal that the access token error has a particular error response format? Thanks

Failed to add a new subscription for my own onedrive 'Documents' folder

I have failed to add a new subscription webhook URL for my personal one drive Documents folder ( not a business account). The request response details is as follows.
URL: https://graph.microsoft.com/v1.0/subscriptions
Try 1
Request JSON:
{
"notificationUrl": "https://****.rest/OneDrive/newItemWebhook",
"expirationDateTime": "2017-09-18T11:23:00.000Z",
"resource": "/me/drive/root/Documents",
"changeType": "updated",
"clientState": "client-specific string"
}
Response JSON:
{
"error": {
"code": "BadRequest",
"message": "Unsupported segment type. ODataQuery:
users/****#outlook.com/drive/root/Documents",
"innerError": {
"request-id": "169755ee-bdf5-460e-8577-6f1e22777207",
"date": "2017-09-18T10:46:07"
}
}
}
Try 2
Request JSON:
{
"notificationUrl": "https://****.rest/OneDrive/newItemWebhook",
"expirationDateTime": "2017-09-18T11:23:00.000Z",
"resource": "/me/drive/root:/Documents",
"changeType": "updated",
"clientState": "client-specific string"
}
Response JSON:
{
"error": {
"code": "InvalidRequest",
"message": "resource '/me/drive/root:/Documents' is not supported.",
"innerError": {
"request-id": "403229b0-0554-46c2-82b8-ede69a4ef9a2",
"date": "2017-09-18T10:50:36"
}
}
}
In both cases the cases the failed to add subscription webhook URL but the reason in unknown to me, means what is :
Unsupported segment type. ODataQuery
resource '/me/drive/root:/Documents' is not supported.
and then if I want to set webhook URL for the above mention "resource" then what will be the path for my case!
Apparently, you cannot subscribe to changes in the particular folder, but to a drive, so you should subscribe to /me/drive/root. Then you could use driveitem delta api to track changes: https://learn.microsoft.com/en-gb/onedrive/developer/rest-api/api/driveitem_delta or use your own method (i.e. list files in the Documents folder)
Here is a bit outdated video explaining the process: https://channel9.msdn.com/Events/Build/2016/P566