Skip to content

Commit a127838

Browse files
authored
Add email validation (#38)
* add email validation
1 parent 6cab5f5 commit a127838

4 files changed

Lines changed: 103 additions & 0 deletions

File tree

src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package it.aboutbits.springboot.testing.validation.core;
22

33
import it.aboutbits.springboot.testing.validation.rule.BetweenRule;
4+
import it.aboutbits.springboot.testing.validation.rule.EmailRule;
45
import it.aboutbits.springboot.testing.validation.rule.FutureRule;
56
import it.aboutbits.springboot.testing.validation.rule.MaxRule;
67
import it.aboutbits.springboot.testing.validation.rule.MinRule;
@@ -35,6 +36,7 @@
3536
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<R>> implements
3637
ValidationRulesData,
3738
BetweenRule<R>,
39+
EmailRule<R>,
3840
FutureRule<R>,
3941
MaxRule<R>,
4042
MinRule<R>,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package it.aboutbits.springboot.testing.validation.rule;
2+
3+
import com.google.errorprone.annotations.CheckReturnValue;
4+
import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder;
5+
import it.aboutbits.springboot.testing.validation.core.Rule;
6+
import it.aboutbits.springboot.testing.validation.core.ValidationRulesData;
7+
import it.aboutbits.springboot.testing.validation.source.InvalidEmailValueSource;
8+
import org.jspecify.annotations.NullMarked;
9+
10+
@SuppressWarnings("unchecked")
11+
@NullMarked
12+
public interface EmailRule<V extends BaseRuleBuilder<?>> extends ValidationRulesData {
13+
/// Bean validation considers `null` and blank values to be valid e-mail addresses.
14+
/// Combine this rule with `notNull` or `notBlank` if the property must be present.
15+
@CheckReturnValue
16+
default V email(String property) {
17+
addRule(
18+
new Rule(property, InvalidEmailValueSource.class)
19+
);
20+
return (V) this;
21+
}
22+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package it.aboutbits.springboot.testing.validation.source;
2+
3+
import it.aboutbits.springboot.testing.validation.core.ValueSource;
4+
import org.jspecify.annotations.NullMarked;
5+
import org.jspecify.annotations.Nullable;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.function.Function;
10+
import java.util.stream.Stream;
11+
12+
@NullMarked
13+
public class InvalidEmailValueSource implements ValueSource {
14+
private static final Map<Class<?>, Function<Object[], Stream<?>>> TYPE_SOURCES = new HashMap<>();
15+
16+
static {
17+
TYPE_SOURCES.put(
18+
String.class,
19+
(Object[] args) -> Stream.of(
20+
"plainaddress",
21+
"@example.com",
22+
"someone@",
23+
"someone@@example.com",
24+
"some one@example.com",
25+
"someone@exam ple.com",
26+
"someone@example..com",
27+
"someone@.example.com"
28+
)
29+
);
30+
}
31+
32+
@SuppressWarnings("unused")
33+
public static void registerType(Class<?> type, Function<Object[], Stream<?>> source) {
34+
TYPE_SOURCES.put(type, source);
35+
}
36+
37+
@Override
38+
@SuppressWarnings("unchecked")
39+
public <T> Stream<@Nullable T> values(Class<T> propertyClass, Object... args) {
40+
var sourceFunction = TYPE_SOURCES.get(propertyClass);
41+
if (sourceFunction != null) {
42+
return (Stream<@Nullable T>) sourceFunction.apply(args);
43+
}
44+
45+
throw new IllegalArgumentException("Property class not supported!");
46+
}
47+
}

src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
77
import it.aboutbits.springboot.toolbox.validation.annotation.ValidPassword;
88
import jakarta.validation.Valid;
9+
import jakarta.validation.constraints.Email;
910
import jakarta.validation.constraints.Future;
1011
import jakarta.validation.constraints.Max;
1112
import jakarta.validation.constraints.Min;
@@ -188,6 +189,11 @@ public record SomeValidParameter(
188189
@ValidPassword
189190
String password,
190191

192+
// Email
193+
@Email String email,
194+
@Email @Nullable String nullableEmail,
195+
@Email @NotBlank String requiredEmail,
196+
191197
// Not validated
192198
Object notValidated
193199
) {
@@ -333,6 +339,13 @@ void testWithBeanValidation() {
333339
// ValidPassword
334340
.validPassword("password", 8, 50)
335341

342+
// Email
343+
.email("email")
344+
.email("nullableEmail")
345+
.nullable("nullableEmail")
346+
.email("requiredEmail")
347+
.notBlank("requiredEmail")
348+
336349
// Not validated
337350
.notValidated("notValidated")
338351
.isCompliant();
@@ -476,6 +489,13 @@ void invalidParameter_shouldFail() {
476489
// ValidPassword
477490
.validPassword("password", 8, 50)
478491

492+
// Email
493+
.email("email")
494+
.email("nullableEmail")
495+
.nullable("nullableEmail")
496+
.email("requiredEmail")
497+
.notBlank("requiredEmail")
498+
479499
// Not validated
480500
.notValidated("notValidated")
481501
.isCompliant());
@@ -620,6 +640,13 @@ void propertyMissingRule_shouldFail() {
620640
// ValidPassword
621641
.validPassword("password", 8, 50)
622642

643+
// Email
644+
.email("email")
645+
.email("nullableEmail")
646+
.nullable("nullableEmail")
647+
.email("requiredEmail")
648+
.notBlank("requiredEmail")
649+
623650
// Not validated
624651
.notValidated("notValidated")
625652
.isCompliant());
@@ -922,6 +949,11 @@ private static SomeValidParameter getSomeValidParameter() {
922949
// ValidPassword
923950
"password123",
924951

952+
// Email
953+
"someone@example.com",
954+
null,
955+
"required@example.com",
956+
925957
// Not validated
926958
null
927959
);

0 commit comments

Comments
 (0)