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.
Related
Crosspost from: https://repost.aws/questions/QUp5jDZ6bsRkeXhIwHgQaWkg/app-sync-request-mapping-template-errors-not-logged-in-cloud-watch
I have a simple resolver that has a simple Lambda function as a data source. This function always throws an error (to test out logging).
The resolver has request mapping template enabled and it is configured as follows:
$util.error("request mapping error 1")
The API has logging configured to be as verbose as possible yet I cannot see this request mapping error 1 from my CloudWatch logs in RequestMapping log type:
{
"logType": "RequestMapping",
"path": [
"singlePost"
],
"fieldName": "singlePost",
"resolverArn": "xxx",
"requestId": "bab942c6-9ae7-4771-ba45-7911afd262ac",
"context": {
"arguments": {
"id": "123"
},
"stash": {},
"outErrors": []
},
"fieldInError": false,
"errors": [],
"parentType": "Query",
"graphQLAPIId": "xxx"
}
The error is not completely lost because I can see this error in the query response:
{
"data": {
"singlePost": null
},
"errors": [
{
"path": [
"singlePost"
],
"data": null,
"errorType": null,
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "request mapping error 1"
}
]
}
When I add $util.appendError("append request mapping error 1") to the request mapping template so it looks like this:
$util.appendError("append request mapping error 1")
$util.error("request mapping error 1")
Then the appended error appears in the RequestMapping log type but the errors array is still empty:
{
"logType": "RequestMapping",
"path": [
"singlePost"
],
"fieldName": "singlePost",
"resolverArn": "xxx",
"requestId": "f8eecff9-b211-44b7-8753-6cc6e269c938",
"context": {
"arguments": {
"id": "123"
},
"stash": {},
"outErrors": [
{
"message": "append request mapping error 1"
}
]
},
"fieldInError": false,
"errors": [],
"parentType": "Query",
"graphQLAPIId": "xxx"
}
When I do the same thing with response mapping template then everything works as expected (errors array contains $util.error(message) and outErrors array contains $util.appendError(message) messages.
Is this working as expected so the $util.error(message) will never show up in RequestMapping type CloudWatch logs?
Under what conditions will errors array in RequestMapping log type be populated?
Bonus question: can the errors array contain more than 1 item for either RequestMapping or ResponseMapping log types?
We are using the API /skillsFutureCredits/claims/encryptRequests with the following payload
{
"claimRequest": {
"course": {
"id": "TGS-2020002051",
"fee": "1.00",
"runId": "278849",
"startDate": "2022-04-28"
},
"individual": {
"nric": "T5001077A",
"email": "abc#abc.com",
"homeNumber": "87654321",
"mobileNumber": "12345678"
},
"additionalInformation": "any additional information"
}
}
We get the encrypted response back and run /skillsFutureCredits/claims/decryptRequests with the following payload
{ "claimRequestStatus": "eyJ2IjoiIiwiaXYiOiJ4eS9XTGdiM3l2cHNEK1VENzh3TVFBa1BSWGpHSzhzbmJ0QUI4YUlTZmtNPSIsImtleXMiOnsiZGQ6YTk6YzM6NGY6Yzk6YWQ6OTY6NjQ6ZDc6YWQ6NDY6M2Q6ZDE6YmI6OGM6MDQ6Y2E6OGY6YzA6MWUiOiJWWnN1d2xacGtZdEdKZEtnV3dMblJxMGNmN1ZwK3ZXbjcvWFhEOG5TdVBJMGpRRkFIRG0vaUM5ejRPTEt4ODhST21pQ1p3WEVXU2NHd0lod2htMnBvM0ExMEdYZy80eWdDVEJhNkI1NVQ1TmdvWmlGTS9LWlBVRHFoVzFrUXQ3alB3anlTZm81Z0M5a0JtSnJDbXlJZ05veXFQV2RPcytBQ0J2SHgrUGVqT1d3T2trYThJbm9LS2NqenhHV2hKbWpoWEl2ZFM3aDUwdE10TmJQMk5wbTdvZWROeUl1OWw0Ty9BV3FWQmtYVGY2ZFRvb2J5OFJsM2JJcHhFQmp6Qy9pYzZBN3N5OGFrZDQ4Znk4WWhyTngvdXpDZ0NENVl0bElHSmFpYmt5bm55ZWNBS1NEbFdqMnNlOGdsbmdFNUZCa0hCZjdmWDVuU2VydXdmRDl4Q1MrbnRSYU5Yc3NweHJ2bE9sMmpWN0l1dENId2JvNmRnS3laMVRDSXc0Q2tta2tuY1VrcWluZk5nNjBHaFNOTXI3aGlUWmg4RnNESkJ4TmEzMFRpY1IvNExtTGdncFNyKy9HdDRweW1WTitiUDRuQ0o4QSt1L0VuVEJOcnEzZ3RCczVkYmMzdjdhT3BQTXMyTHIvK2RtdENDTEVWNWpZUkVsZDM5ZkVVd1lXUVZZOWduVFpRUTQxbUs4N3RPc04vNTJWc095djNackUzWlBCUzk0YzMrQk9sQk9zRjdBVjFZSEVHalR6R0J0cTRTLy9XVGN2d2x0Q2JmS2s3RDFjanNMWHNyN1p5cmY4c2ZYYVZWR204ZC9hUGNpWm1HM0tJNHdZVzNMR1NHOXprb2dPc3hZcFlRNm5vMFowa0RXQ2ppTG5NQS8xdG8relhQdXJoS0FMeGdWbG44UT0ifSwiY2lwaGVyIjoiRnlvV0kxRS9FelNtQnZuRTY0SWVQcGs4b2pTNTBJd1habWt6aVRvdWhrbUllQ25SMzRKaUdCejlCZVF5Y1g5c3krOE4zRlVTNEhwWTB2WFNlL1FzMnVGUlgrOXl2TUNRRWh1K2M1eG5KeUNRb3BwWEF1aUM0VklpMDB1eHpzUC92UWhVcVIwQW5CR01tbml5TnNOTUtOL3pUUmV4ZE1GMVhOVHc4eHc1cElaSnFhRjdJMGNtZ1BaQ0xqdjJiUzFKRVRpQk9yUEtmUWc4OFhHNDJ2RlI5Z1JWcVp2dVpYaXNidTJDYkczSGZ4aUpySXZVc1N0ZFlxRVRic1dpL1JVdUo0QzdpMHgwaTBsbkJoK1lHRndHWVRXQWVCLy9iUDBjdE1WbDNoL2tlbGZ6ZHZvajJYOGJIRHJVTEtXTCt2R3lXd0xMUG05NzVVdjQ2OHEwRDFLMWs0K0tPM2lEbmtDd05IVlNFYmdDRy91ckQyTGRuWWlQN1V6VU44NXVXYzBDdjNERUtCUHJ2a1RRUExkTjhOVDN2SVcxTmNicGVlK2xKc3JTZmZ5cGdLbHpaempmRzYrU2JRMHZuMG45TEFqV2NFdFZXOGR3Nk40dWo2bG4vcG9KYUlHdmxOWVdHbjd0T3V1cG95RG1PeCtKUURRbVdkTFd1cjZDY2xkcVhidGdsWFZyQkpwZTFRVWp4dHc5OTF0TGVBPT0ifQ==" }
The response we got back is error
"error": "42+fmhx6gjvNyCQsf1ktG7nFNcfsjarCS1dVJ71ehiTxJcBz9utPN6K9Dv0jpISN7U4jy7Dv5jxcueQyQbC4qq/JWX7o7Pe0OeKUSYIMT/0xaPD19yOOyxuY0y+LWDhV9VP4oTFIGM/2hhoJRuLfGwVevQdQsRh3XLfcpPccO4Xer4D2KelIFACofkn0goY1"
which when decrypt, it is an empty data. In post man, it is showing 500 internal server error but not showing us what error it is.
Any help is greatly appreciated.
We use terraform to deploy BigQuery objects (datasets, tables, routines etc..) to region europe-west2 in GCP. We do this many many times a day and all of a sudden at "2021-08-18T21:15:44.033910202Z" our deployments starting failing when attempting to deploy BigQuery routines. They are all failing with errors of the form:
status: {
code: 3
message: "Unknown option: description"
}
Here is the first log message I can find pertaining to this error (I have redacted project names):
"protoPayload": {
"#type": "type.googleapis.com/google.cloud.audit.AuditLog",
"status": {
"code": 3,
"message": "Unknown option: description"
},
"authenticationInfo": {
"principalEmail": "deployer-dev#myadminproject.iam.gserviceaccount.com",
"serviceAccountDelegationInfo": [
{
"firstPartyPrincipal": {
"principalEmail": "deployer-dev#myadminproject.iam.gserviceaccount.com"
}
}
]
},
"requestMetadata": {
"callerIp": "10.51.0.116",
"callerSuppliedUserAgent": "Terraform/0.14.7 (+https://www.terraform.io) Terraform-Plugin-SDK/2.5.0 terraform-provider-google/3.69.0,gzip(gfe)",
"callerNetwork": "//compute.googleapis.com/projects/myadminproject/global/networks/__unknown__",
"requestAttributes": {},
"destinationAttributes": {}
},
"serviceName": "bigquery.googleapis.com",
"methodName": "google.cloud.bigquery.v2.RoutineService.InsertRoutine",
"authorizationInfo": [
{
"resource": "projects/myproject/datasets/p00003818_dp_model",
"permission": "bigquery.routines.create",
"granted": true,
"resourceAttributes": {}
}
],
"resourceName": "projects/myproject/datasets/p00003818_dp_model/routines/UserProfile_Events_AllCarData_Deployment",
"metadata": {
"routineCreation": {
"routine": {
"routineName": "projects/myproject/datasets/p00003818_dp_model/routines/UserProfile_Events_AllCarData_Deployment"
},
"reason": "ROUTINE_INSERT_REQUEST"
},
"#type": "type.googleapis.com/google.cloud.audit.BigQueryAuditMetadata"
}
},
"insertId": "ak27xdbke",
"resource": {
"type": "bigquery_dataset",
"labels": {
"dataset_id": "p00003818_dp_model",
"project_id": "myproject"
}
},
"timestamp": "2021-08-18T21:15:43.109609Z",
"severity": "ERROR",
"logName": "projects/myproject/logs/cloudaudit.googleapis.com%2Factivity",
"receiveTimestamp": "2021-08-18T21:15:44.033910202Z"
}
The fact that this occurred without any changes by ourselves indicates that this is a problem at the Google end. I also observe that whilst we witnessed this in a few projects it occurred first in one project and then a few minutes later in another - that may or may not be helpful information.
Posting here in case anyone else hits this problem and also hoping it might catch the attention of a googler.
UPDATE! I have reproduced the pproblem using the REST API https://cloud.google.com/bigquery/docs/reference/rest/v2/routines/insert
I have entered a payload that does not include a description and that successfully creates a routine:
However, if I include a description which, as this screenshot indicates, is a valid parameter:
then the request fails:
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()))
);
}
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