Repository: inetaf/netaddr (go)

Issue:
proposal: add method IP.IPAddr for adapting into existing code
I am certainly a fan of the ideas being put forth by this package, but there is a massive amount of preexisting code which expects to work with net.IP/net.IPAddr exclusively.

I'd propose a method as follows:

```go
// IP returns the net.IPAddr representation of an IP. If the IP contains a zone
// identifier, the Zone field of the returned IPAddr is populated.
func (ip IP) IPAddr() *net.IPAddr
```

From there, the caller can either use the net.IPAddr directly or extract the IP address for use with APIs which expect net.IP.

I was considering proposing `IP() net.IP` instead, but due to the inclusion of the zone identifier in this package, I think this is a fair compromise.

Pull request description:
netaddr: implement IP.IPAddr for interop with existing code
Signed-off-by: Matt Layher <mdlayher@gmail.com>

Thoughts welcome on the "what to do with nil" TODO. I am tempted to panic to avoid an error return and also a potential nil pointer dereference in calling code. But I'm open to all suggestions.

Fixes #2.

Patch:
diff --git a/netaddr.go b/netaddr.go
index 136a02f..cb87af3 100644
--- a/netaddr.go
+++ b/netaddr.go
@@ -86,6 +86,37 @@ func ParseIP(s string) (IP, error) {
 	return IP{v6}, nil
 }
 
+// IPAddr returns the net.IPAddr representation of an IP. The returned value is
+// always non-nil, but the IPAddr.IP will be nil if ip is the zero value.
+// If ip contains a zone identifier, IPAddr.Zone is populated.
+func (ip IP) IPAddr() *net.IPAddr {
+	switch ip := ip.ipImpl.(type) {
+	case nil:
+		// Nothing to do.
+		return &net.IPAddr{}
+	case v4Addr:
+		return &net.IPAddr{
+			// Assume the caller wants the 4-byte representation.
+			IP: net.IPv4(ip[0], ip[1], ip[2], ip[3]).To4(),
+		}
+	case v6Addr:
+		b := make(net.IP, net.IPv6len)
+		copy(b, ip[:])
+
+		return &net.IPAddr{IP: b}
+	case v6AddrZone:
+		b := make(net.IP, net.IPv6len)
+		copy(b, ip.v6Addr[:])
+
+		return &net.IPAddr{
+			IP:   b,
+			Zone: ip.zone,
+		}
+	default:
+		panic("netaddr: unhandled ipImpl representation")
+	}
+}
+
 // Is4 reports whether ip is an IPv4 address.
 //
 // TODO: decide/clarify the behavior for IPv4-mapped IPv6 addresses.


Test patch:
diff --git a/netaddr_test.go b/netaddr_test.go
index 0356bd2..95b9342 100644
--- a/netaddr_test.go
+++ b/netaddr_test.go
@@ -4,7 +4,11 @@
 
 package netaddr
 
-import "testing"
+import (
+	"net"
+	"reflect"
+	"testing"
+)
 
 func TestParseString(t *testing.T) {
 	tests := []string{
@@ -34,3 +38,56 @@ func TestParseString(t *testing.T) {
 		})
 	}
 }
+
+func TestIPIPAddr(t *testing.T) {
+	tests := []struct {
+		name string
+		ip   IP
+		ipa  *net.IPAddr
+	}{
+		{
+			name: "nil",
+			ipa:  &net.IPAddr{},
+		},
+		{
+			name: "v4Addr",
+			ip:   mustIP("192.0.2.1"),
+			ipa: &net.IPAddr{
+				IP: net.IPv4(192, 0, 2, 1).To4(),
+			},
+		},
+		{
+			name: "v6Addr",
+			ip:   mustIP("2001:db8::1"),
+			ipa: &net.IPAddr{
+				IP: net.ParseIP("2001:db8::1"),
+			},
+		},
+		{
+			name: "v6AddrZone",
+			ip:   mustIP("fe80::1%eth0"),
+			ipa: &net.IPAddr{
+				IP:   net.ParseIP("fe80::1"),
+				Zone: "eth0",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.ip.IPAddr()
+			if !reflect.DeepEqual(tt.ipa, got) {
+				t.Errorf("IPAddr = %+v; want %+v", got, tt.ipa)
+			}
+		})
+	}
+}
+
+func mustIP(s string) IP {
+	ip, err := ParseIP(s)
+	if err != nil {
+		panic(err)
+	}
+
+	return ip
+}