Skip to content
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

After updating the Micronaut version to 4.7.0, the project cannot be compiled due to a syntax error in the generated Java file #1869

Closed
alapierre opened this issue Nov 15, 2024 · 35 comments · Fixed by #1871

Comments

@alapierre
Copy link

alapierre commented Nov 15, 2024

for inherited class, equals and hashCode methods are generated wrong.

in micronaut 4.6.2:

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        return super.equals(o);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode());
    }

in micronaut 4.7.0:

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        SalesInvoiceCreateDto salesInvoiceCreateDto = (SalesInvoiceCreateDto) o;
        return  &&
            super.equals(o);
    }

    @Override
    public int hashCode() {
        return Objects.hash(, super.hashCode());
    }

DTO in openAPI

    SalesInvoiceCreateDto:
      allOf:
        - $ref: '#/components/schemas/BaseInvoiceDto'

Expected Behavior

needs works like before

Steps To Reproduce

just try to compile sample project from 4.7.0 branch https://github.com/alapierre/micronaut-sample/tree/4.7.0.

Environment Information

No response

Example Application

https://github.com/alapierre/micronaut-sample

Version

4.7.0

@alapierre alapierre changed the title After update micronaut version to 4.7.0 project can't by compiled because of generated java file syntax error After updating the Micronaut version to 4.7.0, the project cannot be compiled due to a syntax error in the generated Java file. Nov 15, 2024
@alapierre alapierre changed the title After updating the Micronaut version to 4.7.0, the project cannot be compiled due to a syntax error in the generated Java file. After updating the Micronaut version to 4.7.0, the project cannot be compiled due to a syntax error in the generated Java file Nov 15, 2024
altro3 added a commit to altro3/micronaut-openapi that referenced this issue Nov 15, 2024
altro3 added a commit to altro3/micronaut-openapi that referenced this issue Nov 15, 2024
altro3 added a commit to altro3/micronaut-openapi that referenced this issue Nov 16, 2024
@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

I see that this is the same example as in this ticket: #1794 (comment)

Please try to build micronaut-openapi from this branch: #1871 (it already has the fixed equals generation)

About oneOf: now it clearly generates a different code, not the one you described in the last comment. Plus, I suggest doing the following:

  1. Try to generate the code with the useOneOfInterfaces setting with the value true / false .

  2. Generate the code via spring generator with the useOneOfInterfaces setting with the value true / false .

  3. Then compare what happened in both cases. If you see that the generator for spring generates a more correct model - create a ticket, I will fix it. But so far I do not understand how to generate a model better in your case.

Let's continue the discussion here, and not in a closed ticket (#1794).

altro3 added a commit to altro3/micronaut-openapi that referenced this issue Nov 16, 2024
@alapierre
Copy link
Author

ok, thank you. I will do like you said and let you know.

@alapierre
Copy link
Author

alapierre commented Nov 16, 2024

I'm not sure how to force 6.13.2-SNAPSHOT in my project (I have it already build in my local envirnoment). I tried add this:

 <dependency>
            <groupId>io.micronaut.openapi</groupId>
            <artifactId>micronaut-openapi-generator</artifactId>
            <version>6.13.2-SNAPSHOT</version>
        </dependency>

and:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.micronaut.openapi</groupId>
                <artifactId>micronaut-openapi</artifactId>
                <version>6.13.2-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>io.micronaut.openapi</groupId>
                <artifactId>micronaut-openapi-annotations</artifactId>
                <version>6.13.2-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>io.micronaut.openapi</groupId>
                <artifactId>micronaut-openapi-common</artifactId>
                <version>6.13.2-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

and setting:

<micronaut.openapi.version>6.13.2-SNAPSHOT</micronaut.openapi.version>

but SalesInvoiceCreateDto code generates in the same way with equals and hashCode syntax problem.

I do not know how to change useOneOfInterfaces - there is no micronaut property like this

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

Did you get source code from this branch: #1871 ???

Fix not merged yet, as you see.

@alapierre
Copy link
Author

alapierre commented Nov 16, 2024

Yes, I cloned your repository, built it locally, and installed it using (publishToMavenLocal)

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

Try to change version to 6.13.2-1 and publish it maven local repo again. Not sure that SNAPSHOT works correctly

@alapierre
Copy link
Author

it not helps, added <micronaut.openapi.version>6.13.2-1</micronaut.openapi.version> and:

        <dependency>
            <groupId>io.micronaut.openapi</groupId>
            <artifactId>micronaut-openapi</artifactId>
            <version>${micronaut.openapi.version}</version>
        </dependency>

        <dependency>
            <groupId>io.micronaut.openapi</groupId>
            <artifactId>micronaut-openapi-generator</artifactId>
            <version>${micronaut.openapi.version}</version>
        </dependency>
mvn dependency:tree | grep micronaut-openapi
[INFO] +- io.micronaut.openapi:micronaut-openapi:jar:6.13.2-1:compile
[INFO] |  +- io.micronaut.openapi:micronaut-openapi-annotations:jar:6.13.2-1:compile
[INFO] |  +- io.micronaut.openapi:micronaut-openapi-common:jar:6.13.2-1:compile
[INFO] +- io.micronaut.openapi:micronaut-openapi-generator:jar:6.13.2-1:compile

you can check it on: https://github.com/alapierre/micronaut-sample/tree/4.7.0

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

okay, let me check

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

I don't see any information about local repo in you project. How did you tell maven that need to check local repo too?

@alapierre
Copy link
Author

it is maven default behavior - use local jars if exists. There is no special config required - especially since there is no such version as 6.13.2-1 in the remote repository

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

ok, understand. Need to check it with gradle plugin... I have never used the maven plugin, and I am not sure that it can correctly replace the openapi version

@alapierre
Copy link
Author

alapierre commented Nov 16, 2024

the same like I have never used gradle...

mvn dependency:build-classpath shows paths for all jars

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

@alapierre alapierre/micronaut-sample#2

I migrated your project to gradle. As I said, the problem is in the maven plugin - it clearly uses the wrong version.

So, there are 2 plugins connected there now - from micronaut and the official one from openapi generator. All you need to do is run the tasks 4 times:

  1. Run 2 tasks: generateServerOpenApiApis generateServerOpenApiModels - this is the option for micronaut + useOneOfInterfaces = true

  2. Add the useOneOfInterfaces = false setting to the micronaut plugin and run 2 tasks: generateServerOpenApiApis generateServerOpenApiModels - this is the option for micronaut + useOneOfInterfaces = false

  3. Run the generateApi task - this is the option for spring + useOneOfInterfaces = true

  4. In the generateApi task, replace the useOneOfInterfaces = false setting Run the generateApi task - this is the option for spring + useOneOfInterfaces = false

@altro3
Copy link
Collaborator

altro3 commented Nov 16, 2024

@alapierre Did you manage to get it running?

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

Adding a dependency with 6.13.2-1 version on the Micronaut Maven plugin helped:

<plugin>
                <groupId>io.micronaut.maven</groupId>
                <artifactId>micronaut-maven-plugin</artifactId>
                <configuration>
                    <configFile>aot-${packaging}.properties</configFile>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>io.micronaut.openapi</groupId>
                        <artifactId>micronaut-openapi-generator</artifactId>
                        <version>${micronaut.openapi.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

now, code for equals and hashCode is generated correctly.

One potentially compatibility issue:

for endpoint:

  /api/invoice:
    post:
      tags:
        - Invoice
      operationId: createInvoice
      summary: Creates new sale invoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/SalesInvoiceCreateDto'
                - $ref: '#/components/schemas/CurrencyInvoiceCreateDto'

before, code was generated in that way:

HttpResponse<@Valid InvoiceIdDto> createInvoice(CreateInvoiceRequest createInvoiceRequest)

but currently is:

InvoiceIdDto createInvoice(CreateInvoiceRequest createInvoiceRequest).

I still don’t know how to set in Micronaut the parameter useOneOfInterfaces. Could you guide me?

Now error is different than before:

Internal Server Error: No bean introspection available for type [interface dev.itrust.sample.fk.api.model.CreateInvoiceRequest]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected

manually adding @Serdeable into CreateInvoiceRequest causes the error to reappear:

Failed to convert argument [createInvoiceRequest] for value [null] due to: Unable to deserialize type [CreateInvoiceRequest createInvoiceRequest]: No default constructor exists

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

For maven you can set it by this property:

<micronaut.openapi.use.one.of.interfaces>false</micronaut.openapi.use.one.of.interfaces>

About problem with HttpResponse: yes, I saw it - this is just a bug. Default value were true, but after last release it was changed to false. I'll fix it next maven / gradle plugin releases. But it is not critical bug, just set this property manual:

<micronaut.openapi.generate.http.response.where.required>true</micronaut.openapi.generate.http.response.where.required>

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

About your error: I told you, why it's happens. But I don't know how else to interpret your anonymous schema. I think you'll get exactly the same behavior with the spring generator. But that's why I asked you to check 4 different options and compare. If the spring generator creates a more correct model, then tell me about it, then I'll correct the generation.

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

yes I understand - but: spring generator currenty has bug and cannot generate correct code with polimorfism, so I cannot check that

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

setting useOneOfInterfaces cause that LoadInvoice200Response and CreateInvoiceRequest are not in BaseInvoiceDto hierarchy - so now I lost polimorfizm at all.

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

sorry, don't understand your last comment

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

now LoadInvoice200Response and CreateInvoiceRequest are classes like expected, but they not extends anything.

public class CreateInvoiceRequest {

    public static final String JSON_PROPERTY_DOC_TYPE = "docType";
    public static final String JSON_PROPERTY_SELLER_VAT_ID = "sellerVatId";
    public static final String JSON_PROPERTY_CURRENCY = "currency";
    public static final String JSON_PROPERTY_EXCHANGE_RATE = "exchangeRate";
    public static final String JSON_PROPERTY_EXCHANGE_DATE = "exchangeDate";

    @NotNull
    @JsonProperty(JSON_PROPERTY_DOC_TYPE)
    private DocType docType;

    @NotNull
    @Size(max = 10)
    @JsonProperty(JSON_PROPERTY_SELLER_VAT_ID)
    private String sellerVatId;

    /**
     * Currency of the invoice
     */
    @NotNull
    @JsonProperty(JSON_PROPERTY_CURRENCY)
    private String currency = "PLN";

    /**
     * Exchange rate for the currency
     */
    @NotNull
    @JsonProperty(JSON_PROPERTY_EXCHANGE_RATE)
    private BigDecimal exchangeRate;

    @NotNull
    @JsonProperty(JSON_PROPERTY_EXCHANGE_DATE)
    private LocalDate exchangeDate;

    public CreateInvoiceRequest(DocType docType, String sellerVatId, String currency, BigDecimal exchangeRate, LocalDate exchangeDate) {
        this.docType = docType;
        this.sellerVatId = sellerVatId;
        this.currency = currency;
        this.exchangeRate = exchangeRate;
        this.exchangeDate = exchangeDate;
    }

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

maybe let's wait until Spring fix error in they generator and than I will check if it can work with interface?

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

you understand what the matter is, there is no error in code generation at the moment. Everything is generated exactly as you wrote in the specification.

To make it clearer what the error is, write the code that you expect to see

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

yes, right. I will make some R&D and back to you later. For now I can move on with my project with this limitation. Thank you for your help.

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

For fast fix you just need to add information about discriminator to your anonymous schema:

  1. The first solution is to remove the anonymous scheme and refer to the scheme where the discriminator is described
  /api/invoice:
    post:
      tags:
        - Invoice
      operationId: createInvoice
      summary: Creates new sale invoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BaseInvoiceDto'

In the BaseInvoiceDto scheme - you have information about the discriminator, so everything will work correctly

  1. Add discriminator information to your anonymity schema like this:
  /api/invoice:
    post:
      tags:
        - Invoice
      operationId: createInvoice
      summary: Creates new sale invoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              discriminator:
                propertyName: docType
                mapping:
                  FS: "#/components/schemas/SalesInvoiceDto"
                  FW: "#/components/schemas/CurrencyInvoiceDto"
              oneOf:
                - $ref: '#/components/schemas/SalesInvoiceDto'
                - $ref: '#/components/schemas/CurrencyInvoiceDto'

In this case, everything should work correctly too.

@alapierre
Copy link
Author

alapierre commented Nov 17, 2024

I made some R&D with Jackon and, it is completely possibile to use interfaces with polimorfizm - only one expectation is to put right annotations in it.

@JsonIgnoreProperties(
        value = "docType", // ignore manually set docType, it will be automatically generated by Jackson during serialization
        allowSetters = true // allows the docType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "docType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = SalesInvoiceCreateDto.class, name = "FS"),
        @JsonSubTypes.Type(value = CurrencyInvoiceCreateDto.class, name = "FW")
})
public interface CreateInvoiceRequest {
}
@Data
public class SalesInvoiceCreateDto implements CreateInvoiceRequest {

    private String docType;

    private String seller;

    private String buyer;

}
@EqualsAndHashCode(callSuper = true)
@Data
public class CurrencyInvoiceCreateDto extends SalesInvoiceCreateDto implements CreateInvoiceRequest {

    private String currency;

}
@Test
    void toJson() throws Exception {

        val currencyInvoice = new CurrencyInvoiceCreateDto();
        currencyInvoice.setCurrency("EUR");
        currencyInvoice.setBuyer("buyer");
        currencyInvoice.setSeller("seller");

        val res = objectMapper.writeValueAsString(currencyInvoice);

        System.out.println(res);

    }

    @Test
    void fromJsonCurrency() throws Exception {

        String json = """
                {
                    "docType":"FW",
                    "seller":"seller",
                    "buyer":"buyer",
                    "currency":"EUR"
                }
                """;

        val res = objectMapper.readValue(json, CreateInvoiceRequest.class);

        System.out.println(res.getClass().getSimpleName());

        Assertions.assertInstanceOf(CurrencyInvoiceCreateDto.class,res);

    }

    @Test
    void fromJsonSales() throws Exception {

        String json = """
                {
                    "docType":"FS",
                    "seller":"seller",
                    "buyer":"buyer"
                }
                """;

        val res = objectMapper.readValue(json, CreateInvoiceRequest.class);

        System.out.println(res.getClass().getSimpleName());

        Assertions.assertInstanceOf(SalesInvoiceCreateDto.class,res);
    }

https://github.com/alapierre/jackson-polimorfizm-sample

@alapierre
Copy link
Author

thank you for sample OpenAPI - it looks like resolution.

@alapierre
Copy link
Author

now everything works like expected - only smal thing with discriminator as enum:

    DocType:
      type: string
      enum: [ FS, FW, FK, FP ]
      default: "FS"

but I think it is not Micronaut Generator issue, because in Spring is the same problem. When I change descriminator field type to string everything works fine.

@alapierre
Copy link
Author

thank you so much for your help, time and patience.

@altro3
Copy link
Collaborator

altro3 commented Nov 17, 2024

also you can use lombok in generated sources - it wil be fast fix for equals bug. See availbale options here: https://github.com/micronaut-projects/micronaut-maven-plugin/tree/4.7.x/micronaut-maven-plugin/src/main/java/io/micronaut/maven/openapi

@alapierre
Copy link
Author

using:

<micronaut.openapi.server.lombok>true</micronaut.openapi.server.lombok>

in some cases is not an option in existing project, because it cause that DTO's does not have convenient semi-builder methods, like: Invoice id(String id) - only plain setters.

@altro3
Copy link
Collaborator

altro3 commented Nov 26, 2024

You are wrong. There is a special annotation added there @Accessors(chain = true) This is done as an alternative to the builder. For example, the code will be something like this

var model = new Model()
        .setAttr1(v1)
        .setAttr2(v2)

@altro3
Copy link
Collaborator

altro3 commented Nov 27, 2024

@alapierre The only difference is the "set" prefix. Do you think it's inconvenient? I used it in my projects - my opinion - it's very convenient and allows you to continue using lombok without a builder :-)

@alapierre
Copy link
Author

the only think is - in existing project it could take some time to change every single place and add lot of set

@altro3
Copy link
Collaborator

altro3 commented Nov 27, 2024

Well, yes, I agree. I just wanted you to understand that using lombok is no worse in functionality than without it. And it removes possible bugs with toString / equals / hashCode methods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants