Skip to content

Commit e7c250a

Browse files
committed
Make url and http_url work with url.URL.
Support the url and http_url validations with url.URL or any newtype that implements the Stringer interface.
1 parent bc77d03 commit e7c250a

File tree

4 files changed

+537
-27
lines changed

4 files changed

+537
-27
lines changed

baked_in.go

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,29 +1465,49 @@ func isURI(fl FieldLevel) bool {
14651465
func isURL(fl FieldLevel) bool {
14661466
field := fl.Field()
14671467

1468+
var s string
1469+
var uri *url.URL
1470+
var ok bool
1471+
14681472
switch field.Kind() {
14691473
case reflect.String:
1474+
s = strings.ToLower(field.String())
1475+
case reflect.Struct:
1476+
var u url.URL
1477+
if u, ok = field.Interface().(url.URL); !ok {
1478+
panic(fmt.Sprintf("Bad field type %s", field.Type()))
1479+
}
1480+
uri = &u
1481+
default:
1482+
var stringer fmt.Stringer
1483+
if stringer, ok = field.Interface().(fmt.Stringer); !ok {
1484+
panic(fmt.Sprintf("Bad field type %s", field.Type()))
1485+
}
1486+
s = stringer.String()
1487+
}
14701488

1471-
s := strings.ToLower(field.String())
1489+
if len(s) == 0 && uri == nil {
1490+
return false
1491+
}
14721492

1473-
if len(s) == 0 {
1493+
if uri == nil {
1494+
var err error
1495+
if uri, err = url.Parse(s); err != nil {
14741496
return false
14751497
}
1498+
}
14761499

1477-
url, err := url.Parse(s)
1478-
if err != nil || url.Scheme == "" {
1479-
return false
1480-
}
1481-
isFileScheme := url.Scheme == "file"
1500+
if uri.Scheme == "" {
1501+
return false
1502+
}
14821503

1483-
if (isFileScheme && (len(url.Path) == 0 || url.Path == "/")) || (!isFileScheme && len(url.Host) == 0 && len(url.Fragment) == 0 && len(url.Opaque) == 0) {
1484-
return false
1485-
}
1504+
isFileScheme := uri.Scheme == "file"
14861505

1487-
return true
1506+
if (isFileScheme && (len(uri.Path) == 0 || uri.Path == "/")) || (!isFileScheme && len(uri.Host) == 0 && len(uri.Fragment) == 0 && len(uri.Opaque) == 0) {
1507+
return false
14881508
}
14891509

1490-
panic(fmt.Sprintf("Bad field type %s", field.Type()))
1510+
return true
14911511
}
14921512

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

14991519
field := fl.Field()
1520+
var s string
1521+
var ok bool
15001522
switch field.Kind() {
15011523
case reflect.String:
1524+
s = strings.ToLower(field.String())
1525+
case reflect.Struct:
1526+
var u url.URL
15021527

1503-
s := strings.ToLower(field.String())
1504-
1505-
url, err := url.Parse(s)
1506-
if err != nil || url.Host == "" {
1507-
return false
1528+
if u, ok = field.Interface().(url.URL); !ok {
1529+
panic(fmt.Sprintf("Bad field type %s", field.Type()))
1530+
}
1531+
s = u.String()
1532+
default:
1533+
if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok {
1534+
s = stringer.String()
1535+
} else {
1536+
panic(fmt.Sprintf("Bad field type %s", field.Type()))
15081537
}
1538+
}
15091539

1510-
return url.Scheme == "http" || url.Scheme == "https"
1540+
url, err := url.Parse(s)
1541+
if err != nil || url.Host == "" {
1542+
return false
15111543
}
15121544

1513-
panic(fmt.Sprintf("Bad field type %s", field.Type()))
1545+
return url.Scheme == "http" || url.Scheme == "https"
15141546
}
15151547

15161548
// isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141.

validator.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
169169
}
170170

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

462462
default:
463-
464463
// set Field Level fields
465464
v.slflParent = parent
466465
v.flField = current

validator_instance.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net/url"
78
"reflect"
89
"strings"
910
"sync"
@@ -54,6 +55,7 @@ const (
5455
var (
5556
timeDurationType = reflect.TypeOf(time.Duration(0))
5657
timeType = reflect.TypeOf(time.Time{})
58+
urlType = reflect.TypeOf(url.URL{})
5759

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

0 commit comments

Comments
 (0)