Skip to content
This repository has been archived by the owner on Feb 5, 2020. It is now read-only.

Commit

Permalink
Merge pull request #6 from ripienaar/5
Browse files Browse the repository at this point in the history
(#5) add regex validation
  • Loading branch information
ripienaar authored Jan 23, 2018
2 parents 91742c0 + c583049 commit 99e0f79
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 6 deletions.
2 changes: 2 additions & 0 deletions example_struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Request struct {
Flags []string `validate:"enum=debug,verbose"`
Args string `validate:"maxlength=128"`
AnyIP string `validate:"ipaddress"` // can also check ipv4 and ipv6
User string `validate:"regex=^\\w+$"`
}

func Example_struct() {
Expand All @@ -19,6 +20,7 @@ func Example_struct() {
Flags: []string{"debug"},
Args: "hello world",
AnyIP: "2a00:1450:4003:807::200e",
User: "bob",
}

ok, err := validator.ValidateStruct(r)
Expand Down
2 changes: 1 addition & 1 deletion ipaddress/ipaddress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestFileContent(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Validator/IPv4")
RunSpecs(t, "Validator/IPAddress")
}

var _ = Describe("ValidateString", func() {
Expand Down
2 changes: 1 addition & 1 deletion ipv6/ipv6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestFileContent(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Validator/IPv4")
RunSpecs(t, "Validator/IPv6")
}

var _ = Describe("ValidateString", func() {
Expand Down
38 changes: 38 additions & 0 deletions regex/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package regex

import (
"fmt"
"reflect"
"regexp"
)

// ValidateString validates that a string matches a regex
func ValidateString(input string, regex string) (bool, error) {
re, err := regexp.Compile(regex)
if err != nil {
return false, fmt.Errorf("invalid regex '%s'", regex)
}

if !re.MatchString(input) {
return false, fmt.Errorf("input does not match '%s'", regex)
}

return true, nil
}

// ValidateStructField validates that field holds a string matching the regex
// tag must be in the form `validate:"regex=\d+"`
func ValidateStructField(value reflect.Value, tag string) (bool, error) {
re := regexp.MustCompile(`^regex=(.+)$`)
parts := re.FindStringSubmatch(tag)

if len(parts) != 2 {
return false, fmt.Errorf("invalid tag '%s', must be in the form regex=^hello.+world$", tag)
}

if value.Kind() != reflect.String {
return false, fmt.Errorf("only strings can be regex validated")
}

return ValidateString(value.String(), parts[1])
}
69 changes: 69 additions & 0 deletions regex/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package regex

import (
"reflect"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestFileContent(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Validator/Regex")
}

var _ = Describe("ValidateString", func() {
It("Should match strings correctly", func() {
ok, err := ValidateString("hello world", "world$")
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

ok, err = ValidateString("hello", "world$")
Expect(err).To(MatchError("input does not match 'world$'"))
Expect(ok).To(BeFalse())

ok, err = ValidateString("hello", "invalid(")
Expect(err).To(MatchError("invalid regex 'invalid('"))
Expect(ok).To(BeFalse())
})
})

var _ = Describe("ValidateStructField", func() {
type t struct {
String string `validate:"regex=world$"`
Invalid string `validate:"regex"`
}

It("Should fail for invalid tags", func() {
st := t{"1", "foo"}

val := reflect.ValueOf(st)
valueField := val.FieldByName("Invalid")
typeField, _ := val.Type().FieldByName("Invalid")

ok, err := ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("invalid tag 'regex', must be in the form regex=^hello.+world$"))
Expect(ok).To(BeFalse())
})

It("Should match the regex correctly", func() {
st := t{"fail", "foo"}

val := reflect.ValueOf(st)
valueField := val.FieldByName("String")
typeField, _ := val.Type().FieldByName("String")

ok, err := ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("input does not match 'world$'"))
Expect(ok).To(BeFalse())

st.String = "hello world"
val = reflect.ValueOf(st)
valueField = val.FieldByName("String")

ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
})
})
12 changes: 9 additions & 3 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ package validator
import (
"fmt"
"reflect"
"regexp"
"strings"

"github.com/choria-io/go-validator/enum"
"github.com/choria-io/go-validator/ipaddress"
"github.com/choria-io/go-validator/ipv4"
"github.com/choria-io/go-validator/ipv6"
"github.com/choria-io/go-validator/maxlength"
"github.com/choria-io/go-validator/regex"
"github.com/choria-io/go-validator/shellsafe"
)

Expand Down Expand Up @@ -102,11 +102,17 @@ func validateStructField(valueField reflect.Value, typeField reflect.StructField
return fmt.Errorf("%s IP address validation failed: %s", typeField.Name, err)
}

} else if ok, _ := regexp.MatchString(`^maxlength=\d+$`, validation); ok {
} else if strings.HasPrefix(validation, "regex") {
if ok, err := regex.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s regular expression validation failed: %s", typeField.Name, err)
}

} else if strings.HasPrefix(validation, "maxlength") {
if ok, err := maxlength.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s maxlength validation failed: %s", typeField.Name, err)
}
} else if ok, _ := regexp.MatchString(`^enum=(.+,*?)+$`, validation); ok {

} else if strings.HasPrefix(validation, "enum") {
if ok, err := enum.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s enum validation failed: %s", typeField.Name, err)
}
Expand Down
11 changes: 10 additions & 1 deletion validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type vdata struct {
IPv4 string `validate:"ipv4"`
IPv6 string `validate:"ipv6"`
IP string `validate:"ipaddress"`

RE string `validate:"regex=world$"`
nest
}

Expand All @@ -37,6 +37,7 @@ var _ = Describe("ValidateStructField", func() {
IPv4: "1.2.3.4",
IPv6: "2a00:1450:4003:807::200e",
IP: "1.2.3.4",
RE: "hello world",
}
})

Expand All @@ -60,6 +61,7 @@ var _ = Describe("ValidateStruct", func() {
IPv4: "1.2.3.4",
IPv6: "2a00:1450:4003:807::200e",
IP: "1.2.3.4",
RE: "hello world",
}
})

Expand Down Expand Up @@ -121,4 +123,11 @@ var _ = Describe("ValidateStruct", func() {
Expect(ok).To(BeFalse())
})

It("Should support regex", func() {
s.RE = "1"
ok, err := validator.ValidateStruct(s)

Expect(err).To(MatchError("RE regular expression validation failed: input does not match 'world$'"))
Expect(ok).To(BeFalse())
})
})

0 comments on commit 99e0f79

Please sign in to comment.