[インデックス 17499] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおけるIPアドレスのマーシャリング(バイト列への変換)とアンマーシャリング(バイト列からの復元)に関する挙動の修正です。具体的には、nil
のIPアドレスをencoding.TextMarshaler
インターフェースを通じてマーシャリングする際にエラーを返さないように変更し、また空のバイト列をアンマーシャリングする際にnil
のIPアドレスとして正しく処理するように修正しています。これにより、net.IP
型のnil
値の扱いが一貫性を持つようになり、特にJSONなどのテキストベースのフォーマットでIPアドレスを扱う際の利便性と堅牢性が向上しました。
コミット
commit da7a51d16be93ad91cfbc2d90c06d55bd847aa98
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Sep 6 15:29:09 2013 -0700
net: don't error when marshalling nil IP addresses
See https://code.google.com/p/go/issues/detail?id=6339#c3
Fixes #6339
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/13553044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da7a51d16be93ad91cfbc2d90c06d55bd847aa98
元コミット内容
net: don't error when marshalling nil IP addresses
このコミットは、nil
のIPアドレスをマーシャリングする際にエラーを発生させないようにするものです。これは、GoのIssue 6339に関連する修正であり、https://code.google.com/p/go/issues/detail?id=6339#c3
で議論された内容に基づいています。
変更の背景
Go言語のnet
パッケージにおけるIP
型は、IPアドレスを表すバイトスライスです。この型は、encoding.TextMarshaler
およびencoding.TextUnmarshaler
インターフェースを実装しており、これによりJSONなどのテキストベースのデータフォーマットとの間でIPアドレスを簡単に変換できます。
このコミット以前は、net.IP
型の値がnil
(つまり、IPアドレスが設定されていない状態)である場合にMarshalText
メソッドを呼び出すと、「invalid IP address」というエラーが返されていました。これは、IPアドレスが存在しない状態を表現したい場合に不便であり、特にデータベースのNULL値や、オプションのIPアドレスフィールドを扱う際に問題となりました。
GoのIssue 6339では、この挙動が議論され、nil
のIPアドレスは空文字列としてマーシャリングされるべきであり、空文字列はnil
のIPアドレスとしてアンマーシャリングされるべきであるという合意が形成されました。この変更は、nil
のIPアドレスをエラーとして扱わず、空の表現として扱うことで、より柔軟で直感的なAPIを提供することを目的としています。
前提知識の解説
net.IP
型
net.IP
はGo言語のnet
パッケージで定義されている型で、IPアドレス(IPv4またはIPv6)を表すバイトスライスです。例えば、IPv4アドレスは4バイト、IPv6アドレスは16バイトで表現されます。net.IP
型の変数は、IPアドレスが設定されていない状態をnil
で表現できます。
encoding.TextMarshaler
インターフェース
encoding.TextMarshaler
インターフェースは、Goの標準ライブラリencoding
パッケージで定義されています。このインターフェースは、型が自身をテキスト形式にマーシャリングする方法を定義するために使用されます。
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
このインターフェースを実装する型は、json.Marshal
などの関数によってJSONにエンコードされる際に、MarshalText
メソッドが呼び出され、その結果がJSON文字列として出力されます。
encoding.TextUnmarshaler
インターフェース
encoding.TextUnmarshaler
インターフェースもencoding
パッケージで定義されており、型が自身をテキスト形式からアンマーシャリングする方法を定義します。
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
このインターフェースを実装する型は、json.Unmarshal
などの関数によってJSONからデコードされる際に、UnmarshalText
メソッドが呼び出され、入力されたテキストデータがその型の値に変換されます。
nil
と空スライス
Goにおいて、nil
スライスと空スライス([]byte{}
やmake([]byte, 0)
)は異なる概念です。
nil
スライスは、基底配列へのポインタがnil
であり、長さと容量が0です。- 空スライスは、基底配列が存在し、長さと容量が0です。
このコミットでは、net.IP
がnil
の場合と、空のバイト列(""
)として表現される場合の両方を適切に扱うように変更されています。
技術的詳細
このコミットの主要な変更点は、src/pkg/net/ip.go
内のIP.MarshalText
メソッドとIP.UnmarshalText
メソッドの挙動修正です。
IP.MarshalText
の変更
変更前は、IP.MarshalText
メソッドはlen(ip) != IPv4len && len(ip) != IPv6len
という条件でIPアドレスの長さが不正な場合にエラーを返していました。この条件はlen(ip) == 0
(つまりnil
のIPアドレス)の場合も含まれていました。
変更後は、if len(ip) == 0 { return []byte(""), nil }
という行が追加されました。これにより、nil
のIPアドレスが渡された場合、エラーを返さずに空のバイトスライス(""
)とnil
エラーを返すようになりました。これは、nil
のIPアドレスを空文字列として表現するという意図を反映しています。
IP.UnmarshalText
の変更
変更前は、IP.UnmarshalText
メソッドは空のテキストが渡された場合にParseIP
がnil
を返し、その結果x == nil
の条件に合致して「invalid IP address」エラーを返していました。
変更後は、if len(text) == 0 { *ip = nil; return nil }
という行が追加されました。これにより、空のバイトスライス(""
)が渡された場合、エラーを返さずに*ip = nil
としてIPアドレスをnil
に設定し、nil
エラーを返すようになりました。これは、空文字列をnil
のIPアドレスとして解釈するという意図を反映しています。
テストケースの追加と修正
src/pkg/net/ip_test.go
には、これらの変更を検証するための新しいテストケースTestMarshalEmptyIP
が追加されました。このテストは、nil
または空のバイトスライスをUnmarshalText
に渡した場合にnil
のIPアドレスが正しく設定されること、およびnil
のIPアドレスをMarshalText
でマーシャリングした場合に空のバイトスライスが返されることを確認します。
また、既存のTestIPString
テストでは、nil
のIPアドレスのString()
メソッドの期待値が"<nil>"
から""
に変更され、MarshalText
のテストロジックもnil
の場合にエラーを期待するのではなく、空文字列を期待するように修正されました。
コアとなるコードの変更箇所
src/pkg/net/ip.go
--- a/src/pkg/net/ip.go
+++ b/src/pkg/net/ip.go
@@ -315,6 +315,9 @@ func (ip IP) String() string {
// MarshalText implements the encoding.TextMarshaler interface.
// The encoding is the same as returned by String.
func (ip IP) MarshalText() ([]byte, error) {
+ if len(ip) == 0 {
+ return []byte(""), nil
+ }
if len(ip) != IPv4len && len(ip) != IPv6len {
return nil, errors.New("invalid IP address")
}
@@ -324,6 +327,10 @@ func (ip IP) MarshalText() ([]byte, error) {
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The IP address is expected in a form accepted by ParseIP.
func (ip *IP) UnmarshalText(text []byte) error {
+ if len(text) == 0 {
+ *ip = nil
+ return nil
+ }
s := string(text)
x := ParseIP(s)
if x == nil {
src/pkg/net/ip_test.go
--- a/src/pkg/net/ip_test.go
+++ b/src/pkg/net/ip_test.go
@@ -32,14 +32,35 @@ func TestParseIP(t *testing.T) {
if out := ParseIP(tt.in); !reflect.DeepEqual(out, tt.out) {
t.Errorf("ParseIP(%q) = %v, want %v", tt.in, out, tt.out)
}
+ if tt.in == "" {
+ // Tested in TestMarshalEmptyIP below.
+ continue
+ }
var out IP
-
if err := out.UnmarshalText([]byte(tt.in)); !reflect.DeepEqual(out, tt.out) || (tt.out == nil) != (err != nil) {
t.Errorf("IP.UnmarshalText(%q) = %v, %v, want %v", tt.in, out, err, tt.out)
}
}
}
+// Issue 6339
+func TestMarshalEmptyIP(t *testing.T) {
+ for _, in := range [][]byte{nil, []byte("")} {
+ var out = IP{1, 2, 3, 4}
+ if err := out.UnmarshalText(in); err != nil || out != nil {
+ t.Errorf("UnmarshalText(%v) = %v, %v; want nil, nil", in, out, err)
+ }
+ }
+ var ip IP
+ got, err := ip.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(got, []byte("")) {
+ t.Errorf(`got %#v, want []byte("")`, got)
+ }
+}
+
var ipStringTests = []struct {
in IP
out string // see RFC 5952
@@ -53,23 +74,19 @@ var ipStringTests = []struct {
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1}, "2001:db8::1:0:0:1"},
{IP{0x20, 0x1, 0xD, 0xB8, 0, 0, 0, 0, 0, 0xA, 0, 0xB, 0, 0xC, 0, 0xD}, "2001:db8::a:b:c:d"},
{IPv4(192, 168, 0, 1), "192.168.0.1"},
- {nil, "<nil>"},
+ {nil, ""},
}
func TestIPString(t *testing.T) {
for _, tt := range ipStringTests {
- if out := tt.in.String(); out != tt.out {
- t.Errorf("IP.String(%v) = %q, want %q", tt.in, out, tt.out)
- }
if tt.in != nil {
- if out, err := tt.in.MarshalText(); string(out) != tt.out || err != nil {
- t.Errorf("IP.MarshalText(%v) = %q, %v, want %q, nil", out, err, tt.out)
- }
- } else {
- if _, err := tt.in.MarshalText(); err == nil {
- t.Errorf("IP.MarshalText(nil) succeeded, want failure")
+ if out := tt.in.String(); out != tt.out {
+ t.Errorf("IP.String(%v) = %q, want %q", tt.in, out, tt.out)
}
}
+ if out, err := tt.in.MarshalText(); string(out) != tt.out || err != nil {
+ t.Errorf("IP.MarshalText(%v) = %q, %v, want %q, nil", out, err, tt.out)
+ }
}
}
コアとなるコードの解説
src/pkg/net/ip.go
の変更
-
func (ip IP) MarshalText() ([]byte, error)
:- 追加された
if len(ip) == 0 { return []byte(""), nil }
は、ip
スライスの長さが0の場合(つまり、nil
または空スライスの場合)に、空のバイトスライスとnil
エラーを返します。これにより、nil
のIPアドレスがJSONなどでは空文字列として表現されるようになります。 - この変更により、以前は
len(ip) != IPv4len && len(ip) != IPv6len
の条件でnil
のIPアドレスが「invalid IP address」エラーを返していた挙動が修正されました。
- 追加された
-
func (ip *IP) UnmarshalText(text []byte) error
:- 追加された
if len(text) == 0 { *ip = nil; return nil }
は、入力text
が空のバイトスライスの場合に、*ip
をnil
に設定し、nil
エラーを返します。これにより、空文字列がnil
のIPアドレスとして正しくアンマーシャリングされるようになります。 - 以前は、空文字列が
ParseIP
に渡されるとnil
が返され、その結果「invalid IP address」エラーが発生していました。この変更により、その問題が解決されました。
- 追加された
src/pkg/net/ip_test.go
の変更
-
func TestMarshalEmptyIP(t *testing.T)
:- この新しいテスト関数は、
nil
または空のバイトスライスをUnmarshalText
に渡したときに、IP
型の値が正しくnil
になることを検証します。 - また、
nil
のIP
型の値をMarshalText
でマーシャリングしたときに、空のバイトスライスが返されることを検証します。これは、新しい挙動が期待通りであることを確認するための重要なテストです。
- この新しいテスト関数は、
-
var ipStringTests
の修正:{nil, "<nil>"}
が{nil, ""}
に変更されました。これは、nil
のIPアドレスのString()
メソッドが空文字列を返すように変更されたためです。
-
func TestIPString(t *testing.T)
の修正:nil
のIPアドレスに対するMarshalText
のテストロジックが変更されました。以前はnil
の場合にエラーを期待していましたが、変更後は空文字列とnil
エラーを期待するように修正されました。IP.String()
のテストロジックも、nil
の場合の期待値が""
になるように調整されました。
これらの変更により、net.IP
型がnil
である場合のマーシャリングとアンマーシャリングの挙動が明確になり、encoding.TextMarshaler
およびencoding.TextUnmarshaler
インターフェースの実装がより堅牢で使いやすくなりました。
関連リンク
- Go Code Review: https://golang.org/cl/13553044
- Go Issue 6339 (元のリンクは現在アクセスできませんが、コミットメッセージに記載されています):
https://code.google.com/p/go/issues/detail?id=6339#c3
参考にした情報源リンク
- Go言語の公式ドキュメント(
net
パッケージ、encoding
パッケージ) - Go言語のソースコード(
src/pkg/net/ip.go
,src/pkg/net/ip_test.go
) - Go言語のIssueトラッカー(Issue 6339に関する議論)
encoding.TextMarshaler
およびencoding.TextUnmarshaler
インターフェースに関する一般的なGo言語のドキュメントや解説記事。