Skip to content

Commit eed52c6

Browse files
committed
Add 'types/' from commit '38a6e18f9010e700a60c91db6bc276b9966cdff3'
git-subtree-dir: types git-subtree-mainline: b8dc2db git-subtree-split: 38a6e18
2 parents b8dc2db + 38a6e18 commit eed52c6

File tree

9 files changed

+520
-0
lines changed

9 files changed

+520
-0
lines changed

types/date.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"time"
6+
)
7+
8+
const DateFormat = "2006-01-02"
9+
10+
type Date struct {
11+
time.Time
12+
}
13+
14+
func (d Date) MarshalJSON() ([]byte, error) {
15+
return json.Marshal(d.Time.Format(DateFormat))
16+
}
17+
18+
func (d *Date) UnmarshalJSON(data []byte) error {
19+
var dateStr string
20+
err := json.Unmarshal(data, &dateStr)
21+
if err != nil {
22+
return err
23+
}
24+
parsed, err := time.Parse(DateFormat, dateStr)
25+
if err != nil {
26+
return err
27+
}
28+
d.Time = parsed
29+
return nil
30+
}
31+
32+
func (d Date) String() string {
33+
return d.Time.Format(DateFormat)
34+
}
35+
36+
func (d *Date) UnmarshalText(data []byte) error {
37+
parsed, err := time.Parse(DateFormat, string(data))
38+
if err != nil {
39+
return err
40+
}
41+
d.Time = parsed
42+
return nil
43+
}

types/date_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestDate_MarshalJSON(t *testing.T) {
13+
testDate := time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC)
14+
b := struct {
15+
DateField Date `json:"date"`
16+
}{
17+
DateField: Date{testDate},
18+
}
19+
jsonBytes, err := json.Marshal(b)
20+
assert.NoError(t, err)
21+
assert.JSONEq(t, `{"date":"2019-04-01"}`, string(jsonBytes))
22+
}
23+
24+
func TestDate_UnmarshalJSON(t *testing.T) {
25+
testDate := time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC)
26+
jsonStr := `{"date":"2019-04-01"}`
27+
b := struct {
28+
DateField Date `json:"date"`
29+
}{}
30+
err := json.Unmarshal([]byte(jsonStr), &b)
31+
assert.NoError(t, err)
32+
assert.Equal(t, testDate, b.DateField.Time)
33+
}
34+
35+
func TestDate_Stringer(t *testing.T) {
36+
t.Run("nil date", func(t *testing.T) {
37+
var d *Date
38+
assert.Equal(t, "<nil>", fmt.Sprintf("%v", d))
39+
})
40+
41+
t.Run("ptr date", func(t *testing.T) {
42+
d := &Date{
43+
Time: time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC),
44+
}
45+
assert.Equal(t, "2019-04-01", fmt.Sprintf("%v", d))
46+
})
47+
48+
t.Run("value date", func(t *testing.T) {
49+
d := Date{
50+
Time: time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC),
51+
}
52+
assert.Equal(t, "2019-04-01", fmt.Sprintf("%v", d))
53+
})
54+
}
55+
56+
func TestDate_UnmarshalText(t *testing.T) {
57+
testDate := time.Date(2022, 6, 14, 0, 0, 0, 0, time.UTC)
58+
value := []byte("2022-06-14")
59+
60+
date := Date{}
61+
err := date.UnmarshalText(value)
62+
63+
assert.NoError(t, err)
64+
assert.Equal(t, testDate, date.Time)
65+
}

types/email.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
)
7+
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.
14+
type Email string
15+
16+
func (e Email) MarshalJSON() ([]byte, error) {
17+
if !emailRegex.MatchString(string(e)) {
18+
return nil, ErrValidationEmail
19+
}
20+
21+
return json.Marshal(string(e))
22+
}
23+
24+
func (e *Email) UnmarshalJSON(data []byte) error {
25+
if e == nil {
26+
return nil
27+
}
28+
29+
var s string
30+
if err := json.Unmarshal(data, &s); err != nil {
31+
return err
32+
}
33+
34+
*e = Email(s)
35+
if !emailRegex.MatchString(s) {
36+
return ErrValidationEmail
37+
}
38+
39+
return nil
40+
}

types/email_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestEmail_MarshalJSON_Validation(t *testing.T) {
11+
type requiredEmail struct {
12+
EmailField Email `json:"email"`
13+
}
14+
15+
testCases := map[string]struct {
16+
email Email
17+
expectedJSON []byte
18+
expectedError error
19+
}{
20+
"it should succeed marshalling a valid email and return valid JSON populated with the email": {
21+
email: Email("[email protected]"),
22+
expectedJSON: []byte(`{"email":"[email protected]"}`),
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+
})
50+
}
51+
}
52+
53+
func TestEmail_UnmarshalJSON_RequiredEmail_Validation(t *testing.T) {
54+
type requiredEmail struct {
55+
EmailField Email `json:"email"`
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":"[email protected]"}`,
65+
expectedError: nil,
66+
expectedEmail: func() Email {
67+
e := Email("[email protected]")
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":"[email protected]"}`,
126+
expectedError: nil,
127+
expectedEmail: func() *Email {
128+
e := Email("[email protected]")
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+
}
176+
}

types/file.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"mime/multipart"
8+
)
9+
10+
type File struct {
11+
multipart *multipart.FileHeader
12+
data []byte
13+
filename string
14+
}
15+
16+
func (file *File) InitFromMultipart(header *multipart.FileHeader) {
17+
file.multipart = header
18+
file.data = nil
19+
file.filename = ""
20+
}
21+
22+
func (file *File) InitFromBytes(data []byte, filename string) {
23+
file.data = data
24+
file.filename = filename
25+
file.multipart = nil
26+
}
27+
28+
func (file File) MarshalJSON() ([]byte, error) {
29+
b, err := file.Bytes()
30+
if err != nil {
31+
return nil, err
32+
}
33+
return json.Marshal(b)
34+
}
35+
36+
func (file *File) UnmarshalJSON(data []byte) error {
37+
return json.Unmarshal(data, &file.data)
38+
}
39+
40+
func (file File) Bytes() ([]byte, error) {
41+
if file.multipart != nil {
42+
f, err := file.multipart.Open()
43+
if err != nil {
44+
return nil, err
45+
}
46+
defer func() { _ = f.Close() }()
47+
return io.ReadAll(f)
48+
}
49+
return file.data, nil
50+
}
51+
52+
func (file File) Reader() (io.ReadCloser, error) {
53+
if file.multipart != nil {
54+
return file.multipart.Open()
55+
}
56+
return io.NopCloser(bytes.NewReader(file.data)), nil
57+
}
58+
59+
func (file File) Filename() string {
60+
if file.multipart != nil {
61+
return file.multipart.Filename
62+
}
63+
return file.filename
64+
}
65+
66+
func (file File) FileSize() int64 {
67+
if file.multipart != nil {
68+
return file.multipart.Size
69+
}
70+
return int64(len(file.data))
71+
}

0 commit comments

Comments
 (0)