EasyPost / easypost-java

EasyPost Shipping API Client Library for Java
https://easypost.com/docs/api
MIT License
54 stars 37 forks source link

[Bug]: IllegalStateException when deseralizing "error" #239

Closed terrettazmaxime closed 1 year ago

terrettazmaxime commented 1 year ago

Software Version

6.3.0

Language Version

11

Operating System

Linux

What happened?

We have noticed a bug in the software that occurs every time there is a validation errors when performing a One-Call Buy Shipment with Purolator and they return multiple validation errors. The bug is related to the deserialization of the JSON errors, which is not functioning as expected. Specifically, we have identified that when your code is deserializing the error node, it expects the message to be either a string or an array, but in this case, it is an object, it cashes in your internal code, and we do not get an EasyPostException. This issue is causing us significant trouble as we are currently unable to move forward with our production process.

I have included the JSON that is causing the problem below, as an example:

{
    "error": {
        "code": "UNPROCESSABLE_ENTITY",
        "message": {
            "errors": [
                "1100156: From On Label address is an invalid Service Point.",
                "1100674: Postal Code XXX is only valid for XXX."
            ]
        },
        "errors": [
            {
                "message": "1100156: From On Label address is an invalid Service Point."
            },
            {
                "message": "1100674: Postal Code XXX is only valid for XXX."
            }
        ]
    }
}

Code of your Error class package com.easypost.model;

import java.util.List;

public final class Error {
    private String message;
    private String code;
    private List<Error> errors;
    private String suggestion;
    private String field;

    public Error() {
    }

    void setMessage(String message) {
        this.message = message;
    }

    void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return this.message;
    }

    public String getCode() {
        return this.code;
    }

    public List<Error> getErrors() {
        return this.errors;
    }

    public String getSuggestion() {
        return this.suggestion;
    }

    public String getField() {
        return this.field;
    }
}

When your deserializer, do the work, the message node is not a string, neither an array but and object and it crash.

public final class ErrorDeserializer implements JsonDeserializer<Error> {
    public ErrorDeserializer() {
    }

    public Error deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jo = json.getAsJsonObject();
        JsonElement results = jo.get("error");
        Gson gson = new Gson();
        if (results == null) {
            Error error = new Error();
            error.setMessage("API did not return error details.");
            error.setCode("NO RESPONSE CODE");
            return error;
        } else {
            JsonElement errorMessage = results.getAsJsonObject().get("message");
            if (errorMessage.isJsonArray()) { // This does not handle cases where it is an object
                JsonArray jsonArray = errorMessage.getAsJsonArray();
                ArrayList<String> messages = new ArrayList();

                for(int i = 0; i < jsonArray.size(); ++i) {
                    messages.add(jsonArray.get(i).getAsString());
                }

                JsonPrimitive value = new JsonPrimitive(String.join(", ", messages));
                results.getAsJsonObject().add("message", value);
            }

            return (Error)gson.fromJson(results, Error.class);
        }
    }
}

What was expected?

The library to return a EasyPostException and beeing able to parse Errors correctly.

Sample Code

No response

Relevant logs

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected STRING but was BEGIN_OBJECT at path $.message
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:226)
        at com.google.gson.Gson.fromJson(Gson.java:932)
        at com.google.gson.Gson.fromJson(Gson.java:1003)
        at com.google.gson.Gson.fromJson(Gson.java:975)
        at com.easypost.model.ErrorDeserializer.deserialize(ErrorDeserializer.java:53)
        at com.easypost.model.ErrorDeserializer.deserialize(ErrorDeserializer.java:16)
        at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69)
        at com.google.gson.Gson.fromJson(Gson.java:932)
        at com.google.gson.Gson.fromJson(Gson.java:897)
        at com.google.gson.Gson.fromJson(Gson.java:846)
        at com.google.gson.Gson.fromJson(Gson.java:817)
        at com.easypost.http.Requestor.handleAPIError(Requestor.java:530)
        at com.easypost.http.Requestor.httpRequest(Requestor.java:514)
        at com.easypost.http.Requestor.request(Requestor.java:438)
        at com.easypost.service.ShipmentService.create(ShipmentService.java:58)
        at com.easypost.service.ShipmentService.create(ShipmentService.java:40)
        at com.easypost.service.ShipmentService$create.call(Unknown Source)
jchen293 commented 1 year ago

Hi @terrettazmaxime

Thank you for bringing this issue to our attention. I am glad that you are using our latest Java library version.

A similar issue was previously reported by another user regarding Purolator returning an array in the error message. In response, we implemented a guard clause in our client libraries to handle such edge cases during error deserialization.

Regarding your reported issue, we have created an internal task to investigate whether this issue is occurring at the API level. If it is found to be the case, we will patch the issue at the API level, rather than making changes in all seven client libraries. We will keep you informed of any updates on this matter.

Thank you again for your valuable feedback.

terrettazmaxime commented 1 year ago

@jchen293 Do you have any EAT for a fix, this is currently breaking our elements using Purolator.

jchen293 commented 1 year ago

Hello @terrettazmaxime , I've left a comment on your PR #240. If you can address the feedback, we can proceed with merging the PR. Please let me know if you would like me to take care of the rest of the PR.

terrettazmaxime commented 1 year ago

Hello @jchen293 , I will appreciate if you can handle the PR and move this forward.

jchen293 commented 1 year ago

@terrettazmaxime I have opened PR #242 for this issue and it's waiting for reviews, I will let you know when we make a release. Thanks for reporting this issue again!

jchen293 commented 1 year ago

@terrettazmaxime I have merged PR #242 to handle these edge cases when deserializing error JSON, we are planning to release it tomorrow. Stay tuned and thanks for reporting this issue again!

jchen293 commented 1 year ago

@terrettazmaxime Exciting news, we have just released v6.3.1 for the patch of this issue😄