micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.01k stars 1.05k forks source link

Improve traceability of the JSON Property Binder in Micronaut 4.0 #9577

Open saw303 opened 1 year ago

saw303 commented 1 year ago

Expected Behavior

Given the following YAML

onstructive:
  product-engine:
    insurance-needs:
      insurance-needs: # <-- this collection causes troubles in Micronaut 4
        - key: insuranceNeeds.best.price
          id: bc14def2-07f9-4790-a5ba-4cea80d51d35
          products:
            - name: PRD_GV_BTC
            - name: PRD_ZV_SP3
            - name: PRD_KA_UTI
            - name: PRD_KA_KTI
              constraint:
                type: AGE
                range: "0..14"

    product-configuration:
      default-base-insurance-product: PRD_GV_BTC # Telmed
      third-kid-discount-code-active: COD_RK_BER
      third-kid-discount-code-inactive: COD_RK_NBE
      family-discount-code-active: COD_RK_MRA
      family-discount-code-inactive: COD_RK_ORA
      products: # <-- this collection causes troubles in Micronaut 4
        - name: PRD_KA_KTI # KTI
          insurance-begin-strategy: FIRST_DAY_OF_THE_FOLLOWING_YEAR_OR_NEXT_MONTH
          constraint:
            type: AGE
            range: "0..54"
          legal-basis: VVG
          criteria:
            - name: AGE_GROUP
              exposable: false
              constraint:
                type: AGE
                range: "0..0"
              value: COD_LA_B01

The immutable configuration should be bound correctly onto the ConfigurationProperties interface type.

Actual Behaviour

For some reason, I do not understand yet, Micronaut has issues with binding collections onto the ProductConfiguration.

@ConfigurationProperties(ProductConfiguration.PREFIX)
public interface ProductConfiguration {

  String PREFIX = ProductEngineConfiguration.PREFIX + ".product-configuration";

  @NotBlank
  String getDefaultBaseInsuranceProduct();

  @NotBlank
  String getThirdKidDiscountCodeActive();

  @NotBlank
  String getThirdKidDiscountCodeInactive();

  @NotBlank
  String getFamilyDiscountCodeActive();

  @NotBlank
  String getFamilyDiscountCodeInactive();

  Set<Product> getProducts();

  @Introspected
  record Product(
      String name,
      InsuranceBeginStrategy insuranceBeginStrategy,
      LegalBasis legalBasis,
      List<Criterion> criteria,
      Constraint constraint) {}

  // further code omitted... (see Git repo)

Steps To Reproduce

  1. Pull this repository
  2. Checkout branch micronaut394 and run ./gradlew check. All tests will pass.
  3. Checkout branch micronaut400 and rerun the same Gradle task. Some tests will fail.

Environment Information

Example Application

https://github.com/saw303/m4configuration

Version

4.0.0

saw303 commented 1 year ago

Turns out that there was an serialisation introspection missing on type Constraint.

https://github.com/saw303/m4configuration/compare/micronaut400...micronaut400_fixed

After debugging down the rabbit hole, I found the issue hint in JsonBeanPropertyBinder

So, I guess that's not an actual Micronaut 4.x bug, but as a sort of improvement it would be nice to have a trace log that could be activated to find serialisation issues easier.

@Override
    public BindingResult<Object> bind(ArgumentConversionContext<Object> context, Map<CharSequence, ? super Object> source) {
        try {
            JsonNode objectNode = buildSourceObjectNode(source.entrySet());
            Object result = jsonMapper.readValueFromTree(objectNode, context.getArgument());
            return () -> Optional.of(result);
        } catch (Exception e) {
            // add a trace log here. This could be very helpful, when debugging configuration issues.
            context.reject(e);
            return new BindingResult<Object>() {
                @Override
                public List<ConversionError> getConversionErrors() {
                    return CollectionUtils.iterableToList(context);
                }

                @Override
                public boolean isSatisfied() {
                    return false;
                }

                @Override
                public Optional<Object> getValue() {
                    return Optional.empty();
                }
            };
        }
    }
graemerocher commented 1 year ago

@n0tl3ss can you look into improving error messages for binding errors in configuration like this