Skip to content

Commit

Permalink
feat(core): add partial fix to micronaut hibernate validator and Valu…
Browse files Browse the repository at this point in the history
…eExtractor
  • Loading branch information
nKwiatkowski authored and loicmathieu committed Jan 8, 2025
1 parent 47d2b09 commit 6890062
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public class Property<T> {
private String expression;
private T value;

//TODO: Temporary to make the ValueExtractor work, but it's not supposed to say here
public T getValue(){
return this.value;
}

// used only by the deserializer and in tests
@VisibleForTesting
public Property(String expression) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.kestra.core.validations.extractors;

import io.kestra.core.models.property.Property;
import io.micronaut.context.annotation.Context;
import jakarta.validation.valueextraction.ExtractedValue;
import jakarta.validation.valueextraction.ValueExtractor;

@Context
public class PropertyValueExtractor implements ValueExtractor<Property<@ExtractedValue ?>> {

@Override
public void extractValues(Property<?> originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.kestra.core.validations.factory;

import io.kestra.core.validations.extractors.PropertyValueExtractor;
import io.micronaut.configuration.hibernate.validator.ValidatorFactoryProvider;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.annotation.Value;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.TypeHint;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.validation.Configuration;
import jakarta.validation.ConstraintValidatorFactory;
import jakarta.validation.MessageInterpolator;
import jakarta.validation.ParameterNameProvider;
import jakarta.validation.TraversableResolver;
import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;

/**
* Produce a Validator factory provider that replace {@link ValidatorFactoryProvider} from micronaut
* hibernate validator. This has to be done because of a conflict between micronaut validation and micronaut hibernate validation
* that prevent {@link jakarta.validation.valueextraction.ValueExtractor} to work
* <br>
* This provider allows to manually register the ValueExtractors. To do that, they have to be injected
* and set in the {@link CustomValidatorFactoryProvider#configureValueExtractor(Configuration)} method
*/
@Factory
@Requires(classes = HibernateValidator.class)
@TypeHint(HibernateValidator.class)
@Replaces(ValidatorFactoryProvider.class)
public class CustomValidatorFactoryProvider {

@Inject
protected Optional<MessageInterpolator> messageInterpolator = Optional.empty();

@Inject
protected Optional<TraversableResolver> traversableResolver = Optional.empty();

@Inject
protected Optional<ConstraintValidatorFactory> constraintValidatorFactory = Optional.empty();

@Inject
protected Optional<ParameterNameProvider> parameterNameProvider = Optional.empty();

@Inject
protected PropertyValueExtractor propertyValueExtractor;

@Value("${hibernate.validator.ignore-xml-configuration:true}")
protected boolean ignoreXmlConfiguration = true;

/**
* Produces a Validator factory class.
* @param environment optional param for environment
* @return validator factory
*/
@Singleton
@Requires(classes = HibernateValidator.class)
@Replaces(ValidatorFactory.class)
ValidatorFactory validatorFactory(Optional<Environment> environment) {
Configuration<?> validatorConfiguration = Validation.byDefaultProvider()
.configure();

validatorConfiguration.messageInterpolator(messageInterpolator.orElseGet(ParameterMessageInterpolator::new));
messageInterpolator.ifPresent(validatorConfiguration::messageInterpolator);
traversableResolver.ifPresent(validatorConfiguration::traversableResolver);
constraintValidatorFactory.ifPresent(validatorConfiguration::constraintValidatorFactory);
parameterNameProvider.ifPresent(validatorConfiguration::parameterNameProvider);

if (ignoreXmlConfiguration) {
validatorConfiguration.ignoreXmlConfiguration();
}
environment.ifPresent(env -> {
Optional<Properties> config = env.getProperty("hibernate.validator", Properties.class);
config.ifPresent(properties -> {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
Object value = entry.getValue();
if (value != null) {
validatorConfiguration.addProperty(
"hibernate.validator." + entry.getKey(),
value.toString()
);
}
}
});
});

configureValueExtractor(validatorConfiguration);

return validatorConfiguration.buildValidatorFactory();
}

/**
* The custom ValueExtractors has to be set here
*/
protected void configureValueExtractor(Configuration<?> validatorConfiguration ){
validatorConfiguration.addValueExtractor(propertyValueExtractor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.kestra.core.validations.extractors;

import io.kestra.core.models.property.Property;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public class DynamicPropertyDto {

@NotNull
private Property<@Min(10) Integer> number;

@NotNull
private Property<String> string;

public DynamicPropertyDto(Property<@Min(value = 10, message = "must be greater than or equal to {value}") Integer> number, Property<String> string) {
this.number = number;
this.string = string;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.kestra.core.validations.extractors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.kestra.core.models.property.Property;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.micronaut.validation.validator.Validator;
import jakarta.inject.Inject;
import jakarta.validation.ConstraintViolation;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.Test;

@MicronautTest
public class PropertyValueExtractorTest {

@Inject
private Validator validator;

@Test
public void should_extract_and_validate_integer_value(){
DynamicPropertyDto dto = new DynamicPropertyDto(Property.of(20), Property.of("Test"));
Set<ConstraintViolation<DynamicPropertyDto>> violations = validator.validate(dto);
assertTrue(violations.isEmpty());

dto = new DynamicPropertyDto(Property.of(5), Property.of("Test"));
violations = validator.validate(dto);
assertThat(violations.size(), is(1));
ConstraintViolation<DynamicPropertyDto> violation = violations.stream().findFirst().get();
assertThat(violation.getMessage(), is("must be greater than or equal to 10"));
}

}

0 comments on commit 6890062

Please sign in to comment.