Protocol BuffersのOptionをGo側で読み取る方法を考える

## はじめに

[Protocol Buffers](https://github.com/protocolbuffers/protobuf)では、Enumを宣言してGoのコードを生成すると、基本的には `string` ↔ `int32` の対応関係が生成される。しかし、この `string` に使える文字種は、[Specification](https://protobuf.com/docs/language-spec#enums)によると、 `[A-Za-z_]` の範囲に限られる。そのため、これ以外の文字種を使いたい場合は、他の方法で表現する必要がある[^1]。

[^1]: 例えば、認可グラントをEnumで表現したい場合、`urn:ietf:params:oauth:grant-type:jwt-bearer` のような値を設定することはできない。

変換ロジックを外部に置くことも出来るが、その場合は各言語で実装することになるため、対応関係に差異が生じる危険性がある。そこで、オプショナルな値をProtocol Buffers側で宣言し、各実装でオプションの値を取得する方法を本記事では紹介する。

## 実際の例を考える

具体例が理解の助けになると思うので、色を表すEnum定義と、定義に該当するカラーコードを持たせる例について考える。本記事で利用するコードはリポジトリで公開しているので、必要に応じて参照して欲しい。

https://github.com/task4233/get-proto-optional-value

さて、[Specification](https://protobuf.com/docs/language-spec#resolving-option-names)によると、Optionを設定するためにはEnumValueにEnumValueOptionsを持たせれば良いので、次のようにProtocol Buffersを定義する。

```protobuf
// EnumValueOptionsを拡張
extend google.protobuf.EnumValueOptions {
  optional ColorOptions color_options = 50001;
}

// Enum用のOption値
message ColorOptions {
  string color_code = 1;
}

// 色を表すEnumと、そのオプション定義
enum Color {
  WHITE = 0 [ (color_options) = {color_code: "#FFFFFF" } ];
  BLACK = 1 [ (color_options) = {color_code: "#000000" } ];
}
```

このコードを用意して生成されるコードは次の通りだ[^2]。

[^2]: protoc-gen-goはv1.32.0、protocはv4.23.4を利用した。

<details>
<summary>color.pb.go</summary>
    
```go
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.32.0
// 	protoc        v4.23.4
// source: proto/color.proto

package generated

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	descriptorpb "google.golang.org/protobuf/types/descriptorpb"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type Color int32

const (
	Color_WHITE Color = 0
	Color_BLACK Color = 1
)

// Enum value maps for Color.
var (
	Color_name = map[int32]string{
		0: "WHITE",
		1: "BLACK",
	}
	Color_value = map[string]int32{
		"WHITE": 0,
		"BLACK": 1,
	}
)

func (x Color) Enum() *Color {
	p := new(Color)
	*p = x
	return p
}

func (x Color) String() string {
	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}

func (Color) Descriptor() protoreflect.EnumDescriptor {
	return file_proto_color_proto_enumTypes[0].Descriptor()
}

func (Color) Type() protoreflect.EnumType {
	return &file_proto_color_proto_enumTypes[0]
}

func (x Color) Number() protoreflect.EnumNumber {
	return protoreflect.EnumNumber(x)
}

// Deprecated: Use Color.Descriptor instead.
func (Color) EnumDescriptor() ([]byte, []int) {
	return file_proto_color_proto_rawDescGZIP(), []int{0}
}

type ColorOptions struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	ColorCode string `protobuf:"bytes,1,opt,name=color_code,json=colorCode,proto3" json:"color_code,omitempty"`
}

func (x *ColorOptions) Reset() {
	*x = ColorOptions{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_color_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *ColorOptions) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*ColorOptions) ProtoMessage() {}

func (x *ColorOptions) ProtoReflect() protoreflect.Message {
	mi := &file_proto_color_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use ColorOptions.ProtoReflect.Descriptor instead.
func (*ColorOptions) Descriptor() ([]byte, []int) {
	return file_proto_color_proto_rawDescGZIP(), []int{0}
}

func (x *ColorOptions) GetColorCode() string {
	if x != nil {
		return x.ColorCode
	}
	return ""
}

var file_proto_color_proto_extTypes = []protoimpl.ExtensionInfo{
	{
		ExtendedType:  (*descriptorpb.EnumValueOptions)(nil),
		ExtensionType: (*ColorOptions)(nil),
		Field:         50001,
		Name:          "sample.color_options",
		Tag:           "bytes,50001,opt,name=color_options",
		Filename:      "proto/color.proto",
	},
}

// Extension fields to descriptorpb.EnumValueOptions.
var (
	// optional sample.ColorOptions color_options = 50001;
	E_ColorOptions = &file_proto_color_proto_extTypes[0]
)

var File_proto_color_proto protoreflect.FileDescriptor

var file_proto_color_proto_rawDesc = []byte{
	0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x70, 0x72,
	0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x1a, 0x20, 0x67, 0x6f, 0x6f,
	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73,
	0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a,
	0x0c, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a,
	0x0a, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x09, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x3b, 0x0a, 0x05,
	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x05, 0x57, 0x48, 0x49, 0x54, 0x45, 0x10, 0x00,
	0x1a, 0x0d, 0x8a, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x23, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x12,
	0x18, 0x0a, 0x05, 0x42, 0x4c, 0x41, 0x43, 0x4b, 0x10, 0x01, 0x1a, 0x0d, 0x8a, 0xb5, 0x18, 0x09,
	0x0a, 0x07, 0x23, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3a, 0x61, 0x0a, 0x0d, 0x63, 0x6f, 0x6c,
	0x6f, 0x72, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f,
	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75,
	0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd1, 0x86,
	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x43,
	0x6f, 0x6c, 0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0c, 0x63, 0x6f, 0x6c,
	0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x5a, 0x0c,
	0x2e, 0x2e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72,
	0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_proto_color_proto_rawDescOnce sync.Once
	file_proto_color_proto_rawDescData = file_proto_color_proto_rawDesc
)

func file_proto_color_proto_rawDescGZIP() []byte {
	file_proto_color_proto_rawDescOnce.Do(func() {
		file_proto_color_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_color_proto_rawDescData)
	})
	return file_proto_color_proto_rawDescData
}

var file_proto_color_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proto_color_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proto_color_proto_goTypes = []interface{}{
	(Color)(0),                            // 0: sample.Color
	(*ColorOptions)(nil),                  // 1: sample.ColorOptions
	(*descriptorpb.EnumValueOptions)(nil), // 2: google.protobuf.EnumValueOptions
}
var file_proto_color_proto_depIdxs = []int32{
	2, // 0: sample.color_options:extendee -> google.protobuf.EnumValueOptions
	1, // 1: sample.color_options:type_name -> sample.ColorOptions
	2, // [2:2] is the sub-list for method output_type
	2, // [2:2] is the sub-list for method input_type
	1, // [1:2] is the sub-list for extension type_name
	0, // [0:1] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_proto_color_proto_init() }
func file_proto_color_proto_init() {
	if File_proto_color_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_proto_color_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*ColorOptions); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_proto_color_proto_rawDesc,
			NumEnums:      1,
			NumMessages:   1,
			NumExtensions: 1,
			NumServices:   0,
		},
		GoTypes:           file_proto_color_proto_goTypes,
		DependencyIndexes: file_proto_color_proto_depIdxs,
		EnumInfos:         file_proto_color_proto_enumTypes,
		MessageInfos:      file_proto_color_proto_msgTypes,
		ExtensionInfos:    file_proto_color_proto_extTypes,
	}.Build()
	File_proto_color_proto = out.File
	file_proto_color_proto_rawDesc = nil
	file_proto_color_proto_goTypes = nil
	file_proto_color_proto_depIdxs = nil
}
```
</details>

    

結論を先に書くと、次のように取得できる。

```go
package main

import (
	"log"

	"gihtub.com/task4233/protoc/generated"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/descriptorpb"
)

func GetOption(x generated.Color) *generated.ColorOptions {
	return proto.GetExtension(
		x.Descriptor().Values().Get(int(x.Number())).Options().(*descriptorpb.EnumValueOptions),
		generated.E_ColorOptions,
	).(*generated.ColorOptions)
}

func main() {
	log.Printf("optional_value: %s", GetOption(generated.Color_BLACK).GetColorCode())
}
```

とはいえ、内部の仕組みについて知りたい方もいると思うので、順を追って書く。

## Extensionへのアクセス

protoのGoDocを見ると、Extensionについて次のように言及されている。

> Extension accessors [¶](https://pkg.go.dev/google.golang.org/protobuf/proto#hdr-Extension_accessors)
> 
> - [HasExtension](https://pkg.go.dev/google.golang.org/protobuf/proto#HasExtension), [GetExtension](https://pkg.go.dev/google.golang.org/protobuf/proto#GetExtension), [SetExtension](https://pkg.go.dev/google.golang.org/protobuf/proto#SetExtension), and [ClearExtension](https://pkg.go.dev/google.golang.org/protobuf/proto#ClearExtension)
> access extension field values in a protocol buffer message.
> 
> Extension fields are only supported in proto2.
> 

proto2でしかサポートされていないらしいが、ひとまず[GetExtension関数](https://pkg.go.dev/google.golang.org/protobuf/proto#GetExtension)で取得できそうだ。GetExtension関数の定義を見ると、 `proto.Message` すなわち `protoreflect.ProtoMessage` インタフェースを満たしている引数と、 `protoreflect.ExtensionType` インタフェースを満たしている引数を必要とする。したがって、これらの値をどのように取得するか考えれば良い。

まず、 `protore.Message` は欲しいExtensionのMessage情報を取得すれば良いので、EnumValueのOptionsを利用する。この取得方法は後述する。

次に、 `protoreflect.ExtensionType` は欲しいExtensionのTypeを取得すれば良いので、生成された `.pb.go` に含まれている定義を流用する。上記の `color.pb.go` では次の通り定義されている。

```go
var file_proto_color_proto_extTypes = []protoimpl.ExtensionInfo{
	{
		ExtendedType:  (*descriptorpb.EnumValueOptions)(nil),
		ExtensionType: (*ColorOptions)(nil),
		Field:         50001,
		Name:          "sample.color_options",
		Tag:           "bytes,50001,opt,name=color_options",
		Filename:      "proto/color.proto",
	},
}

// Extension fields to descriptorpb.EnumValueOptions.
var (
	// optional sample.ColorOptions color_options = 50001;
	E_ColorOptions = &file_proto_color_proto_extTypes[0]
)
```

したがって、1つ目の `proto.Mesasge` をどのように取得するかを考える。そのために、Protocol BuffersからGoコードがどのように生成されるかに触れる必要があるので、次はそれについて書く。

## Protocol BuffersからGoコードの生成まで

今回はEnumの話なので、Protocol Buffersから生成されるGoのコードのうちEnumのみに絞って書く。他の型においても多少は異なるかもしれないが、似たような実装になっているので各自実装を追って欲しい。

ちなみに、protoreflectパッケージを見ると、それぞれの型の関係が図式化してあるので、理解の助けになるかもしれない。

```go
//	                       ┌───────────────────────────────────┐
//	                       V                                   │
//	   ┌────────────── New(n) ─────────────┐                   │
//	   │                                   │                   │
//	   │      ┌──── Descriptor() ──┐       │  ┌── Number() ──┐ │
//	   │      │                    V       V  │              V │
//	╔════════════╗  ╔════════════════╗  ╔════════╗  ╔════════════╗
//	║  EnumType  ║  ║ EnumDescriptor ║  ║  Enum  ║  ║ EnumNumber ║
//	╚════════════╝  ╚════════════════╝  ╚════════╝  ╚════════════╝
//	      Λ           Λ                   │ │
//	      │           └─── Descriptor() ──┘ │
//	      │                                 │
//	      └────────────────── Type() ───────┘
```

ref: [https://pkg.go.dev/google.golang.org/protobuf@v1.32.0/reflect/protoreflect#hdr-Relationships](https://pkg.go.dev/google.golang.org/protobuf@v1.32.0/reflect/protoreflect#hdr-Relationships)

それはさておき、Protocol BuffersのEnum型から生成されるGoのコードは、 `impl` パッケージの `EnumInfo` 構造体によって表現される。

```go
type EnumInfo struct {
	GoReflectType reflect.Type // int32 kind
	Desc          protoreflect.EnumDescriptor
}
```

ref: [https://pkg.go.dev/google.golang.org/protobuf@v1.32.0/internal/impl#EnumInfo](https://pkg.go.dev/google.golang.org/protobuf@v1.32.0/internal/impl#EnumInfo)

これを見るとEnumInfoは型情報とDescriptorを持っていることが分かる。

 `reflect.Type` 型の `GoReflectType` は型情報を持つ。このフィールドには、[ここ](https://github.com/protocolbuffers/protobuf-go/blob/9e454d65149668ac46c431956cecb61d6d20075f/internal/filetype/build.go#L148-L151)でGoコードのBuild時に生成されたGoの型が入るようになっている。今回は `Color` 型が `int32` なので `int32` が入る。DescriptorはGoで生成されている次の `rawDesc` をパースして生成される。

<details>
<summary>rawDescのバイト列</summary>

```go
var file_proto_color_proto_rawDesc = []byte{
	0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x70, 0x72,
	0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x1a, 0x20, 0x67, 0x6f, 0x6f,
	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73,
	0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a,
	0x0c, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a,
	0x0a, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x09, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x3b, 0x0a, 0x05,
	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x05, 0x57, 0x48, 0x49, 0x54, 0x45, 0x10, 0x00,
	0x1a, 0x0d, 0x8a, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x23, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x12,
	0x18, 0x0a, 0x05, 0x42, 0x4c, 0x41, 0x43, 0x4b, 0x10, 0x01, 0x1a, 0x0d, 0x8a, 0xb5, 0x18, 0x09,
	0x0a, 0x07, 0x23, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3a, 0x61, 0x0a, 0x0d, 0x63, 0x6f, 0x6c,
	0x6f, 0x72, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f,
	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75,
	0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd1, 0x86,
	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x43,
	0x6f, 0x6c, 0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0c, 0x63, 0x6f, 0x6c,
	0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x5a, 0x0c,
	0x2e, 0x2e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72,
	0x6f, 0x74, 0x6f, 0x33,
}
```

</details>



    

HexCodeのままASCIIコードが読める~~変人~~天才ならば、これらにProtocol Buffersで設定したOption値が設定されていることが分かる。実際に、protocでデコードすると次のようにパースできる。

<details>
<summary>パース結果</summary>
    
```bash
$ echo 0a1170726f746f2f636f6c6f722e70726f746f120673616d706c651a20676f6f676c652f70726f746f6275662f64657363726970746f722e70726f746f222d0a0c436f6c6f724f7074696f6e73121d0a0a636f6c6f725f636f64651801200128095209636f6c6f72436f64652a3b0a05436f6c6f7212180a05574849544510001a0d8ab518090a072346464646464612180a05424c41434b10011a0d8ab518090a07233030303030303a610a0d636f6c6f725f6f7074696f6e7312212e676f6f676c652e70726f746f6275662e456e756d56616c75654f7074696f6e7318d186032001280b32142e73616d706c652e436f6c6f724f7074696f6e73520c636f6c6f724f7074696f6e73880101420e5a0c2e2e2f67656e657261746564620670726f746f33 | xxd -r -p | protoc --decode_raw
1: "proto/color.proto"
2: "sample"
3: "google/protobuf/descriptor.proto"
4 {
  1: "ColorOptions"
  2 {
    1: "color_code"
    3: 1
    4: 1
    5: 9
    10: "colorCode"
  }
}
5 {
  1: "Color"
  2 {
    1: "WHITE"
    2: 0
    3 {
      50001 {
        1: "#FFFFFF"
      }
    }
  }
  2 {
    1: "BLACK"
    2: 1
    3 {
      50001 {
        1: "#000000"
      }
    }
  }
}
7 {
  1: "color_options"
  2: ".google.protobuf.EnumValueOptions"
  3: 50001
  4: 1
  5: 11
  6: ".sample.ColorOptions"
  10: "colorOptions"
  17: 1
}
8 {
  11: "../generated"
}
12: "proto3"
```

</details>

フォーマットに関しては[ドキュメント](https://protobuf.dev/programming-guides/encoding/)や[実装](https://github.com/protocolbuffers/protobuf-go/blob/784c4825545540dc41a1dc85715d3251903bc8ce/internal/filedesc/desc_init.go#L94-L220)を見れば理解できるはずなので、興味のある人は参照して欲しい。

また、 `protoreflect.EnumDescriptor` 型の `Desc` は `google.protobuf.EnumDescriptorProto` messsageに対応する型で、次の通り、値やオプションの情報などを持つことが分かる。

```proto
// Describes an enum type.
message EnumDescriptorProto {
  optional string name = 1;

  repeated EnumValueDescriptorProto value = 2;

  optional EnumOptions options = 3;

  // Range of reserved numeric values. Reserved values may not be used by
  // entries in the same enum. Reserved ranges may not overlap.
  //
  // Note that this is distinct from DescriptorProto.ReservedRange in that it
  // is inclusive such that it can appropriately represent the entire int32
  // domain.
  message EnumReservedRange {
    optional int32 start = 1;  // Inclusive.
    optional int32 end = 2;    // Inclusive.
  }

  // Range of reserved numeric values. Reserved numeric values may not be used
  // by enum values in the same enum declaration. Reserved ranges may not
  // overlap.
  repeated EnumReservedRange reserved_range = 4;

  // Reserved enum value names, which may not be reused. A given name may only
  // be reserved once.
  repeated string reserved_name = 5;
}
```

ref: [https://github.com/protocolbuffers/protobuf/blob/v21.3/src/google/protobuf/descriptor.proto#L246-L273](https://github.com/protocolbuffers/protobuf/blob/v21.3/src/google/protobuf/descriptor.proto#L246-L273)
    

そして、Goの型定義は次の通りだ。

```go
// EnumDescriptor describes an enum and
// corresponds with the google.protobuf.EnumDescriptorProto message.
//
// Nested declarations:
// [EnumValueDescriptor].
type EnumDescriptor interface {
	Descriptor

	// Values is a list of nested enum value declarations.
	Values() EnumValueDescriptors

	// ReservedNames is a list of reserved enum names.
	ReservedNames() Names
	// ReservedRanges is a list of reserved ranges of enum numbers.
	ReservedRanges() EnumRanges

	isEnumDescriptor
}
```

ここで、Values()、ReservedNames()、ReversedRanges()、Descriptor、isEnumDesciriptor interfaceはどこで実装されているのか、と思うかもしれない。実は、これらのinterfaceは、全てfiledescパッケージの中で宣言されている `Enum` structおよびembedded fieldの `Base` structで実装されている([ref](https://github.com/protocolbuffers/protobuf-go/blob/v1.32.0/internal/filedesc/desc.go#L151-L206))。そして、このstructに対して上記のrawDescのバイト列をパースして値がマップされるように実装されている。

## Optional Valueの取り方

上記の定義からEnumのOptional ValueはValues()に紐づいた情報であることが分かる。したがって、Enum→Descriptor→Valuesから、各valueの値を取得すれば良さそうだ。ここで、ValuesからValueのフィルタ方法について考えたい。Valuesは次のinterfaceを持つ。

```go
// EnumValueDescriptors is a list of enum value declarations.
type EnumValueDescriptors interface {
	// Len reports the number of enum values.
	Len() int
	// Get returns the ith EnumValueDescriptor. It panics if out of bounds.
	Get(i int) EnumValueDescriptor
	// ByName returns the EnumValueDescriptor for the enum value named s.
	// It returns nil if not found.
	ByName(s Name) EnumValueDescriptor
	// ByNumber returns the EnumValueDescriptor for the enum value numbered n.
	// If multiple have the same number, the first one defined is returned
	// It returns nil if not found.
	ByNumber(n EnumNumber) EnumValueDescriptor

	doNotImplement
}
```

これを見るに、管理されているEnum valuesの番号、もしくは名前で参照することが出来るようだ。そのため、対象のEnumの変数名を `x` とした場合、次のように取得できるだろう。

- `x.Descriptor().Values().Get(int(x.Number())`
- `x.Descriptor().Values().ByName(string(x))`
- `x.Descriptor().Values().ByNumber(x.Number())`
    - aliasを利用する場合、適切ではないことに注意

この辺りはお好みだと思うが、ByNameを用いると `string` への変換コストがかかるので、無難なのは1番目の取得方法になると思う。ここから、次のようにOptional Valueを取得すれば良さそうだ。

- `x.Descriptor().Values().Get(int(x.Number()).Options().(*descriptorpb.EnumValueOptions)`

これで、無事に当初の目的であった `proto.Message` が取得できた。

## 総括

これでExtensionの値を取得でき、次の通り無事に値を取得できる。

```go
func GetOption(x generated.Color) *generated.ColorOptions {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("panic with %v", err)
		}
	}()

	return proto.GetExtension(
		x.Descriptor().Values().Get(int(x.Number())).Options().(*descriptorpb.EnumValueOptions),
		generated.E_ColorOptions,
	).(*generated.ColorOptions)
}

func main() {
	log.Printf("optional_value: %s", GetOption(generated.Color_BLACK).GetColorCode())
}
```

## おわりに

今回は、ふと疑問を持ったのでprotobuf関連のGoパッケージの実装に色々と目を通してみた。その過程で、Protocol Buffersに対応するGoの実装におけるinterfaceの使い方が上手いなと思った。


例えば、今回取り上げたEnumは保持しているDescriptorをinterfaceとして持っていたことを覚えているだろうか。同様に、Message型でも同じようにDescriptorをMessageDescriptorというinterfaceで持っていることが分かる。

```go
// MessageInfo provides protobuf related functionality for a given Go type
// that represents a message. A given instance of MessageInfo is tied to
// exactly one Go type, which must be a pointer to a struct type.
//
// The exported fields must be populated before any methods are called
// and cannot be mutated after set.
type MessageInfo struct {
	// GoReflectType is the underlying message Go type and must be populated.
	GoReflectType reflect.Type // pointer to struct

	// Desc is the underlying message descriptor type and must be populated.
	Desc protoreflect.MessageDescriptor

	// Exporter must be provided in a purego environment in order to provide
	// access to unexported fields.
	Exporter exporter

	// OneofWrappers is list of pointers to oneof wrapper struct types.
	OneofWrappers []interface{}

	initMu   sync.Mutex // protects all unexported fields
	initDone uint32

	reflectMessageInfo // for reflection implementation
	coderMessageInfo   // for fast-path method implementations
}
```

こういった綺麗なinterfaceを設計できるようになりたいものだなと思った。