Skip to content

Getters returning NULL when model extends HashMap #1112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
tmulle opened this issue Apr 16, 2025 · 2 comments
Open

Getters returning NULL when model extends HashMap #1112

tmulle opened this issue Apr 16, 2025 · 2 comments
Labels
area:client This item is related to the client extension

Comments

@tmulle
Copy link

tmulle commented Apr 16, 2025

Tell us the extension you're using

Client

I tried this:

We are using quarkus-resteasy-problem for our REST error responses and their HttpProblem class extends RuntimeException.

When the client openapi generator creates the model for that schema it generates the code but ALL of the getters return NULL which the actual values are really stored in the parent MAP.

Here is the code generated for the HttpProblem.

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
  * HTTP Problem Response according to RFC9457 & RFC7807
 **/
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class HttpProblem extends HashMap<String, Object> {

    /**
      * A optional URI reference that identifies the problem type
     **/
    private URI type;
    /**
      * A optional, short, human-readable summary of the problem type
     **/
    private String title;
    /**
      * The HTTP status code for this occurrence of the problem
     **/
    private Integer status;
    /**
      * A optional human-readable explanation specific to this occurrence of the problem
     **/
    private String detail;
    /**
      * A URI reference that identifies the specific occurrence of the problem
     **/
    private URI instance;

    /**
    * A optional URI reference that identifies the problem type
    * @return type
    **/
    @JsonProperty("type")
    @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
    public URI getType() {
        return type;
    }

    /**
     * Set type
     **/
    public void setType(URI type) {
        this.type = type;
    }

    public HttpProblem type(URI type) {
        this.type = type;
        return this;
    }

    /**
    * A optional, short, human-readable summary of the problem type
    * @return title
    **/
    @JsonProperty("title")
    @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
    public String getTitle() {
        return title;
    }

    /**
     * Set title
     **/
    public void setTitle(String title) {
        this.title = title;
    }

    public HttpProblem title(String title) {
        this.title = title;
        return this;
    }

    /**
    * The HTTP status code for this occurrence of the problem
    * @return status
    **/
    @JsonProperty("status")
    @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
    public Integer getStatus() {
        return status;
    }

    /**
     * Set status
     **/
    public void setStatus(Integer status) {
        this.status = status;
    }

    public HttpProblem status(Integer status) {
        this.status = status;
        return this;
    }

    /**
    * A optional human-readable explanation specific to this occurrence of the problem
    * @return detail
    **/
    @JsonProperty("detail")
    @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
    public String getDetail() {
        return detail;
    }

    /**
     * Set detail
     **/
    public void setDetail(String detail) {
        this.detail = detail;
    }

    public HttpProblem detail(String detail) {
        this.detail = detail;
        return this;
    }

    /**
    * A URI reference that identifies the specific occurrence of the problem
    * @return instance
    **/
    @JsonProperty("instance")
    @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
    public URI getInstance() {
        return instance;
    }

    /**
     * Set instance
     **/
    public void setInstance(URI instance) {
        this.instance = instance;
    }

    public HttpProblem instance(URI instance) {
        this.instance = instance;
        return this;
    }

    /**
     * Create a string representation of this pojo.
     **/
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("class HttpProblem {\n");
        sb.append("    ").append(toIndentedString(super.toString())).append("\n");
        sb.append("    type: ").append(toIndentedString(type)).append("\n");
        sb.append("    title: ").append(toIndentedString(title)).append("\n");
        sb.append("    status: ").append(toIndentedString(status)).append("\n");
        sb.append("    detail: ").append(toIndentedString(detail)).append("\n");
        sb.append("    instance: ").append(toIndentedString(instance)).append("\n");
        
        sb.append("}");
        return sb.toString();
    }

    /**
     * Compares this object to the specified object. The result is
     * {@code true} if and only if the argument is not
     * {@code null} and is a {@code HttpProblem} object that
     * contains the same values as this object.
     *
     * @param   obj   the object to compare with.
     * @return  {@code true} if the objects are the same;
     *          {@code false} otherwise.
     **/
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        HttpProblem model = (HttpProblem) obj;

        return java.util.Objects.equals(type, model.type) &&
        java.util.Objects.equals(title, model.title) &&
        java.util.Objects.equals(status, model.status) &&
        java.util.Objects.equals(detail, model.detail) &&
        java.util.Objects.equals(instance, model.instance);
    }

    /**
     * Returns a hash code for a {@code HttpProblem}.
     *
     * @return a hash code value for a {@code HttpProblem}.
     **/
    @Override
    public int hashCode() {
        return java.util.Objects.hash(type,
        title,
        status,
        detail,
        instance);
    }

    /**
     * Convert the given object to string with each line indented by 4 spaces
     * (except the first line).
     */
    private static String toIndentedString(Object o) {
        if (o == null) {
            return "null";
        }
        return o.toString().replace("\n", "\n    ");
    }

    /**
      * HTTP Problem Response according to RFC9457 & RFC7807
     **/
    @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
    public static class HttpProblemQueryParam extends HashMap<String, Object> {

        /**
          * HTTP Problem Response according to RFC9457 & RFC7807
         **/
        @jakarta.ws.rs.QueryParam("type")
        private URI type;
        /**
          * HTTP Problem Response according to RFC9457 & RFC7807
         **/
        @jakarta.ws.rs.QueryParam("title")
        private String title;
        /**
          * HTTP Problem Response according to RFC9457 & RFC7807
         **/
        @jakarta.ws.rs.QueryParam("status")
        private Integer status;
        /**
          * HTTP Problem Response according to RFC9457 & RFC7807
         **/
        @jakarta.ws.rs.QueryParam("detail")
        private String detail;
        /**
          * HTTP Problem Response according to RFC9457 & RFC7807
         **/
        @jakarta.ws.rs.QueryParam("instance")
        private URI instance;

        /**
        * A optional URI reference that identifies the problem type
        * @return type
        **/
        @com.fasterxml.jackson.annotation.JsonProperty("type")
        public URI getType() {
            return type;
        }

        /**
         * Set type
         **/
        public void setType(URI type) {
            this.type = type;
        }

        public HttpProblemQueryParam type(URI type) {
            this.type = type;
            return this;
        }

        /**
        * A optional, short, human-readable summary of the problem type
        * @return title
        **/
        @com.fasterxml.jackson.annotation.JsonProperty("title")
        public String getTitle() {
            return title;
        }

        /**
         * Set title
         **/
        public void setTitle(String title) {
            this.title = title;
        }

        public HttpProblemQueryParam title(String title) {
            this.title = title;
            return this;
        }

        /**
        * The HTTP status code for this occurrence of the problem
        * @return status
        **/
        @com.fasterxml.jackson.annotation.JsonProperty("status")
        public Integer getStatus() {
            return status;
        }

        /**
         * Set status
         **/
        public void setStatus(Integer status) {
            this.status = status;
        }

        public HttpProblemQueryParam status(Integer status) {
            this.status = status;
            return this;
        }

        /**
        * A optional human-readable explanation specific to this occurrence of the problem
        * @return detail
        **/
        @com.fasterxml.jackson.annotation.JsonProperty("detail")
        public String getDetail() {
            return detail;
        }

        /**
         * Set detail
         **/
        public void setDetail(String detail) {
            this.detail = detail;
        }

        public HttpProblemQueryParam detail(String detail) {
            this.detail = detail;
            return this;
        }

        /**
        * A URI reference that identifies the specific occurrence of the problem
        * @return instance
        **/
        @com.fasterxml.jackson.annotation.JsonProperty("instance")
        public URI getInstance() {
            return instance;
        }

        /**
         * Set instance
         **/
        public void setInstance(URI instance) {
            this.instance = instance;
        }

        public HttpProblemQueryParam instance(URI instance) {
            this.instance = instance;
            return this;
        }

        /**
         * Create a string representation of this pojo.
         **/
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("class HttpProblemQueryParam {\n");
            sb.append("    ").append(toIndentedString(super.toString())).append("\n");
            sb.append("    type: ").append(toIndentedString(type)).append("\n");
            sb.append("    title: ").append(toIndentedString(title)).append("\n");
            sb.append("    status: ").append(toIndentedString(status)).append("\n");
            sb.append("    detail: ").append(toIndentedString(detail)).append("\n");
            sb.append("    instance: ").append(toIndentedString(instance)).append("\n");
            sb.append("}");
            return sb.toString();
        }

        /**
         * Convert the given object to string with each line indented by 4 spaces
         * (except the first line).
         */
        private static String toIndentedString(Object o) {
            if (o == null) {
                return "null";
            }
            return o.toString().replace("\n", "\n    ");
        }
    }}

When I print out the model returned from the Rest call I can see the values from the parent but not in the child class.

class HttpProblem {
    {instance=/api/v1/bcp/notifications/client/436c5f5f-414c-4b12-ab10-47219da0a8f8, X-Request-Id=6ac32b58-47a2-45ee-9691-16841b216fd5, detail=Client 436c5f5f-414c-4b12-ab10-47219da0a8f8 does not exist, title=Not Found, status=404}
    type: null
    title: null
    status: null
    detail: null
    instance: null
}```

Here is the SCHEMA snippet for the HTTPProblem that gets auto generated from Quarkus OpenAPI extension:

```json
"HttpProblem" : {
        "description" : "HTTP Problem Response according to RFC9457 & RFC7807",
        "additionalProperties" : true,
        "type" : "object",
        "properties" : {
          "type" : {
            "type" : "string",
            "format" : "uri",
            "description" : "A optional URI reference that identifies the problem type",
            "examples" : [ "https://example.com/errors/not-found" ]
          },
          "title" : {
            "type" : "string",
            "description" : "A optional, short, human-readable summary of the problem type",
            "examples" : [ "Not Found" ]
          },
          "status" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "The HTTP status code for this occurrence of the problem",
            "examples" : [ 404 ]
          },
          "detail" : {
            "type" : "string",
            "description" : "A optional human-readable explanation specific to this occurrence of the problem",
            "examples" : [ "Record not found" ]
          },
          "instance" : {
            "type" : "string",
            "format" : "uri",
            "description" : "A URI reference that identifies the specific occurrence of the problem",
            "examples" : [ "https://api.example.com/errors/123" ]
          }
        }
      },
      ...

This happened:

Calling any of the getters generated always returns NULL

I expected this:

I should get the values returned when I call the getters like getStatus() and getDetails(), etc. and not NULL

Is there a workaround?

No response

How can we try to reproduce the issue?

No response

Anything else?

No response

Output of uname -a or ver

No response

Output of java -version

No response

Quarkus OpenApi version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

Community Notes

  • Please vote by adding a 👍 reaction to the issue to help us prioritize.
  • If you are interested to work on this issue, please leave a comment.name: Bug Report 🐞
@ricardozanini ricardozanini added the area:client This item is related to the client extension label Apr 16, 2025
@ricardozanini
Copy link
Member

Thanks for raising this issue. I believe we need first to investigate how to have a better integration with quarkus-resteasy-problem instead, since now they support RestClients: https://github.com/quarkiverse/quarkus-resteasy-problem?tab=readme-ov-file#restclients-available-since-v3200.

I'd assess this and try a few examples before jumping into your issue. Unfortunately, I don't have the time now, but feel free to send a PR or start investigating it since it seems you're a heavy user of the extension.

@tmulle
Copy link
Author

tmulle commented Apr 16, 2025

Right, there are two things to their client support now:

If I use their ThrowingHttpProblemClientExceptionMapper by setting the property during client generation, then I can use their raw HttpProblem exception class BUT then the models for HttpProblem Violation and HttpValidatingProblem still get generated and cause confusion because of the same class name but different packages.

This is why I asked for #1111 so those models would not get created since I know ahead of time I do not want those generated.

application.properties
quarkus.openapi-generator.codegen.spec.openapi_json.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterProvider(value=io.quarkiverse.resteasy.problem.client.ThrowingHttpProblemClientExceptionMapper.class)

Right now, I will live with the extra classes being generated as I also need to move forward with these classes and my product for production.

I will look at how to make a PR for #1111 sometime soon unless someone beats me to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:client This item is related to the client extension
Projects
None yet
Development

No branches or pull requests

2 participants