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 #4 from choria-io/3
Browse files Browse the repository at this point in the history
(#3) add ipv4, ipv6 and ipaddress validators
  • Loading branch information
ripienaar authored Jan 23, 2018
2 parents 6c64503 + 5e77ddb commit 91742c0
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
vendor
.DS_Store
2 changes: 2 additions & 0 deletions example_struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ type Request struct {
Command string `validate:"shellsafe"`
Flags []string `validate:"enum=debug,verbose"`
Args string `validate:"maxlength=128"`
AnyIP string `validate:"ipaddress"` // can also check ipv4 and ipv6
}

func Example_struct() {
r := Request{
Command: "/bin/some/command",
Flags: []string{"debug"},
Args: "hello world",
AnyIP: "2a00:1450:4003:807::200e",
}

ok, err := validator.ValidateStruct(r)
Expand Down
27 changes: 27 additions & 0 deletions ipaddress/ipaddress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ipaddress

import (
"fmt"
"net"
"reflect"
)

// ValidateString validates that the given string is either an IPv6 or an IPv4 address
func ValidateString(input string) (bool, error) {
ip := net.ParseIP(input)

if ip == nil {
return false, fmt.Errorf("%s is not an IP address", input)
}

return true, nil
}

// ValidateStructField validates a struct field holds either an IPv6 or an IPv4 address
func ValidateStructField(value reflect.Value, tag string) (bool, error) {
if value.Kind() != reflect.String {
return false, fmt.Errorf("only strings can be IPv6 validated")
}

return ValidateString(value.String())
}
60 changes: 60 additions & 0 deletions ipaddress/ipaddress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ipaddress

import (
"reflect"
"testing"

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

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

var _ = Describe("ValidateString", func() {
It("Should match ipv4 addresses correctly", func() {
ok, err := ValidateString("1.2.3.4")
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

ok, err = ValidateString("2a00:1450:4003:807::200e")
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

ok, err = ValidateString("foo")
Expect(err).To(MatchError("foo is not an IP address"))
Expect(ok).To(BeFalse())
})
})

var _ = Describe("ValidateStructField", func() {
type t struct {
IP string `validate:"ipaddress"`
}

It("Should validate the struct correctly", func() {
st := t{"1.2.3.4"}

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

ok, err := ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

st.IP = "foo"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("foo is not an IP address"))
Expect(ok).To(BeFalse())

st.IP = "2a00:1450:4003:807::200e"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
})
})
27 changes: 27 additions & 0 deletions ipv4/ipv4.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ipv4

import (
"fmt"
"net"
"reflect"
)

// ValidateString validates that the given string is an IPv4 address
func ValidateString(input string) (bool, error) {
ip := net.ParseIP(input).To4()

if ip == nil {
return false, fmt.Errorf("%s is not an IPv4 address", input)
}

return true, nil
}

// ValidateStructField validates a struct field holds an IPv4 address
func ValidateStructField(value reflect.Value, tag string) (bool, error) {
if value.Kind() != reflect.String {
return false, fmt.Errorf("only strings can be IPv4 validated")
}

return ValidateString(value.String())
}
60 changes: 60 additions & 0 deletions ipv4/ipv4_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ipv4

import (
"reflect"
"testing"

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

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

var _ = Describe("ValidateString", func() {
It("Should match ipv4 addresses correctly", func() {
ok, err := ValidateString("1.2.3.4")
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

ok, err = ValidateString("foo")
Expect(err).To(MatchError("foo is not an IPv4 address"))
Expect(ok).To(BeFalse())

ok, err = ValidateString("2a00:1450:4003:807::200e")
Expect(err).To(MatchError("2a00:1450:4003:807::200e is not an IPv4 address"))
Expect(ok).To(BeFalse())
})
})

var _ = Describe("ValidateStructField", func() {
type t struct {
IP string `validate:"ipv4"`
}

It("Should validate the struct correctly", func() {
st := t{"1.2.3.4"}

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

ok, err := ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

st.IP = "foo"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("foo is not an IPv4 address"))
Expect(ok).To(BeFalse())

st.IP = "2a00:1450:4003:807::200e"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("2a00:1450:4003:807::200e is not an IPv4 address"))
Expect(ok).To(BeFalse())
})
})
31 changes: 31 additions & 0 deletions ipv6/ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ipv6

import (
"fmt"
"net"
"reflect"
)

// ValidateString validates that the given string is an IPv4 address
func ValidateString(input string) (bool, error) {
ip := net.ParseIP(input)

if ip == nil {
return false, fmt.Errorf("%s is not an IPv6 address", input)
}

if ip.To4() != nil {
return false, fmt.Errorf("%s is not an IPv6 address", input)
}

return true, nil
}

// ValidateStructField validates a struct field holds an IPv4 address
func ValidateStructField(value reflect.Value, tag string) (bool, error) {
if value.Kind() != reflect.String {
return false, fmt.Errorf("only strings can be IPv6 validated")
}

return ValidateString(value.String())
}
60 changes: 60 additions & 0 deletions ipv6/ipv6_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ipv6

import (
"reflect"
"testing"

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

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

var _ = Describe("ValidateString", func() {
It("Should match ipv4 addresses correctly", func() {
ok, err := ValidateString("1.2.3.4")
Expect(err).To(MatchError("1.2.3.4 is not an IPv6 address"))
Expect(ok).To(BeFalse())

ok, err = ValidateString("foo")
Expect(err).To(MatchError("foo is not an IPv6 address"))
Expect(ok).To(BeFalse())

ok, err = ValidateString("2a00:1450:4003:807::200e")
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
})
})

var _ = Describe("ValidateStructField", func() {
type t struct {
IP string `validate:"ipv6"`
}

It("Should validate the struct correctly", func() {
st := t{"1.2.3.4"}

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

ok, err := ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("1.2.3.4 is not an IPv6 address"))
Expect(ok).To(BeFalse())

st.IP = "foo"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).To(MatchError("foo is not an IPv6 address"))
Expect(ok).To(BeFalse())

st.IP = "2a00:1450:4003:807::200e"
valueField = reflect.ValueOf(st).FieldByName("IP")
ok, err = ValidateStructField(valueField, typeField.Tag.Get("validate"))
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
})
})
2 changes: 2 additions & 0 deletions maxlength/maxlength.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
)

// ValidateString validates that input is not longer than max
func ValidateString(input string, max int) (bool, error) {
if len(input) > max {
return false, fmt.Errorf("%d characters, max allowed %d", len(input), max)
Expand All @@ -15,6 +16,7 @@ func ValidateString(input string, max int) (bool, error) {
return true, nil
}

// ValidateStructField validates value based on the tag in the form maxlength=10
func ValidateStructField(value reflect.Value, tag string) (bool, error) {
re := regexp.MustCompile(`^maxlength=(\d+)$`)
parts := re.FindStringSubmatch(tag)
Expand Down
18 changes: 18 additions & 0 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
"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/shellsafe"
)
Expand Down Expand Up @@ -84,6 +87,21 @@ func validateStructField(valueField reflect.Value, typeField reflect.StructField
return fmt.Errorf("%s shellsafe validation failed: %s", typeField.Name, err)
}

} else if validation == "ipv4" {
if ok, err := ipv4.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s IPv4 validation failed: %s", typeField.Name, err)
}

} else if validation == "ipv6" {
if ok, err := ipv6.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s IPv6 validation failed: %s", typeField.Name, err)
}

} else if validation == "ipaddress" {
if ok, err := ipaddress.ValidateStructField(valueField, validation); !ok {
return fmt.Errorf("%s IP address validation failed: %s", typeField.Name, err)
}

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

0 comments on commit 91742c0

Please sign in to comment.