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

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()))
);
}

Related

Filter CloudWatch logs with a property selector for a JSON property containing a period character "."

How would I write a CloudWatch filter with a property selector for a JSON property containing a period character "."?
The CloudWatch documentation describes using a property selector to filter log data (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#metric-filters-extract-json):
Documentation Syntax:
{ PropertySelector EqualityOperator String }
Documentation Example:
{ $.eventType = "UpdateTrail" }
If a log event contains a JSON property containing a period character ".", is it possible to describe it with a property selector?
I have attempted the following property selectors:
Filter encounters error { $.attributes.http\.method = "POST" }
Filter encounters error { $.attributes["http.method"] = "POST" }
Filter produces zero results { $.attributes.http.method = "POST" }
Example Event
{
"traceId": "75e07edf6f50ddf0d4a8239cbe91d60a",
"parentId": "f8875623e2e77de4",
"name": "request handler - /subscriptions",
"id": "7f8b38dcbbb15983",
"kind": 0,
"timestamp": 1673621283065,
"duration": 3,
"attributes": {
"http.route": "/subscriptions",
"express.name": "/subscriptions",
"express.type": "request_handler",
"http.method": "POST",
"http.url": "/subscriptions",
"http.body": "{\"foo\":\"bar\"}"
},
"status": {
"code": 0
},
"events": [],
"links": []
}

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.

AppSync request mapping template errors not logged in CloudWatch

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?

How to match string and ignore the case in karate?

There is a case where one value sometimes is lower case and sometimes it's upper case. this is the response coming from an API and we have to match if every field in response is correct by ignoring some values. The error text in response sometimes has one keyword in lower and some scenarios it is upper case. How we can ignore one keyword in a string to not match? I don't want to ignore whole text as it works fine if I ignore whole string but is it possible to ignore one keyword only?
Scenario: string matching
* def test =
"""
{
"sourceType": "Error",
"id": "123456",
"type": "searchuser",
"total": 0,
"value": [
{
"details": "this is the user search case",
"source": {
"sourceType": "Error",
"id": "77200203043",
"issue": [
{
"severity": "high",
"code": "678",
"message": {
"text": "No matching User details found"
},
"errorCode": "ERROR401"
}
]
},
"user": {
"status": "active"
}
}
]
}
"""
* match test ==
"""
{
"sourceType": "Error",
"id": "#present",
"type": "searchuser",
"total": 0,
"value": [
{
"details": "#present",
"source": {
"sourceType": "Error",
"id": "#ignore",
"issue": [
{
"severity": "high",
"code": "678",
"message": {
"text": "No matching User details found"
},
"errorCode": "ERROR401"
}
]
},
"user": {
"status": "active"
}
}
]
}
"""
How to ignore the case only for user here? I tried below but it treats #ignore as a value.
"text": "No matching #ignore details found"
I'm not looking at your payload dump but providing a simple example. Use karate.lowerCase():
* def response = { foo: 'Bar' }
* match karate.lowerCase(response) == { foo: 'bar' }
EDIT: you can also extract one value at a time and do a check only for that:
* def response = { foo: 'Bar' }
* def foo = response.foo
* match karate.lowerCase(foo) == 'bar'

INVALID_ARGUMENT error with Google Analytics Reporting API

I'm trying to do a request to the userActivity.search method,
this is the payload that I'm trying to do:
{
"viewId": "<VIEW ID>",
"dateRange": {
"startDate": "7daysAgo",
"endDate": "today"
},
"user": {
"type": "CLIENT_ID",
"userId": "<USER ID>"
}
}
But I'm stuck getting this error:
{
"error": {
"code": 400,
"message": "CLIENT_ID: <ID> not found.",
"status": "INVALID_ARGUMENT"
}
}
The ClientId I get previously at other GA endpoint that list the ClientIds that I need to get more details.
What am I doing wrong?