Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend validators #24

Merged
merged 1 commit into from
Sep 6, 2024
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
2 changes: 1 addition & 1 deletion human_text.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package validator

const humanRegexp = "^[\\p{L}\\d !?-~–—‘.,'\"«»„“’`´′″\\[\\]\\/]+$"
const humanRegexp = "^[\\p{L}\\d !?-~-–—:;#()‘.,'\"«»„“’`´′″\\[\\]\\/]+$"

type HumanText struct {
*MatchRegularExpression
Expand Down
7 changes: 7 additions & 0 deletions match_regular_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ func NewMatchRegularExpression(pattern string) *MatchRegularExpression {
}
}

func (r *MatchRegularExpression) WithPattern(pattern string) *MatchRegularExpression {
rc := *r
rc.pattern = pattern

return &rc
}

func (r *MatchRegularExpression) WithMessage(message string) *MatchRegularExpression {
rc := *r
rc.message = message
Expand Down
81 changes: 81 additions & 0 deletions or.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package validator

import (
"context"
)

type OR struct {
rules []Rule
message string
whenFunc WhenFunc
skipEmpty bool
skipError bool
}

func NewOR(message string, rules ...Rule) *OR {
return &OR{
message: message,
rules: rules,
}
}

func (o *OR) WithMessage(message string) *OR {
rc := *o
rc.message = message

return &rc
}

func (o *OR) When(v WhenFunc) *OR {
rc := *o
rc.whenFunc = v

return &rc
}

func (o *OR) when() WhenFunc {
return o.whenFunc
}

func (o *OR) setWhen(v WhenFunc) {
o.whenFunc = v
}

func (o *OR) SkipOnEmpty() *OR {
rc := *o
rc.skipEmpty = true

return &rc
}

func (o *OR) skipOnEmpty() bool {
return o.skipEmpty
}

func (o *OR) setSkipOnEmpty(v bool) {
o.skipEmpty = v
}

func (o *OR) SkipOnError() *OR {
rs := *o
rs.skipError = true

return &rs
}

func (o *OR) shouldSkipOnError() bool {
return o.skipError
}
func (o *OR) setSkipOnError(v bool) {
o.skipError = v
}

func (o *OR) ValidateValue(ctx context.Context, value any) error {
for _, r := range o.rules {
if err := r.ValidateValue(ctx, value); err == nil {
return nil
}
}

return NewResult().WithError(NewValidationError(o.message))
}
80 changes: 80 additions & 0 deletions or_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package validator

import (
"context"
"testing"

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

func TestOR_ValidateValue_Successfully(t *testing.T) {
ctx := context.Background()

type args struct {
rules []Rule
value any
}
tests := []struct {
name string
args args
}{
{
name: "ip or mac rules for ip value",
args: args{
rules: []Rule{
NewIP(),
NewMAC(),
},
value: "127.0.0.1",
},
},
{
name: "ip or mac rules for mac value",
args: args{
rules: []Rule{
NewIP(),
NewMAC(),
},
value: "00:1b:63:84:45:e6",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := NewOR("Value is not in ip or mac format.", tt.args.rules...)
err := o.ValidateValue(ctx, tt.args.value)
require.NoError(t, err)
})
}
}

func TestOR_ValidateValue_Failure(t *testing.T) {
ctx := context.Background()

type args struct {
rules []Rule
value any
}
tests := []struct {
name string
args args
}{
{
name: "ip or mac rules for invalid value",
args: args{
rules: []Rule{
NewIP(),
NewMAC(),
},
value: "hello world",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := NewOR("Value is not in ip or mac format.", tt.args.rules...)
err := o.ValidateValue(ctx, tt.args.value)
require.Error(t, err)
})
}
}
102 changes: 102 additions & 0 deletions url.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package validator

import (
"context"
"net/url"
"strings"

"golang.org/x/net/idna"
Expand Down Expand Up @@ -164,3 +165,104 @@ func (r *URL) getPattern() string {
"((?i)"+strings.Join(r.validSchemes, "|")+")",
)
}

type DeepLinkURL struct {
invalidSchemes []string
message string
whenFunc WhenFunc
skipEmpty bool
skipError bool
}

func NewDeepLinkURL() *DeepLinkURL {
return &DeepLinkURL{
invalidSchemes: []string{"http", "https", "ws"},
message: "This value is not a valid deep link url.",
}
}

func (r *DeepLinkURL) WithInvalidSchemes(schemes []string) *DeepLinkURL {
rc := *r
rc.invalidSchemes = schemes

return &rc
}

func (r *DeepLinkURL) WithMessage(message string) *DeepLinkURL {
rc := *r
rc.message = message

return &rc
}

func (r *DeepLinkURL) When(v WhenFunc) *DeepLinkURL {
rc := *r
rc.whenFunc = v

return &rc
}

func (r *DeepLinkURL) when() WhenFunc {
return r.whenFunc
}

func (r *DeepLinkURL) setWhen(v WhenFunc) {
r.whenFunc = v
}

func (r *DeepLinkURL) SkipOnEmpty() *DeepLinkURL {
rc := *r
rc.skipEmpty = true

return &rc
}

func (r *DeepLinkURL) skipOnEmpty() bool {
return r.skipEmpty
}

func (r *DeepLinkURL) setSkipOnEmpty(v bool) {
r.skipEmpty = v
}

func (r *DeepLinkURL) SkipOnError() *DeepLinkURL {
rs := *r
rs.skipError = true

return &rs
}

func (r *DeepLinkURL) shouldSkipOnError() bool {
return r.skipError
}
func (r *DeepLinkURL) setSkipOnError(v bool) {
r.skipError = v
}

func (r *DeepLinkURL) ValidateValue(_ context.Context, value any) error {
v, ok := toString(value)
// make sure the length is limited to avoid DOS attacks
if !ok || len(v) >= 2000 {
return NewResult().WithError(NewValidationError(r.message))
}

uri, err := url.Parse(v)
if err != nil {
return NewResult().WithError(NewValidationError(r.message))
}

if len(uri.Scheme) == 0 || (len(uri.Host) == 0 && len(uri.Opaque) == 0) {
return NewResult().WithError(NewValidationError(r.message))
}

if len(r.invalidSchemes) > 0 {
for _, s := range r.invalidSchemes {
if s == uri.Scheme {
return NewResult().WithError(NewValidationError(r.message))
}
}

}

return nil
}
14 changes: 14 additions & 0 deletions url_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
func BenchmarkValidatorUrl(b *testing.B) {
ctx := context.Background()
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
err := NewURL().ValidateValue(ctx, "https://example.com")
Expand All @@ -16,3 +17,16 @@ func BenchmarkValidatorUrl(b *testing.B) {
}
}
}

func BenchmarkValidatorDeepLinkURL(b *testing.B) {
ctx := context.Background()
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
err := NewDeepLinkURL().ValidateValue(ctx, "tg:resolve?domain={domain}")
if err != nil {
b.Error(err)
}
}
}
66 changes: 66 additions & 0 deletions url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,69 @@ func TestUrlValidateValue_InvalidValue_ReturnsExpectedErrorMessage(t *testing.T)
assert.Error(t, err)
assert.Equal(t, "test error.", err.Error())
}

func TestDeepLinkURL_ValidateValue_ValidURL_Successfully(t *testing.T) {
tests := []struct {
Name string
URL string
}{
{
Name: "tg protocol #1",
URL: "tg:resolve?domain={domain}",
},
{
Name: "tg protocol #2",
URL: "tg://resolve?domain={domain}",
},
{
Name: "tg protocol #1 without query",
URL: "tg:resolve",
},
{
Name: "tg protocol #2 without query",
URL: "tg://resolve",
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
ctx := context.Background()
r := NewDeepLinkURL()
err := r.ValidateValue(ctx, tt.URL)
assert.NoError(t, err)
})
}
}

func TestDeepLinkURL_ValidateValue_InvalidURL_Failure(t *testing.T) {
tests := []struct {
Name string
URL string
}{
{
Name: "http scheme",
URL: "http://example.com",
},
{
Name: "https scheme",
URL: "https://example.com",
},
{
Name: "domain only",
URL: "example.com",
},
{
Name: "invalid scheme",
URL: "://example.com",
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
ctx := context.Background()
r := NewDeepLinkURL()
err := r.ValidateValue(ctx, tt.URL)
assert.Error(t, err)
})
}
}
Loading