Skip to content

Commit

Permalink
extend validators (#24)
Browse files Browse the repository at this point in the history
Co-authored-by: Кирилл Маликов <[email protected]>
  • Loading branch information
slipros and Кирилл Маликов authored Sep 6, 2024
1 parent 7a4c525 commit 534faf6
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 1 deletion.
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)
})
}
}

0 comments on commit 534faf6

Please sign in to comment.