2121import jakarta .validation .Validator ;
2222import jakarta .validation .constraints .Min ;
2323import org .hibernate .validator .HibernateValidator ;
24- import org .junit .jupiter .api .Assertions ;
2524import org .junit .jupiter .api .Test ;
2625
2726import org .springframework .boot .test .context .FilteredClassLoader ;
3029import org .springframework .context .annotation .Bean ;
3130import org .springframework .context .annotation .Configuration ;
3231import org .springframework .core .io .ClassPathResource ;
32+ import org .springframework .validation .Errors ;
3333import org .springframework .validation .MapBindingResult ;
34+ import org .springframework .validation .SmartValidator ;
3435import org .springframework .validation .beanvalidation .LocalValidatorFactoryBean ;
3536
3637import static org .assertj .core .api .Assertions .assertThat ;
38+ import static org .assertj .core .api .Assertions .assertThatRuntimeException ;
3739import static org .mockito .ArgumentMatchers .any ;
3840import static org .mockito .BDDMockito .then ;
3941import static org .mockito .Mockito .mock ;
@@ -95,11 +97,26 @@ void wrapperWhenValidationProviderNotPresentShouldNotThrowException() {
9597 }
9698
9799 @ Test
98- void unwrapValidatorInstanceOfJakartaTypeAndExceptionThrownWhenTypeNotSupported () {
100+ void unwrapToJakartaValidatorShouldReturnJakartaValidator () {
99101 this .contextRunner .withUserConfiguration (LocalValidatorFactoryBeanConfig .class ).run ((context ) -> {
100102 ValidatorAdapter wrapper = context .getBean (ValidatorAdapter .class );
101103 assertThat (wrapper .unwrap (Validator .class )).isInstanceOf (Validator .class );
102- Assertions .assertThrows (IllegalArgumentException .class , () -> wrapper .unwrap (HibernateValidator .class ));
104+ });
105+ }
106+
107+ @ Test
108+ void whenJakartaValidatorIsWrappedMultipleTimesUnwrapToJakartaValidatorShouldReturnJakartaValidator () {
109+ this .contextRunner .withUserConfiguration (DoubleWrappedConfig .class ).run ((context ) -> {
110+ ValidatorAdapter wrapper = context .getBean (ValidatorAdapter .class );
111+ assertThat (wrapper .unwrap (Validator .class )).isInstanceOf (Validator .class );
112+ });
113+ }
114+
115+ @ Test
116+ void unwrapToUnsupportedTypeShouldThrow () {
117+ this .contextRunner .withUserConfiguration (LocalValidatorFactoryBeanConfig .class ).run ((context ) -> {
118+ ValidatorAdapter wrapper = context .getBean (ValidatorAdapter .class );
119+ assertThatRuntimeException ().isThrownBy (() -> wrapper .unwrap (HibernateValidator .class ));
103120 });
104121 }
105122
@@ -118,6 +135,55 @@ ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) {
118135
119136 }
120137
138+ @ Configuration (proxyBeanMethods = false )
139+ static class DoubleWrappedConfig {
140+
141+ @ Bean
142+ LocalValidatorFactoryBean validator () {
143+ return new LocalValidatorFactoryBean ();
144+ }
145+
146+ @ Bean
147+ ValidatorAdapter wrapper (LocalValidatorFactoryBean validator ) {
148+ return new ValidatorAdapter (new Wrapper (validator ), true );
149+ }
150+
151+ static class Wrapper implements SmartValidator {
152+
153+ private final SmartValidator delegate ;
154+
155+ Wrapper (SmartValidator delegate ) {
156+ this .delegate = delegate ;
157+ }
158+
159+ @ Override
160+ public boolean supports (Class <?> clazz ) {
161+ return this .delegate .supports (clazz );
162+ }
163+
164+ @ Override
165+ public void validate (Object target , Errors errors ) {
166+ this .delegate .validate (target , errors );
167+ }
168+
169+ @ Override
170+ public void validate (Object target , Errors errors , Object ... validationHints ) {
171+ this .delegate .validate (target , errors , validationHints );
172+ }
173+
174+ @ Override
175+ @ SuppressWarnings ("unchecked" )
176+ public <T > T unwrap (Class <T > type ) {
177+ if (type .isInstance (this .delegate )) {
178+ return (T ) this .delegate ;
179+ }
180+ return this .delegate .unwrap (type );
181+ }
182+
183+ }
184+
185+ }
186+
121187 @ Configuration (proxyBeanMethods = false )
122188 static class NonManagedBeanConfig {
123189
0 commit comments