Is there a way to get the error response object when using generated webclient API to call a webservice that fails?
Asked Answered
G

0

9

I have a REST service that takes an id and two strings as json and returns the id and the two strings concatenated as json. If there errors it can return statuscodes 400, 404 and 500 with a json error response in the body.

The swagger.json file looks like this:

{
  "swagger": "2.0",
  "info": {
    "description": "---",
    "version": "---",
    "title": "---"
  },
  "basePath": "/rest",
  "tags": [
    {
      "name": "Concatenation resource"
    }
  ],
  "schemes": [
    "http",
    "https"
  ],
  "paths": {
    "/concatenation/{id}/concat": {
      "post": {
        "tags": [
          "Concatenation resource"
        ],
        "summary": "Concatenates two strings",
        "description": "",
        "operationId": "concatenate",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string"
          },
          {
            "in": "body",
            "name": "body",
            "required": false,
            "schema": {
              "$ref": "#/definitions/ConcatenationRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strings successfully concatenated",
            "schema": {
              "$ref": "#/definitions/ConcatenationResponse"
            }
          },
          "400": {
            "description": "Invalid input",
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "404": {
            "description": "Id does not exist",
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
  "definitions": {
    "ConcatenationResponse": {
      "type": "object",
      "required": [
        "id",
        "result"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "result": {
          "type": "string"
        }
      }
    },
    "ErrorResponse": {
      "type": "object",
      "properties": {
        "errorCode": {
          "type": "string"
        },
        "errorMessage": {
          "type": "string"
        }
      }
    },
    "ConcatenationRequest": {
      "type": "object",
      "required": [
        "stringOne",
        "stringTwo"
      ],
      "properties": {
        "stringOne": {
          "type": "string",
          "description": "First string"
        },
        "stringTwo": {
          "type": "string",
          "description": "Second string"
        }
      }
    }
  }
}

I then use the gradle plugin org.openapi.generator version 4.2.3 to generate client API classes for the webservice. I use the following configuration for the plugin:

openApiGenerate {
    generatorName = "java"
    library = "webclient"
    inputSpec = "$projectDir/src/main/resources/api-spec/spec.json"
    outputDir = "$buildDir/generated"
    apiPackage = "com.test.rest.api"
    invokerPackage = "com.test.rest.invoker"
    modelPackage = "com.test.rest.model"
    modelNameSuffix = "MM"
    configOptions = [
            java11 : "true",
            dateLibrary: "java11",
            interfaceOnly: "true",
            useSwaggerAnnotations: "true"
    ]

    generateModelTests = false
    generateApiTests = false
}

It then generates these classes:

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-02-20T10:49:08.851266400+01:00[Europe/Paris]")
public class ConcatenationResourceApi {
    private ApiClient apiClient;

    public ConcatenationResourceApi() {
        this(new ApiClient());
    }

    @Autowired
    public ConcatenationResourceApi(ApiClient apiClient) {
        this.apiClient = apiClient;
    }

    public ApiClient getApiClient() {
        return apiClient;
    }

    public void setApiClient(ApiClient apiClient) {
        this.apiClient = apiClient;
    }

    /**
     * Concatenates two strings
     * 
     * <p><b>200</b> - Strings successfully concatenated
     * <p><b>400</b> - Invalid input
     * <p><b>404</b> - Id does not exist
     * <p><b>500</b> - Server error
     * @param id The id parameter
     * @param body The body parameter
     * @return ConcatenationResponseMM
     * @throws RestClientException if an error occurs while attempting to invoke the API
     */
    public Mono<ConcatenationResponseMM> concatenate(String id, ConcatenationRequestMM body) throws RestClientException {
        Object postBody = body;

        // verify the required parameter 'id' is set
        if (id == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'id' when calling concatenate");
        }

        // create path and map variables
        final Map<String, Object> pathParams = new HashMap<String, Object>();
        pathParams.put("id", id);


        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders headerParams = new HttpHeaders();
        final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();

        final String[] accepts = { 
            "application/json"
        };
        final List<MediaType> accept = apiClient.selectHeaderAccept(accepts);
        final String[] contentTypes = { 
            "application/json"
        };
        final MediaType contentType = apiClient.selectHeaderContentType(contentTypes);

        String[] authNames = new String[] {  };

        ParameterizedTypeReference<ConcatenationResponseMM> returnType = new ParameterizedTypeReference<ConcatenationResponseMM>() {};
        return apiClient.invokeAPI("/concatenation/{id}/concat", HttpMethod.POST, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, accept, contentType, authNames, returnType);
    }
}
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-02-20T10:49:08.851266400+01:00[Europe/Paris]")
public class ApiClient {
    [...]

    /**
     * Invoke API by sending HTTP request with the given options.
     *
     * @param <T> the return type to use
     * @param path The sub-path of the HTTP URL
     * @param method The request method
     * @param pathParams The path parameters
     * @param queryParams The query parameters
     * @param body The request body object
     * @param headerParams The header parameters
     * @param formParams The form parameters
     * @param accept The request's Accept header
     * @param contentType The request's Content-Type header
     * @param authNames The authentications to apply
     * @param returnType The return type into which to deserialize the response
     * @return The response body in chosen type
     */
    public <T> Mono<T> invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams, MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept, MediaType contentType, String[] authNames, ParameterizedTypeReference<T> returnType) throws RestClientException {
        final WebClient.RequestBodySpec requestBuilder = prepareRequest(path, method, pathParams, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames);
        return requestBuilder.retrieve().bodyToMono(returnType);
    }

    [...]
}

I then use the generated client API like this:

ConcatenationRequestMM concatenationRequestMM = new ConcatenationRequestMM();
concatenationRequestMM.stringOne(concatenationRequest.getStringOne()).stringTwo(concatenationRequest.getStringTwo());
ConcatenationResponseMM concatenationResponseMM;
try {
    concatenationResponseMM = concatenationResourceApi
            .concatenate(id, concatenationRequestMM)
            .block();
    System.out.println("Id: " + concatenationResponseMM.getId() + ", result: " + concatenationResponseMM.getResult());
} catch (WebClientResponseException e) {
    String responseBodyAsString = e.getResponseBodyAsString();
    // How to get the response body unmarshalled into the ErrorResponseMM class? (getResponseBodyAsClass method does not exist)
    // ErrorResponseMM errorResponseMM = e.getResponseBodyAsClass(ErrorResponseMM.class);
    if (e.getRawStatusCode() == 400) {
        // Do some stuff here
    } else if (e.getRawStatusCode() == 404) {
        // Do some stuff here
    } else {
        // Do some stuff here
    }
}

How do I get the unmarshalled ErrorResponseMM object from the response when the webservice returns HTTP status 400, 404 or 500? Is there a better way to handle error responses with webclient/webflux?

[EDIT] Just unmarshalling the response string using Jackson or Gson will work, but is catching the WebClientResponseException the best practice way to handle errors like this?

Glume answered 20/2, 2020 at 10:40 Comment(5)
Did you try using jackson objectMapper |||| new objectMapper().readValue(responseBodyAsString , GsmAdditionalService.class);Endways
using a Jackson objectmapper or Gson or something like that will work, but I guess my question is also if just catching the WebClientResponseException like that is actually the best practice way to handle this.Glume
Have you find a solution for this? I'm in need of a same thing.Sandoval
Did you get any solution ?Grayish
I had the same question and found there are two open issues on the api generator because of this. Unfortunately they are not (being) resolved. Maybe you can contribute: - github.com/OpenAPITools/openapi-generator/issues/4777 - github.com/OpenAPITools/openapi-generator/pull/5609Aristaeus

© 2022 - 2024 — McMap. All rights reserved.