Skip to content
Open
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
70 changes: 51 additions & 19 deletions baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -1465,29 +1465,49 @@ func isURI(fl FieldLevel) bool {
func isURL(fl FieldLevel) bool {
field := fl.Field()

var s string
var uri *url.URL
var ok bool

switch field.Kind() {
case reflect.String:
s = strings.ToLower(field.String())
case reflect.Struct:
var u url.URL
if u, ok = field.Interface().(url.URL); !ok {
panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
uri = &u
default:
var stringer fmt.Stringer
if stringer, ok = field.Interface().(fmt.Stringer); !ok {
panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
s = stringer.String()
}

s := strings.ToLower(field.String())
if len(s) == 0 && uri == nil {
return false
}

if len(s) == 0 {
if uri == nil {
var err error
if uri, err = url.Parse(s); err != nil {
return false
}
}

url, err := url.Parse(s)
if err != nil || url.Scheme == "" {
return false
}
isFileScheme := url.Scheme == "file"
if uri.Scheme == "" {
return false
}

if (isFileScheme && (len(url.Path) == 0 || url.Path == "/")) || (!isFileScheme && len(url.Host) == 0 && len(url.Fragment) == 0 && len(url.Opaque) == 0) {
return false
}
isFileScheme := uri.Scheme == "file"

return true
if (isFileScheme && (len(uri.Path) == 0 || uri.Path == "/")) || (!isFileScheme && len(uri.Host) == 0 && len(uri.Fragment) == 0 && len(uri.Opaque) == 0) {
return false
}

panic(fmt.Sprintf("Bad field type %s", field.Type()))
return true
}

// isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL.
Expand All @@ -1497,20 +1517,32 @@ func isHttpURL(fl FieldLevel) bool {
}

field := fl.Field()
var s string
var ok bool
switch field.Kind() {
case reflect.String:
s = strings.ToLower(field.String())
case reflect.Struct:
var u url.URL

s := strings.ToLower(field.String())

url, err := url.Parse(s)
if err != nil || url.Host == "" {
return false
if u, ok = field.Interface().(url.URL); !ok {
panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
s = u.String()
default:
if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok {
s = stringer.String()
} else {
panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
}

return url.Scheme == "http" || url.Scheme == "https"
url, err := url.Parse(s)
if err != nil || url.Host == "" {
return false
}

panic(fmt.Sprintf("Bad field type %s", field.Type()))
return url.Scheme == "http" || url.Scheme == "https"
}

// isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141.
Expand Down
3 changes: 1 addition & 2 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
}

case reflect.Struct:
isNestedStruct = !current.Type().ConvertibleTo(timeType)
isNestedStruct = !current.Type().ConvertibleTo(timeType) && !current.Type().ConvertibleTo(urlType)
// For backward compatibility before struct level validation tags were supported
// as there were a number of projects relying on `required` not failing on non-pointer
// structs. Since it's basically nonsensical to use `required` with a non-pointer struct
Expand Down Expand Up @@ -460,7 +460,6 @@ OUTER:
}

default:

// set Field Level fields
v.slflParent = parent
v.flField = current
Expand Down
2 changes: 2 additions & 0 deletions validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/url"
"reflect"
"strings"
"sync"
Expand Down Expand Up @@ -54,6 +55,7 @@ const (
var (
timeDurationType = reflect.TypeOf(time.Duration(0))
timeType = reflect.TypeOf(time.Time{})
urlType = reflect.TypeOf(url.URL{})

byteSliceType = reflect.TypeOf([]byte{})

Expand Down
Loading