Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions internals/is/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package is

import "regexp"

var (
// Email Regex used in: https://github.com/AfterShip/email-verifier/tree/aa8f77c0586ed2ecf9c20cb221de09282ce75355
// emailRegex = regexp.MustCompile("^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
// Email regex used in: https://github.com/go-playground/validator/blob/0e3e2f997385102062275f226e825b4a109f4833/regexes.go#L21
emailRegex = regexp.MustCompile("^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")

// Zod UUID regex
uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$`)
)

func Email(val string) bool {
return emailRegex.MatchString(val)
}

func UUIDv4(val string) bool {
return uuidRegex.MatchString(val)
}
159 changes: 159 additions & 0 deletions internals/is/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package is

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEmail(t *testing.T) {

validEmails := []string{
`email@domain.com`,
`firstname.lastname@domain.com`,
`email@subdomain.domain.com`,
`firstname+lastname@domain.com`,
`1234567890@domain.com`,
`email@domain-one.com`,
`_______@domain.com`,
`email@domain.name`,
`email@domain.co.jp`,
`firstname-lastname@domain.com`,
`very.common@example.com`,
`disposable.style.email.with+symbol@example.com`,
`other.email-with-hyphen@example.com`,
`fully-qualified-domain@example.com`,
`user.name+tag+sorting@example.com`,
`x@example.com`,
`mojojojo@asdf.example.com`,
`example-indeed@strange-example.com`,
`example@s.example`,
`user-@example.org`,
`user@my-example.com`,
`a@b.cd`,
`work+user@mail.com`,
`tom@test.te-st.com`,
`something@subdomain.domain-with-hyphens.tld`,
`common'name@domain.com`,
`francois@etu.inp-n7.fr`,
}
invalidEmails := []string{
// no "printable characters"
// `user%example.com@example.org`,
// `mailhost!username@example.org`,
// `test/test@test.com`,

// double @
`francois@@etu.inp-n7.fr`,
// do not support quotes
// do not support comma
`a,b@domain.com`,

// do not support IPv4
`email@123.123.123.123`,
`email@[123.123.123.123]`,
`postmaster@123.123.123.123`,
`user@[68.185.127.196]`,
`ipv4@[85.129.96.247]`,
`valid@[79.208.229.53]`,
`valid@[255.255.255.255]`,
`valid@[255.0.55.2]`,
`valid@[255.0.55.2]`,

// do not support ipv6
`hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
`bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
`jsmith@[IPv6:2001:db8::1]`,
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,

// microsoft test cases
`plainaddress`,
`#@%^%#$@#$@#.com`,
`@domain.com`,
`Joe Smith <email@domain.com>`,
`email.domain.com`,
`email@domain@domain.com`,
`.email@domain.com`,
`email.@domain.com`,
`email..email@domain.com`,
`email@domain.com (Joe Smith)`,
`email@domain`,
`email@-domain.com`,
`email@111.222.333.44444`,
`email@domain..com`,
`Abc.example.com`,
`A@b@c@example.com`,
`colin..hacks@domain.com`,
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
`just"not"right@example.com`,
`this is"not\allowed@example.com`,
`this\ still\"not\\allowed@example.com`,

// random
"email@email",
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
`QA[icon]CHOCOLATE[icon]@test.com`,
`invalid@-start.com`,
`invalid@end.com-`,
`invalid@[1.1.1.-1]`,
`invalid@[68.185.127.196.55]`,
`temp@[192.168.1]`,
`temp@[9.18.122.]`,
`double..point@test.com`,
`asdad@test..com`,
`asdad@hghg...sd...au`,
`asdad@hghg........au`,
`invalid@[256.2.2.48]`,
`invalid@[256.2.2.48]`,
`invalid@[999.465.265.1]`,
`jkibbey4@[IPv6:82c4:19a8::70a9:2aac:557::ea69:d985:28d]`,
`mlivesay3@[9952:143f:b4df:2179:49a1:5e82:b92e:6b6]`,
`gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`,
`invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`,
`test@.com`,

// Should be false but don't work with the current regex
// `"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
// `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`,
// `a.b@c.d`,
// `あいうえお@domain.com`,
// `"john..doe"@example.org`,
// `" "@example.org`,
// `"email"@domain.com`,
// `"e asdf sadf ?<>ail"@domain.com`,
}

for _, email := range validEmails {
assert.True(t, Email(email), "should be valid email: %s", email)
}

for _, email := range invalidEmails {
assert.False(t, Email(email), "should be invalid email: %s", email)
}
}

func TestUUIDv4(t *testing.T) {
validUUIDs := []string{
"9491d710-3185-4e06-bea0-6a2f275345e0",
"d89e7b01-7598-ed11-9d7a-0022489382fd",
"00000000-0000-0000-0000-000000000000",
"b3ce60f8-e8b9-40f5-1150-172ede56ff74",
"92e76bf9-28b3-4730-cd7f-cb6bc51f8c09",
}

Comment on lines +137 to +144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

“validUUIDs” contains non-v4 & invalid entries – tests are red

00000000-0000-0000-0000-000000000000 (version 0) and
d89e7b01-7598-ed11-… / b3ce60f8-e8b9-40f5-1150-… (variant/version mismatch) do not satisfy the v4 regex, causing CI failure.

Action items:

-validUUIDs := []string{
-    "9491d710-3185-4e06-bea0-6a2f275345e0",
-    "d89e7b01-7598-ed11-9d7a-0022489382fd",
-    "00000000-0000-0000-0000-000000000000",
-    "b3ce60f8-e8b9-40f5-1150-172ede56ff74",
-    "92e76bf9-28b3-4730-cd7f-cb6bc51f8c09",
-}
+validUUIDs := []string{
+    "9491d710-3185-4e06-bea0-6a2f275345e0",
+    "92e76bf9-28b3-4730-8d7f-cb6bc51f8c09", // adjust to v4
+}
+
+invalidUUIDs = append(invalidUUIDs,
+    "d89e7b01-7598-ed11-9d7a-0022489382fd",
+    "00000000-0000-0000-0000-000000000000",
+    "b3ce60f8-e8b9-40f5-1150-172ede56ff74")

Or, if all versions should be accepted, update the production regex accordingly (see comment in regex.go).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validUUIDs := []string{
"9491d710-3185-4e06-bea0-6a2f275345e0",
"d89e7b01-7598-ed11-9d7a-0022489382fd",
"00000000-0000-0000-0000-000000000000",
"b3ce60f8-e8b9-40f5-1150-172ede56ff74",
"92e76bf9-28b3-4730-cd7f-cb6bc51f8c09",
}
validUUIDs := []string{
"9491d710-3185-4e06-bea0-6a2f275345e0",
"92e76bf9-28b3-4730-8d7f-cb6bc51f8c09", // adjust to v4
}
invalidUUIDs = append(invalidUUIDs,
"d89e7b01-7598-ed11-9d7a-0022489382fd",
"00000000-0000-0000-0000-000000000000",
"b3ce60f8-e8b9-40f5-1150-172ede56ff74")
🤖 Prompt for AI Agents
In internals/is/regex_test.go around lines 137 to 144, the validUUIDs slice
includes UUIDs that are not version 4 or are invalid, causing test failures. To
fix this, replace the entries with only valid version 4 UUIDs that match the v4
regex pattern. Alternatively, if the intention is to accept all UUID versions,
update the production regex in regex.go to match all valid UUID versions
accordingly.

invalidUUIDs := []string{
"9491d710-3185-4e06-bea0-6a2f275345e0X",
// random
"not a uuid",
}

for _, uuid := range validUUIDs {
assert.True(t, UUIDv4(uuid), "should be valid uuid: %s", uuid)
}

for _, uuid := range invalidUUIDs {
assert.False(t, UUIDv4(uuid), "should be invalid uuid: %s", uuid)
}

}
8 changes: 3 additions & 5 deletions string.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import (

"github.com/Oudwins/zog/conf"
p "github.com/Oudwins/zog/internals"
"github.com/Oudwins/zog/internals/is"
"github.com/Oudwins/zog/zconst"
)

var (
_ PrimitiveZogSchema[string] = (*StringSchema[string])(nil)
_ NotStringSchema[string] = (*StringSchema[string])(nil)

emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$`)
)

type likeString interface {
Expand Down Expand Up @@ -214,7 +212,7 @@ func (v *StringSchema[T]) Len(n int, options ...TestOption) *StringSchema[T] {
func (v *StringSchema[T]) Email(options ...TestOption) *StringSchema[T] {
t := p.Test[*T]{IssueCode: zconst.IssueCodeEmail}
fn := func(v *T, ctx Ctx) bool {
return emailRegex.MatchString(string(*v))
return is.Email(string(*v))
}
return v.addTest(t, fn, options...)
}
Expand Down Expand Up @@ -317,7 +315,7 @@ func (v *StringSchema[T]) ContainsSpecial(options ...TestOption) *StringSchema[T
func (v *StringSchema[T]) UUID(options ...TestOption) *StringSchema[T] {
t := p.Test[*T]{IssueCode: zconst.IssueCodeUUID}
fn := func(v *T, ctx Ctx) bool {
return uuidRegex.MatchString(string(*v))
return is.UUIDv4(string(*v))
}

return v.addTest(t, fn, options...)
Expand Down
Loading