Rest API with same HTTP status code, different error responses for one endpoint - spring-boot

We are developing our microservices using Spring Boot and OpenAPI.
Each endpoint could possibly return many business exceptions (an errorCode) and their HTTP Status Codes are all the same (say 400).
Below is a pseudo code of one of the endpoints:
#RestController
#RequestMapping("/services")
public interface MyRestController {
#PostMapping("/service1")
HttpResponse executeService(HttpRequest request)
throws FirstBusinessException, SecondBusinessException;
}
And one of the following HTTP Responses would be returned, depending on the exception which occurred:
{
"errorCode": "FirstBusinessException",
"message": "A simple message for FirstBusinessException",
"errorParams": {
"key1": "value1",
"key2": "value2"
}
}
{
"errorCode": "SecondBusinessException",
"message": "A simple message for SecondBusinessException",
"errorParams": {
"key1": "value1",
"key2": "value2"
}
}
Status Code in both of the HTTP Header Responses are 400
We want to present it in our OpenAPI documentation, as it is important to us to tell our clients about each possible error of each endpoint.
Currently our workaround solution is documenting a list of all possible exceptions of the endpoint in its description.
Now I have following questions:
Is there any better (more standard) solution to this workaround?
(I would appreciate if you could demonstrate it with springdoc-openapi way of doing it)
I've also seen anyOf/oneOf feature that has been added since OpenAPI V3, but it requires different schemas. Should we really use different schemas for our exceptions (Instead of having a single one with for example errorCode, message, and errorParam fields, like above) ?

I believe what you are looking for is Spring's #ControllerAdvice. Something like this
#ControllerAdvice
public RestExceptionHandler {
#ExceptionHandler(FirstBusinessException.class)
public HttpResponse handleFirstBusinessException(FirstBusinessException ex, HttpRequest request) {
... do logic and return response
}
#ExceptionHandler(SecondBusinessException.class)
public HttpResponse handleSecondBusinessException(SecondBusinessException ex, HttpRequest request) {
... do logic and return response
}
}
This allows you to return the same HttpStatus for both errors, but a custom error message and/or response entity for each.
Then, in your openApi documentation, you can set it up like this
'400':
description: You have made an invalid request
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ExceptionOne"
- $ref: "#/components/schemas/ExceptionTwo"
examples:
Exception One:
value:
Exception: invalid Id Provided
Provided ID: My ID 1
Message: don't do that
Exception two:
value:
Exception: incomplete request
message: missing one or more fields within your json request
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
When you provide multiple examples, this provides a drop down in your ui (I'm using swagger here) to show your users what the various responses may look like.
You can use these responses and their titles to better explain. For example, instead of calling it ExceptionOne you could call it Missing ID, or Duplicate ID.
If this doesn't work for you, another option is to make use of markdown syntax in your description to make your description more robust. For example:
description: |
# You have made an invalid request
## Exceptions contained here include:
1. Bad ID
2. Your json failed validation
3. You can't follow instructions
| exception | fix |
| --- | --- |
|badId | fix ID|
| failed validation | read validation error message |
| can't follow instructions | read better |
---
If these are in error, contact us:</br>
**phone**: 555-555-5555 </br>
**email**: us#help.com
Have a nice day.
will render as

Related

/* in transcoding from HTTP to gRPC

rpc CreateBook(CreateBookRequest) returns (Book) {
option (google.api.http) = {
post: "/v1/{parent=publishers/*}/books"
body: "book"
};
}
message CreateBookRequest {
// The publisher who will publish this book.
// When using HTTP/JSON, this field is automatically populated based
// on the URI, because of the `{parent=publishers/*}` syntax.
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
child_type: "library.googleapis.com/Book"
}];
Book book = 2 [(google.api.field_behavior) = REQUIRED];
string book_id = 3;
}
I don't understand post: "/v1/{parent=publishers/*}/books"
I thought publishers was a field in CreateBookRequest, then it populates to http, so it is something like this
post: "/v1/parent=publishers_field_value/books"
But publishers is not a field in CreateBookRequest
No, publishers is part of the expected value of the parent field. So suppose you have a protobuf request like this:
{
"parent": "publishers/pub1",
"book_id": "xyz"
"book": {
"author": "Stacy"
}
}
That can be transcoded by a client into an HTTP request with:
Method: POST
URI: /v1/publishers/pub1/books?bookId=xyz (with the appropriate host name)
Body:
{
"author": "Stacy"
}
If you try to specify a request with a parent that doesn't match publishers/*, I'd expect transcoding to fail.
That's in terms of transcoding from protobuf to HTTP, in the request. (That's the direction I'm most familiar with, having been coding it in C# just this week...)
In the server, it should just be the opposite - so given the HTTP request above, the server should come up with the original protobuf request including parent="publishers/pub1".
For a lot more information on all of this, see the proto defining HttpRule.

GraphQL Request: Determine requested resource directly out of request

unlike REST, GraphQL has only one endpoint, usually called /graphql.
I have had good experiences with REST by outsourcing the authorisation to a separate upstream service (e.g. to a proxy like Nginx / Envoy in combination with Open Policy Agent) and using the path and the HTTP verb for the decision. For example, the GET /billing route could only be used by a user with the JWT roles claim "accountant".
Now I am looking for a way to adapt this with GraphQL.
The only possibility I have found is to interpret the query in the request body, e.g.:
body: {
query: 'query {\r\n cats {\r\n id,\r\n name\r\n }\r\n}\r\n'
}
However, this seems to be quite complex and error-prone, as a lot of knowledge and logic would have to be outsourced, especially since the proxies (resp. OPA / other authorisation solutions) don't necessarily have any GraphQL capabilities.
Is there any better way to trustworthily identify which resolver / query / mutation / entity is being requested in a GraphQL request? Headers and other enrichments set by the client are not suitable here, right?
I would highly appreciate any appraoch!
That does indeed look error prone. The GraphQL docs recommend moving authorization checks to the business logic layer. Quoting their example here for completeness:
// Authorization logic lives inside postRepository
var postRepository = require('postRepository');
var postType = new GraphQLObjectType({
name: ‘Post’,
fields: {
body: {
type: GraphQLString,
resolve: (post, args, context, { rootValue }) => {
return postRepository.getBody(context.user, post);
}
}
}
});
So rather than trying to parse the query the authz check is done in the resolver. Some discussion on using OPA with GraphQL can be found in this issue from the OPA contrib repo.

Document all potential errors on GraphQL server?

For a mutation addVoucher there are a limited list of potential errors that can occur.
Voucher code invalid
Voucher has expired
Voucher has already been redeemed
At the moment I'm throwing a custom error when one of these occurs.
// On the server:
const addVoucherResolver = () => {
if(checkIfInvalid) {
throw new Error('Voucher code invalid')
}
return {
// data
}
}
Then on the client I search the message description so I can alert the user. However this feels brittle and also the GraphQL API doesn't automatically document the potential errors. Is there a way to define the potential errors in the GraphQL schema?
Currently my schema looks like this:
type Mutation {
addVoucherResolver(id: ID!): Order
}
type Order {
cost: Int!
}
It would be nice to be able to do something like this:
type Mutation {
addVoucherResolver(id: ID!): Order || VoucherError
}
type Order {
cost: Int!
}
enum ErrorType {
INVALID
EXPIRED
REDEEMED
}
type VoucherError {
status: ErrorType!
}
Then anyone consuming the API would know all the potential errors. This feels like a standard requirement to me but from reading up there doesn't seem to be a standardises GraphQL approach.
It's possible to use a Union or Interface to do what you're trying to accomplish:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
}
union AddVoucherPayload = Order | VoucherError
You're right that there isn't a standardized way to handle user-visible errors. With certain implementations, like apollo-server, it is possible to expose additional properties on the errors returned in the response, as described here. This does make parsing the errors easier, but is still not ideal.
A "Payload" pattern has emerged fairly recently for handling these errors as part of the schema. You see can see it in public API's like Shopify's. Instead of a Union like in the example above, we just utilize an Object Type:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
otherMutation: OtherMutationPayload
}
type AddVoucherPayload {
order: Order
errors: [Error!]!
}
type OtherMutationPayload {
something: Something
errors: [Error!]!
}
type Error {
message: String!
code: ErrorCode! # or a String if you like
}
enum ErrorCode {
INVALID_VOUCHER
EXPIRED_VOUCHER
REDEEMED_VOUCHER
# etc
}
Some implementations add a status or success field as well, although I find that making the actual data field (order is our example) nullable and then returning null when the mutation fails is also sufficient. We can even take this one step further and add an interface to help ensure consistency across our payload types:
interface Payload {
errors: [Error!]!
}
Of course, if you want to be more granular and distinguish between different types of errors to better document which mutation can return what set of errors, you won't be able to use an interface.
I've had success with this sort of approach, as it not only documents possible errors, but also makes it easier for clients to deal with them. It also means that any other errors that are returned with a response should serve as an immediately red flag that something has gone wrong with either the client or the server. YMMV.
You can use scalar type present in graphql
just write scalar JSON and return any JSON type where you want to return it.
`
scalar JSON
type Response {
status: Boolean
message: String
data: [JSON]
}
`
Here is Mutation which return Response
`
type Mutation {
addVoucherResolver(id: ID!): Response
}
`
You can return from resolver
return {
status: false,
message: 'Voucher code invalid(or any error based on condition)',
data: null
}
or
return {
status: true,
message: 'Order fetch successfully.',
data: [{
object of order
}]
}
on Front end you can use status key to identify response is fetch or error occurs.

How to test and automate APIs implemented in GraphQL

In our company, we are creating an application by implementing graphQL.
I want to test and automate this APIs for CI/CD.
I have tried REST-assured but since graphQL queries are different than Json,
REST-assured doesn't have proper support for graphQL queries as discussed here.
How can we send graphQL query using REST-assured?
Please suggest the best approach to test and automate graphQL APIs
And tools which can be used for testing and automation.
So I had the same issue and I was able to make it work on a very simple way.
So I've been strugling for a while trying to make this graphQL request with Restassured in order to validate the response (amazing how scarce is the info about this) and since yesterday I was able to make it work, thought sharing here might help someone else.
What was wrong? By purely copying and pasting my Graphql request (that is not json format) on the request was not working. I kept getting error "Unexpected token t in JSON at position". So I thought it was because graphql is not JSON or some validation of restassured. That said I tried to convert the request to JSON, imported library and lot of other things but none of them worked.
My grahql query request:
String reqString = "{ trade { orders { ticker } }}\n";
How did I fixed it? By using postman to format my request. Yes, I just pasted on the QUERY window of postman and then clicked on code button on the right side (fig. 1). That allowed my to see my request on a different formatt, a formatt that works on restassured (fig. 2). PS: Just remeber to configure postman, which I've pointed with red arrows.
My grahql query request FORMATTED:
String reqString = {"query":"{ trade { orders { ticker } }}\r\n","variables":{}}
Fig 1.
Fig 2.
Hope it helps you out, take care!
You can test it with apitest
{
vars: { #describe("share variables") #client("echo")
req: {
v1: 10,
}
},
test1: { #describe("test graphql")
req: {
url: "https://api.spacex.land/graphql/",
body: {
query: `\`query {
launchesPast(limit: ${vars.req.v1}) {
mission_name
launch_date_local
launch_site {
site_name_long
}
}
}\`` #eval
}
},
res: {
body: {
data: {
launchesPast: [ #partial
{
"mission_name": "", #type
"launch_date_local": "", #type
"launch_site": {
"site_name_long": "", #type
}
}
]
}
}
}
}
}
Apitest is declarative api testing tool with JSON-like DSL.
See https://github.com/sigoden/apitest

WebApi 2.1 Model validation works locally, but does not show missing fields when run on the server

We are using WebApi v2.1 and validating ModelState via a filter applied in the WebApiConfig class.
Fields specified as required are not listed on the error message when we run on the server (Win Server 2008R2), but they work perfectly when we run locally (on IISExpress).
The request is correctly rejected locally and on the server, but the server response does not show the missing fields.
For Example:
Given a Local request that lacks the required abbreviation and issuerName fields, the response shows as expected:
{
"message": "The request is invalid.",
"modelState": {
"value": [
"Required property 'abbreviation' not found in JSON. Path '', line 18, position 2.",
"Required property 'issuerName' not found in JSON. Path '', line 18, position 2."
]
}
When the same request is sent to the server, the response shows:
{
"message": "The request is invalid.",
"modelState": {
"value": [
"An error has occurred.",
"An error has occurred."
]
}
}
Our filter is the following:
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Our data model class is decorated with the DataContract attribute, and the required fields are attributed like so:
[DataMember(IsRequired=true)]
public string IssuerName
The server is more restrictive about sending errors down ot the client. Try setting the IncludeErrorDetails flag on your httpconfiguration to verify that this is the underlying issue.
In general though turning this flag on is not the best idea, and you will want to serialize the errors down differently.
For more info:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

Resources