micronaut-projects / micronaut-validation

Validation support for Micronaut
Apache License 2.0
6 stars 6 forks source link

Micronaut Validation documentation incorrect statement about @Introspected #232

Open sergey-morenets opened 1 year ago

sergey-morenets commented 1 year ago

Expected Behavior

Validation documentation shouldn't state that @Introspected annotation is required for validation.

Actual Behaviour

Current Micronaut Validation documentation (https://micronaut-projects.github.io/micronaut-validation/snapshot/guide/) states that:

To validate data classes, e.g. POJOs (typically used in JSON interchange), the class must be annotated with @Introspected (see Micronaut Guide Introspection section) or, if the class is external, be imported by the @Introspected annotation.

However that's not true as per Micronaut 4.1.4.

So here's our POJO:

public class Product {

    @NotEmpty
    private String name;

    private int id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

And our configuration class:

@SerdeImport(Product.class)
public class BeanValidationConfiguration {
}

So there's no @Introspected here (@SerdeImport is meta-annotation but doesn't contain it) however Jakarta Validation works which was confirmed by our tests.

Steps To Reproduce

No response

Environment Information

No response

Example Application

No response

Version

4.1.4

nedelva commented 8 months ago

Suppose we generate a Micronaut application (Java 17+Maven) with the following features: validation, serialization-jackson, lombok, http-client, then add these classes:

package org.acme.model;

import jakarta.validation.constraints.NotEmpty;
import lombok.Data;

@Data
public class Product {
    @NotEmpty
    private String name;

    private int id;
}

We add also a validated controller:

package org.acme;

import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import jakarta.validation.Valid;
import org.acme.model.Product;

@Controller("/product")
public class ProductsController {
    @Post
    public String addProduct(@Valid @Body Product product) {
        return String.format("added product %s", product);
    }
}

Start the application with mn:run and issue a POST request in your favorite http client app:

POST http://localhost:8080/product
Content-Type: application/json

{
  "id": 1000, "name": null
}

I have a few remarks:

  1. Without the BeanValidationConfiguration class annotated with @SerdeImport there will be an error stating

    " No bean introspection available for type [class org.acme.model.Product]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected""

So fixing the error should be straightforward: annotate the class with @Introspected. Problem is, this time around we get another error:

"Internal Server Error: No deserializable introspection present for type: Product product. Consider adding Serdeable.Deserializable annotate to type Product product. Alternatively if you are not in control of the project's source code, you can use @SerdeImport(Product.class) to enable deserialization of this type."

When we use the annotation Serdeable.Deserializable we get the expected output:

{
  "_links": {
    "self": [
      {
        "href": "/product",
        "templated": false
      }
    ]
  },
  "_embedded": {
    "errors": [
      {
        "message": "product.name: must not be empty"
      }
    ]
  },
  "message": "Bad Request"
}
  1. The last error suggested an alternative to Serdeable.Deserializable, which brings me to the second point, using the @SerdeImport(Product.class) annotation. This is what OP tried and I can confirm that if you use it, no extra annotations are required on Product. This should come as unsurprising though, because the intented usage of @SerdeImport is to introspect external classes (from a library that you do not own @sergey-morenets).

Conclusion

The section 5 of Validation docs is indeed incorrect with respect to point 1 (it should have mentioned @Serdeable.Deserializable instead of @Introspected) and incomplete with respect to point 2 (a quick remark could be added there, that you don't need @Introspected on your own classes if you choose to mark them via @SerdeImport.)