[インデックス 17222] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージ内のIP
型に、encoding.TextMarshaler
およびencoding.TextUnmarshaler
インターフェースの実装を追加するものです。これにより、IP
アドレスをテキスト形式でエンコード(文字列化)およびデコード(パース)する際の標準的な方法が提供されます。
コミット
commit 413d4c6c11e23b79a26465a840bce7b0f1392425
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 00:33:20 2013 -0400
net: make IP implement encoding.MarshalerText, encoding.UnmarshalerText
See golang.org/s/go12encoding for design.
R=golang-dev, bradfitz, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/12705043
---
src/pkg/net/ip.go | 23 +++++++++++++++++++++++
src/pkg/net/ip_test.go | 15 +++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/src/pkg/net/ip.go b/src/pkg/net/ip.go
index 0e42da2168..b73804d528 100644
--- a/src/pkg/net/ip.go
+++ b/src/pkg/net/ip.go
@@ -12,6 +12,8 @@
package net
+import "errors"
+
// IP address lengths (bytes).
const (
IPv4len = 4
@@ -310,6 +312,27 @@ func (ip IP) String() string {
return s
}
+// MarshalText implements the encoding.TextMarshaler interface.
+// The encoding is the same as returned by String.\n+func (ip IP) MarshalText() ([]byte, error) {\n+\tif len(ip) != IPv4len && len(ip) != IPv6len {\n+\t\treturn nil, errors.New(\"invalid IP address\")\n+\t}\n+\treturn []byte(ip.String()), nil\n+}\n+\n+// UnmarshalText implements the encoding.TextUnmarshaler interface.\n+// The IP address is expected in a form accepted by ParseIP.\n+func (ip *IP) UnmarshalText(text []byte) error {\n+\ts := string(text)\n+\tx := ParseIP(s)\n+\tif x == nil {\n+\t\treturn &ParseError{\"IP address\", s}\n+\t}\n+\t*ip = x\n+\treturn nil\n+}\n+\n // Equal returns true if ip and x are the same IP address.\n // An IPv4 address and that same address in IPv6 form are\n // considered to be equal.\ndiff --git a/src/pkg/net/ip_test.go b/src/pkg/net/ip_test.go
index 16f30d446b..606344d265 100644
--- a/src/pkg/net/ip_test.go
+++ b/src/pkg/net/ip_test.go
@@ -32,6 +32,11 @@ func TestParseIP(t *testing.T) {\n \t\tif out := ParseIP(tt.in); !reflect.DeepEqual(out, tt.out) {\n \t\t\tt.Errorf(\"ParseIP(%q) = %v, want %v\", tt.in, out, tt.out)\n \t\t}\n+\t\tvar out IP\n+\n+\t\tif err := out.UnmarshalText([]byte(tt.in)); !reflect.DeepEqual(out, tt.out) || (tt.out == nil) != (err != nil) {\n+\t\t\tt.Errorf(\"IP.UnmarshalText(%q) = %v, %v, want %v\", tt.in, out, err, tt.out)\n+\t\t}\n \t}\n }\n \n@@ -47,6 +52,7 @@ var ipStringTests = []struct {\n \t{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0}, \"2001:db8:0:0:1::\"},\n \t{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1}, \"2001:db8::1:0:0:1\"},\n \t{IP{0x20, 0x1, 0xD, 0xB8, 0, 0, 0, 0, 0, 0xA, 0, 0xB, 0, 0xC, 0, 0xD}, \"2001:db8::a:b:c:d\"},\n+\t{IPv4(192, 168, 0, 1), \"192.168.0.1\"},\n \t{nil, \"<nil>\"},\n }\n \n@@ -55,6 +61,15 @@ func TestIPString(t *testing.T) {\n \t\tif out := tt.in.String(); out != tt.out {\n \t\t\tt.Errorf(\"IP.String(%v) = %q, want %q\", tt.in, out, tt.out)\n \t\t}\n+\t\tif tt.in != nil {\n+\t\t\tif out, err := tt.in.MarshalText(); string(out) != tt.out || err != nil {\n+\t\t\t\tt.Errorf(\"IP.MarshalText(%v) = %q, %v, want %q, nil\", out, err, tt.out)\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif _, err := tt.in.MarshalText(); err == nil {\n+\t\t\t\tt.Errorf(\"IP.MarshalText(nil) succeeded, want failure\")\n+\t\t\t}\n+\t\t}\n \t}\n }\n \n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/413d4c6c11e23b79a26465a840bce7b0f1392425](https://github.com/golang/go/commit/413d4c6c11e23b79a26465a840bce7b0f1392425)
## 元コミット内容
net: make IP implement encoding.MarshalerText, encoding.UnmarshalerText
See golang.org/s/go12encoding for design.
R=golang-dev, bradfitz, mikioh.mikioh CC=golang-dev https://golang.org/cl/12705043
## 変更の背景
このコミットの背景には、Go 1.2リリースに向けたエンコーディング関連のインターフェース設計の標準化があります。特に、構造体やカスタム型をJSON、XML、YAMLなどの様々なデータ形式にエンコード・デコードする際に、その型がどのようにテキスト表現されるべきかを定義するための統一されたメカニズムが必要とされていました。
`net.IP`型は、IPアドレスを表現するためのバイトスライス(`[]byte`)のエイリアスですが、そのテキスト表現(例: "192.168.1.1" や "2001:db8::1")は`String()`メソッドによって提供されていました。しかし、この`String()`メソッドは単なる文字列変換であり、`encoding`パッケージが提供する標準的なエンコーディング/デコーディングの枠組みには適合していませんでした。
`encoding.TextMarshaler`と`encoding.TextUnmarshaler`インターフェースは、Go 1.2で導入された新しいインターフェースであり、任意の型が自身をテキスト形式に変換する方法(マーシャリング)と、テキスト形式から自身を再構築する方法(アンマーシャリング)を定義することを可能にします。これにより、`json.Marshal`や`yaml.Marshal`などの汎用的なエンコーダが、カスタム型を適切に処理できるようになります。
`net.IP`型がこれらのインターフェースを実装することで、`IP`アドレスをフィールドに持つ構造体をJSONや他のテキストベースの形式にエンコードする際に、自動的に人間が読めるIPアドレス文字列として扱われるようになります。同様に、テキスト形式のIPアドレス文字列をデコードする際にも、`net.IP`型として正しくパースされるようになります。これは、APIの設計やデータ永続化において、`IP`アドレスをより自然に扱うための重要な改善でした。
コミットメッセージにある `golang.org/s/go12encoding` は、Go 1.2におけるエンコーディングインターフェースの設計に関する提案文書へのリンクであり、この変更がより大きな設計思想の一部であることを示しています。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とネットワークに関する基礎知識が必要です。
### 1. Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。ある型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たすと見なされます。Goのインターフェースは「暗黙的」であり、`implements`キーワードのような明示的な宣言は不要です。
このコミットでは、`encoding.TextMarshaler`と`encoding.TextUnmarshaler`という2つのインターフェースが重要です。
* **`encoding.TextMarshaler`インターフェース**:
```go
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
```
このインターフェースを実装する型は、自身をテキスト形式のバイトスライスに変換する方法を提供します。`json.Marshal`などのエンコーダは、このインターフェースを実装している型を見つけると、`MarshalText`メソッドを呼び出してテキスト表現を取得します。
* **`encoding.TextUnmarshaler`インターフェース**:
```go
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
```
このインターフェースを実装する型は、テキスト形式のバイトスライスから自身を再構築する方法を提供します。`json.Unmarshal`などのデコーダは、このインターフェースを実装している型を見つけると、`UnmarshalText`メソッドを呼び出してテキストをパースし、型を初期化します。
### 2. `net.IP`型
Go言語の`net`パッケージは、ネットワーク関連のプリミティブを提供します。`net.IP`型は、IPアドレス(IPv4またはIPv6)を表すために使用されるバイトスライスです。
```go
type IP []byte
-
IP.String()
メソッド:net.IP
型には、IPアドレスを人間が読める文字列形式(例: "192.168.1.1" や "2001:db8::1")に変換するString()
メソッドが既に存在します。これはfmt.Stringer
インターフェースを暗黙的に満たします。 -
net.ParseIP()
関数: 文字列形式のIPアドレスをnet.IP
型にパースする関数です。無効なIPアドレス文字列が与えられた場合はnil
を返します。
3. IPアドレスの基礎
- IPv4: 32ビットのアドレス空間を持ち、通常はドット区切りの10進数(例:
192.168.1.1
)で表現されます。net.IPv4len
は4バイト(32ビット)です。 - IPv6: 128ビットのアドレス空間を持ち、通常はコロン区切りの16進数(例:
2001:db8::1
)で表現されます。net.IPv6len
は16バイト(128ビット)です。
4. エラーハンドリング
Go言語では、エラーは関数の最後の戻り値としてerror
インターフェースを実装する型で返されます。このコミットでは、errors.New
を使用して新しいエラーを作成したり、ParseError
というカスタムエラー型を使用したりしています。
技術的詳細
このコミットの技術的詳細は、net.IP
型がencoding.TextMarshaler
とencoding.TextUnmarshaler
インターフェースをどのように実装しているかに集約されます。
MarshalText()
の実装
MarshalText()
メソッドは、net.IP
型の値をテキスト形式のバイトスライスに変換します。
-
シグネチャ:
func (ip IP) MarshalText() ([]byte, error)
- レシーバは値レシーバ(
ip IP
)です。これは、MarshalText
がIP
の値を変更しないためです。 - 戻り値は
[]byte
(テキスト表現)とerror
です。
- レシーバは値レシーバ(
-
IPアドレスの長さチェック:
if len(ip) != IPv4len && len(ip) != IPv6len { return nil, errors.New("invalid IP address") }
net.IP
はバイトスライスであるため、その長さがIPv4(4バイト)またはIPv6(16バイト)のいずれでもない場合、それは無効なIPアドレスと見なされ、エラーを返します。これは、不正なIP
値がマーシャリングされるのを防ぐための堅牢性チェックです。 -
String()
メソッドの利用:return []byte(ip.String()), nil
有効なIPアドレスの場合、既存の
ip.String()
メソッドを呼び出してIPアドレスの標準的なテキスト表現を取得し、それをバイトスライスに変換して返します。これにより、MarshalText
の出力形式がString()
と同じであることが保証されます。
UnmarshalText()
の実装
UnmarshalText()
メソッドは、テキスト形式のバイトスライスからnet.IP
型の値を再構築します。
-
シグネチャ:
func (ip *IP) UnmarshalText(text []byte) error
- レシーバはポインタレシーバ(
ip *IP
)です。これは、UnmarshalText
がレシーバの値を変更する必要があるためです。インターフェースの規約により、アンマーシャリングメソッドはポインタレシーバを持つ必要があります。 - 引数は
text []byte
(入力テキスト)です。 - 戻り値は
error
です。
- レシーバはポインタレシーバ(
-
バイトスライスから文字列への変換:
s := string(text)
入力されたバイトスライス
text
をGoの文字列s
に変換します。これは、既存のParseIP
関数が文字列を引数として取るためです。 -
ParseIP()
関数の利用:x := ParseIP(s)
変換された文字列
s
をnet.ParseIP()
関数に渡して、IPアドレスをパースします。ParseIP
は有効なIPアドレスであればnet.IP
型の値を返し、無効であればnil
を返します。 -
パース結果のチェックとエラーハンドリング:
if x == nil { return &ParseError{"IP address", s} }
ParseIP
がnil
を返した場合(つまり、入力テキストが無効なIPアドレス形式であった場合)、ParseError
というカスタムエラー型を返します。これは、パースに失敗したことを明確に示します。 -
レシーバへの値の代入:
*ip = x
パースが成功した場合、
ParseIP
が返したnet.IP
型の値x
を、ポインタレシーバip
が指す元のIP
変数に代入します。これにより、呼び出し元のIP
変数が更新されます。
テストの追加
src/pkg/net/ip_test.go
には、これらの新しいメソッドの動作を検証するためのテストケースが追加されています。
TestParseIP
関数内では、既存のParseIP
のテストループにUnmarshalText
のテストが追加されています。これにより、UnmarshalText
がParseIP
と同じ結果を生成し、エラーハンドリングも適切に行われることが確認されます。TestIPString
関数内では、既存のIP.String()
のテストループにMarshalText
のテストが追加されています。これにより、MarshalText
がIP.String()
と同じ文字列を生成し、無効なIP
値(nil
)に対してはエラーを返すことが確認されます。
これらのテストは、新しいインターフェース実装が期待通りに機能し、既存のString()
やParseIP()
との整合性が保たれていることを保証します。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルに集中しています。
-
src/pkg/net/ip.go
:import "errors"
が追加されました。これはMarshalText
メソッド内でerrors.New
を使用するためです。IP
型にMarshalText()
メソッドが追加されました。IP
型にUnmarshalText()
メソッドが追加されました。
-
src/pkg/net/ip_test.go
:TestParseIP
関数内にIP.UnmarshalText
のテストケースが追加されました。ipStringTests
変数に{IPv4(192, 168, 0, 1), "192.168.0.1"}
というテストデータが追加されました。これは、MarshalText
のテストでIPv4アドレスのケースをカバーするためです。TestIPString
関数内にIP.MarshalText
のテストケースが追加されました。
コアとなるコードの解説
src/pkg/net/ip.go
package net
import "errors" // errorsパッケージのインポート
// ... (既存のコード) ...
// MarshalText implements the encoding.TextMarshaler interface.
// The encoding is the same as returned by String.
func (ip IP) MarshalText() ([]byte, error) {
// IPアドレスのバイト長がIPv4(4バイト)でもIPv6(16バイト)でもない場合、
// 無効なIPアドレスとしてエラーを返す。
if len(ip) != IPv4len && len(ip) != IPv6len {
return nil, errors.New("invalid IP address")
}
// 有効なIPアドレスの場合、既存のString()メソッドで文字列化し、
// それをバイトスライスに変換して返す。エラーはnil。
return []byte(ip.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The IP address is expected in a form accepted by ParseIP.
func (ip *IP) UnmarshalText(text []byte) error {
// 入力されたバイトスライスを文字列に変換。
s := string(text)
// net.ParseIP関数を使って文字列をIPアドレスにパース。
x := ParseIP(s)
// パース結果がnil(無効なIPアドレス文字列)の場合、
// ParseErrorを返してエラーを通知。
if x == nil {
return &ParseError{"IP address", s}
}
// パースが成功した場合、パースされたIPアドレスをレシーバのポインタが指す変数に代入。
*ip = x
return nil
}
// ... (既存のコード) ...
src/pkg/net/ip_test.go
package net
import (
"reflect" // reflectパッケージのインポート(DeepEqualのため)
"testing" // testingパッケージのインポート
)
// ... (既存のテストコード) ...
func TestParseIP(t *testing.T) {
// ... (既存のテストループ) ...
// UnmarshalTextのテストを追加
// 各テストケースの入力文字列 (tt.in) を使ってUnmarshalTextを呼び出す
var out IP // UnmarshalTextの出力先となるIP変数
// UnmarshalTextを呼び出し、その結果 (out) が期待値 (tt.out) と一致するか、
// およびエラーの有無が期待通りかを確認する。
// (tt.out == nil) != (err != nil) は、期待値がnilならエラーがあるべき、
// 期待値がnilでないならエラーがないべき、という条件。
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)
}
// ... (既存のテストループの続き) ...
}
// ... (既存のテストコード) ...
var ipStringTests = []struct {
in IP
out string
}{
// ... (既存のテストデータ) ...
// IPv4アドレスのテストケースを追加
{IPv4(192, 168, 0, 1), "192.168.0.1"},
{nil, "<nil>"},
}
func TestIPString(t *testing.T) {
// ... (既存のテストループ) ...
// MarshalTextのテストを追加
if tt.in != nil { // 入力IPがnilでない場合
// MarshalTextを呼び出し、その出力 (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)
}
} else { // 入力IPがnilの場合
// MarshalTextを呼び出し、エラーが発生することを確認する。
if _, err := tt.in.MarshalText(); err == nil {
t.Errorf("IP.MarshalText(nil) succeeded, want failure")
}
}
// ... (既存のテストループの続き) ...
}
// ... (既存のテストコード) ...
関連リンク
- Go言語の
encoding
パッケージドキュメント: https://pkg.go.dev/encoding encoding.TextMarshaler
インターフェース: https://pkg.go.dev/encoding#TextMarshalerencoding.TextUnmarshaler
インターフェース: https://pkg.go.dev/encoding#TextUnmarshaler- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net net.IP
型: https://pkg.go.dev/net#IPnet.ParseIP
関数: https://pkg.go.dev/net#ParseIP
参考にした情報源リンク
- Go 1.2 Release Notes (Encoding): https://go.dev/doc/go1.2#encoding
- Go 1.2 Encoding Design Document (golang.org/s/go12encoding): https://go.dev/s/go12encoding (これはコミットメッセージに記載されているリンクであり、Go 1.2のエンコーディングインターフェース設計に関する詳細な背景情報を提供します。)
- Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/9
- Go言語のエラーハンドリングに関する公式ドキュメント: https://go.dev/blog/error-handling-and-go
- IPアドレスの基礎知識 (Wikipediaなど): https://ja.wikipedia.org/wiki/IP%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9