diff --git a/ip.go b/ip.go index 06b8bcb5..4b767479 100644 --- a/ip.go +++ b/ip.go @@ -32,6 +32,14 @@ func (i *ipValue) Type() string { } func ipConv(sval string) (interface{}, error) { + // An unset IP flag with a nil default round-trips through String() as + // "", because that's what net.IP.String returns for a nil + // receiver. Treat that (and the empty string, to match how Set treats + // empty input) as "no IP" so GetIP doesn't error on + // IP("ip", nil, ...). See #351. + if sval == "" || sval == "" { + return net.IP(nil), nil + } ip := net.ParseIP(sval) if ip != nil { return ip, nil diff --git a/ip_test.go b/ip_test.go index 7a5da106..1c1ceb8a 100644 --- a/ip_test.go +++ b/ip_test.go @@ -61,3 +61,31 @@ func TestIP(t *testing.T) { } } } + +// TestIPNilDefault covers #351: declaring an IP flag with a nil default and +// then reading it without setting it must return (nil, nil), not an error. +// Before the ipConv guard, the unset flag's String() returned "" and +// GetIP tried to parse it as an address. +func TestIPNilDefault(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + f.IP("ip", nil, "") + + ip, err := f.GetIP("ip") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if ip != nil { + t.Errorf("expected nil IP, got %v", ip) + } + + var bound net.IP + f2 := NewFlagSet("test2", ContinueOnError) + f2.IPVar(&bound, "ip", nil, "") + ip, err = f2.GetIP("ip") + if err != nil { + t.Fatalf("unexpected error (IPVar variant): %v", err) + } + if ip != nil { + t.Errorf("expected nil IP (IPVar variant), got %v", ip) + } +}