Skip to content

Commit 53f7bd1

Browse files
authored
fix(validation): skip email validation when the types.Email is nullable and the email isn't provided (#1107)
Co-authored-by: Henri Parquet <henri.parquet@banked.com>
1 parent 90f1ce8 commit 53f7bd1

File tree

2 files changed

+173
-19
lines changed

2 files changed

+173
-19
lines changed

email.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@ import (
55
"errors"
66
)
77

8+
// ErrValidationEmail is the sentinel error returned when an email fails validation
9+
var ErrValidationEmail = errors.New("email: failed to pass regex validation")
10+
11+
// Email represents an email address.
12+
// It is a string type that must pass regex validation before being marshalled
13+
// to JSON or unmarshalled from JSON.
814
type Email string
915

1016
func (e Email) MarshalJSON() ([]byte, error) {
1117
if !emailRegex.MatchString(string(e)) {
12-
return nil, errors.New("email: failed to pass regex validation")
18+
return nil, ErrValidationEmail
1319
}
20+
1421
return json.Marshal(string(e))
1522
}
1623

@@ -19,9 +26,11 @@ func (e *Email) UnmarshalJSON(data []byte) error {
1926
if err := json.Unmarshal(data, &s); err != nil {
2027
return err
2128
}
22-
if !emailRegex.MatchString(s) {
23-
return errors.New("email: failed to pass regex validation")
24-
}
29+
2530
*e = Email(s)
31+
if e != nil && !emailRegex.MatchString(s) {
32+
return ErrValidationEmail
33+
}
34+
2635
return nil
2736
}

email_test.go

Lines changed: 160 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,170 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10-
func TestEmail_MarshalJSON(t *testing.T) {
11-
testEmail := "gaben@valvesoftware.com"
12-
b := struct {
10+
func TestEmail_MarshalJSON_Validation(t *testing.T) {
11+
type requiredEmail struct {
1312
EmailField Email `json:"email"`
13+
}
14+
15+
testCases := map[string]struct {
16+
email Email
17+
expectedJSON []byte
18+
expectedError error
1419
}{
15-
EmailField: Email(testEmail),
20+
"it should succeed marshalling a valid email and return valid JSON populated with the email": {
21+
email: Email("validemail@openapicodegen.com"),
22+
expectedJSON: []byte(`{"email":"validemail@openapicodegen.com"}`),
23+
expectedError: nil,
24+
},
25+
"it should fail marshalling an invalid email and return a validation error": {
26+
email: Email("invalidemail"),
27+
expectedJSON: nil,
28+
expectedError: ErrValidationEmail,
29+
},
30+
"it should fail marshalling an empty email and return a validation error": {
31+
email: Email(""),
32+
expectedJSON: nil,
33+
expectedError: ErrValidationEmail,
34+
},
35+
}
36+
37+
for name, tc := range testCases {
38+
tc := tc
39+
t.Run(name, func(t *testing.T) {
40+
t.Parallel()
41+
42+
jsonBytes, err := json.Marshal(requiredEmail{EmailField: tc.email})
43+
44+
if tc.expectedError != nil {
45+
assert.ErrorIs(t, err, tc.expectedError)
46+
} else {
47+
assert.JSONEq(t, string(tc.expectedJSON), string(jsonBytes))
48+
}
49+
})
1650
}
17-
jsonBytes, err := json.Marshal(b)
18-
assert.NoError(t, err)
19-
assert.JSONEq(t, `{"email":"gaben@valvesoftware.com"}`, string(jsonBytes))
2051
}
2152

22-
func TestEmail_UnmarshalJSON(t *testing.T) {
23-
testEmail := Email("gaben@valvesoftware.com")
24-
jsonStr := `{"email":"gaben@valvesoftware.com"}`
25-
b := struct {
53+
func TestEmail_UnmarshalJSON_RequiredEmail_Validation(t *testing.T) {
54+
type requiredEmail struct {
2655
EmailField Email `json:"email"`
27-
}{}
28-
err := json.Unmarshal([]byte(jsonStr), &b)
29-
assert.NoError(t, err)
30-
assert.Equal(t, testEmail, b.EmailField)
56+
}
57+
58+
requiredEmailTestCases := map[string]struct {
59+
jsonStr string
60+
expectedEmail Email
61+
expectedError error
62+
}{
63+
"it should succeed validating a valid email during the unmarshal process": {
64+
jsonStr: `{"email":"gaben@valvesoftware.com"}`,
65+
expectedError: nil,
66+
expectedEmail: func() Email {
67+
e := Email("gaben@valvesoftware.com")
68+
return e
69+
}(),
70+
},
71+
"it should fail validating an invalid email": {
72+
jsonStr: `{"email":"not-an-email"}`,
73+
expectedError: ErrValidationEmail,
74+
expectedEmail: func() Email {
75+
e := Email("not-an-email")
76+
return e
77+
}(),
78+
},
79+
"it should fail validating an empty email": {
80+
jsonStr: `{"email":""}`,
81+
expectedEmail: func() Email {
82+
e := Email("")
83+
return e
84+
}(),
85+
expectedError: ErrValidationEmail,
86+
},
87+
"it should fail validating a null email": {
88+
jsonStr: `{"email":null}`,
89+
expectedEmail: func() Email {
90+
e := Email("")
91+
return e
92+
}(),
93+
expectedError: ErrValidationEmail,
94+
},
95+
}
96+
97+
for name, tc := range requiredEmailTestCases {
98+
tc := tc
99+
t.Run(name, func(t *testing.T) {
100+
t.Parallel()
101+
102+
b := requiredEmail{}
103+
err := json.Unmarshal([]byte(tc.jsonStr), &b)
104+
assert.Equal(t, tc.expectedEmail, b.EmailField)
105+
assert.ErrorIs(t, err, tc.expectedError)
106+
})
107+
}
108+
109+
}
110+
111+
func TestEmail_UnmarshalJSON_NullableEmail_Validation(t *testing.T) {
112+
113+
type nullableEmail struct {
114+
EmailField *Email `json:"email,omitempty"`
115+
}
116+
117+
nullableEmailTestCases := map[string]struct {
118+
body nullableEmail
119+
jsonStr string
120+
expectedEmail *Email
121+
expectedError error
122+
}{
123+
"it should succeed validating a valid email during the unmarshal process": {
124+
body: nullableEmail{},
125+
jsonStr: `{"email":"gaben@valvesoftware.com"}`,
126+
expectedError: nil,
127+
expectedEmail: func() *Email {
128+
e := Email("gaben@valvesoftware.com")
129+
return &e
130+
}(),
131+
},
132+
"it should fail validating an invalid email": {
133+
body: nullableEmail{},
134+
jsonStr: `{"email":"not-an-email"}`,
135+
expectedError: ErrValidationEmail,
136+
expectedEmail: func() *Email {
137+
e := Email("not-an-email")
138+
return &e
139+
}(),
140+
},
141+
"it should fail validating an empty email": {
142+
body: nullableEmail{},
143+
jsonStr: `{"email":""}`,
144+
expectedError: ErrValidationEmail,
145+
expectedEmail: func() *Email {
146+
e := Email("")
147+
return &e
148+
}(),
149+
},
150+
"it should succeed validating a null email": {
151+
body: nullableEmail{},
152+
jsonStr: `{"email":null}`,
153+
expectedEmail: nil,
154+
expectedError: nil,
155+
},
156+
"it should succeed validating a missing email": {
157+
body: nullableEmail{},
158+
jsonStr: `{}`,
159+
expectedEmail: nil,
160+
expectedError: nil,
161+
},
162+
}
163+
164+
for name, tc := range nullableEmailTestCases {
165+
tc := tc
166+
t.Run(name, func(t *testing.T) {
167+
t.Parallel()
168+
169+
err := json.Unmarshal([]byte(tc.jsonStr), &tc.body)
170+
assert.Equal(t, tc.expectedEmail, tc.body.EmailField)
171+
if tc.expectedError != nil {
172+
assert.ErrorIs(t, err, tc.expectedError)
173+
}
174+
})
175+
}
31176
}

0 commit comments

Comments
 (0)