Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Commit

Permalink
netaddr: add functionality to IPRange for feature parity (#202)
Browse files Browse the repository at this point in the history
Add MustParseIPRange, IPRange.{IsZero,AppendTo,MarshalText,UnmarshalText}
to bring it to feature parity with the other common types.

Similar to the MarshalText methods of the other types,
it never returns an error for invalid values.
This behavior should probably change, but should be consistently
done for all other types (see #169).

Fixes #201

Signed-off-by: Joe Tsai <[email protected]>
  • Loading branch information
dsnet authored Jul 18, 2021
1 parent d328a73 commit 06ca814
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 11 deletions.
60 changes: 60 additions & 0 deletions netaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,16 @@ func ParseIPRange(s string) (IPRange, error) {
return r, nil
}

// MustParseIPRange calls ParseIPRange(s) and panics on error.
// It is intended for use in tests with hard-coded strings.
func MustParseIPRange(s string) IPRange {
r, err := ParseIPRange(s)
if err != nil {
panic(err)
}
return r
}

// String returns a string representation of the range.
//
// For a valid range, the form is "From-To" with a single hyphen
Expand All @@ -1644,6 +1654,56 @@ func (r IPRange) String() string {
return "invalid IPRange"
}

// AppendTo appends a text encoding of r,
// as generated by MarshalText,
// to b and returns the extended buffer.
func (r IPRange) AppendTo(b []byte) []byte {
if r.IsZero() {
return b
}
b = r.from.AppendTo(b)
b = append(b, '-')
b = r.to.AppendTo(b)
return b
}

// MarshalText implements the encoding.TextMarshaler interface,
// The encoding is the same as returned by String, with one exception:
// If ip is the zero value, the encoding is the empty string.
func (r IPRange) MarshalText() ([]byte, error) {
if r.IsZero() {
return []byte(""), nil
}
var max int
if r.from.z == z4 {
max = len("255.255.255.255-255.255.255.255")
} else {
max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
}
b := make([]byte, 0, max)
return r.AppendTo(b), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The IP range is expected in a form accepted by ParseIPRange.
// It returns an error if *r is not the IPRange zero value.
func (r *IPRange) UnmarshalText(text []byte) error {
if *r != (IPRange{}) {
return errors.New("netaddr: refusing to Unmarshal into non-zero IPRange")
}
if len(text) == 0 {
return nil
}
var err error
*r, err = ParseIPRange(string(text))
return err
}

// IsZero reports whether r is the zero value of the IPRange type.
func (r IPRange) IsZero() bool {
return r == IPRange{}
}

// IsValid reports whether r.From() and r.To() are both non-zero and
// obey the documented requirements: address families match, and From
// is less than or equal to To.
Expand Down
73 changes: 62 additions & 11 deletions netaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,26 @@ func TestParseIPRange(t *testing.T) {
t.Errorf("input %q stringifies back as %q", tt.in, back)
}
}

var r2 IPRange
err = r2.UnmarshalText([]byte(tt.in))
if err != nil {
got = err.Error()
} else {
got = r2
}
if got != tt.want && tt.in != "" {
t.Errorf("UnmarshalText(%q) = %v; want %v", tt.in, got, tt.want)
}

testAppendToMarshal(t, r)
}
}

func TestIPRangeUnmarshalTextNonZero(t *testing.T) {
r := MustParseIPRange("1.2.3.4-5.6.7.8")
if err := r.UnmarshalText([]byte("1.2.3.4-5.6.7.8")); err == nil {
t.Fatal("unmarshaled into non-empty IPPrefix")
}
}

Expand Down Expand Up @@ -2776,12 +2796,6 @@ func TestNoAllocs(t *testing.T) {
}
return ipp
}
panicIPR := func(ipr IPRange, err error) IPRange {
if err != nil {
panic(err)
}
return ipr
}

test := func(name string, f func()) {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -2879,17 +2893,18 @@ func TestNoAllocs(t *testing.T) {

// IPRange constructors
test("IPRangeFrom", func() { sinkIPRange = IPRangeFrom(IPv4(1, 2, 3, 4), IPv4(4, 3, 2, 1)) })
test("ParseIPRange", func() { sinkIPRange = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")) })
test("ParseIPRange", func() { sinkIPRange = MustParseIPRange("1.2.3.0-1.2.4.150") })

// IPRange methods
test("IPRange.Valid", func() { sinkBool = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")).IsValid() })
test("IPRange.IsZero", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsZero() })
test("IPRange.IsValid", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsValid() })
test("IPRange.Overlaps", func() {
a := panicIPR(ParseIPRange("1.2.3.0-1.2.3.150"))
b := panicIPR(ParseIPRange("1.2.4.0-1.2.4.255"))
a := MustParseIPRange("1.2.3.0-1.2.3.150")
b := MustParseIPRange("1.2.4.0-1.2.4.255")
sinkBool = a.Overlaps(b)
})
test("IPRange.Prefix", func() {
a := panicIPR(ParseIPRange("1.2.3.0-1.2.3.255"))
a := MustParseIPRange("1.2.3.0-1.2.3.255")
sinkIPPrefix = panicPfxOK(a.Prefix())
})
}
Expand Down Expand Up @@ -2926,3 +2941,39 @@ func TestInvalidIPPortString(t *testing.T) {
}
}
}

func TestMethodParity(t *testing.T) {
// Collect all method names for each type.
methods := make(map[string][]reflect.Type)
allTypes := []reflect.Type{
reflect.TypeOf((*IP)(nil)),
reflect.TypeOf((*IPPort)(nil)),
reflect.TypeOf((*IPPrefix)(nil)),
reflect.TypeOf((*IPRange)(nil)),
}
for _, typ := range allTypes {
for i := 0; i < typ.NumMethod(); i++ {
name := typ.Method(i).Name
methods[name] = append(methods[name], typ)
}
}

// Check whether sufficiently common methods exist on all types.
ignoreList := map[string]string{
"Valid": "method is deprecated",
}
for name, types := range methods {
if _, ignore := ignoreList[name]; ignore {
continue // method is ignored for parity check
}
if !(len(allTypes)/2 < len(types) && len(types) < len(allTypes)) {
continue // either too unique or all types already have that method
}
for _, typ := range allTypes {
if _, ok := typ.MethodByName(name); ok {
continue // this type already has this method
}
t.Errorf("%v.%v is missing", typ.Elem().Name(), name)
}
}
}

0 comments on commit 06ca814

Please sign in to comment.