👌
手書きでProtocol Buffersの生成コードを書いてみる
ここではProtocol Buffersの文字列だけのメッセージをエンコード/デコードできるコードを手書きで再現してみます。コード生成はbufを使っています。もしbufを使ったことがなければ、ここのチュートリアルを見ると使い方が分かると思います。
Goプロジェクトを作成
$ mkdir proto-go-example
$ cd proto-go-example
$ go mod init example
protoファイルを定義
$ mkdir -p str/v1
$ touch str/v1/str.proto
str/v1/str.proto
を編集して、次のようにします。
syntax = "proto3";
package str.v1;
option go_package = "example/gen/str/v1;strv1";
message Str {
string str_value = 1;
}
コードの生成
プロジェクトのトップでbuf mod init
を実行すると、buf.gen.yaml
が生成されます。buf lint
でチェックして、何も出ないのを確認してbuf generate
でgen/str/v1/str.pb.go
を生成します。
簡単なコードの実行
Protocol Buffersのエンコード/デコードを試してみます。次のようなコードになります。
main.go
package main
import (
strv1 "example/gen/str/v1"
"fmt"
"log"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
)
func main() {
f := strv1.File_str_v1_str_proto
fmt.Println(f.Syntax())
fmt.Println(f.Path())
opts := proto.Clone(f.Options()).(*descriptorpb.FileOptions)
fmt.Println(opts.GetGoPackage())
fmt.Println(f.Package())
messages := f.Messages()
fmt.Println(messages.Len())
message := messages.Get(0)
fmt.Println(message.FullName())
fields := message.Fields()
fmt.Println(fields.Len())
field := fields.Get(0)
fmt.Println(field.FullName())
fmt.Println(field.Kind())
fmt.Println(field.Cardinality())
fmt.Println(field.JSONName())
fmt.Println(field.TextName())
s := &strv1.Str{StrValue: "hello, protocol buffers!"}
out, err := proto.Marshal(s)
if err != nil {
log.Fatalln("Failed to encode str", err)
}
fmt.Println(out)
var ss strv1.Str
if err := proto.Unmarshal(out, &ss); err != nil {
log.Fatalln(err)
}
fmt.Printf("%s\n", ss.StrValue)
}
go run .
した実行結果は次のようになります。
proto3
str/v1/str.proto
example/gen/str/v1;strv1
str.v1
1
str.v1.Str
1
str.v1.Str.str_value
string
optional
strValue
str_value
[10 24 104 101 108 108 111 44 32 112 114 111 116 111 99 111 108 32 98 117 102 102 101 114 115 33]
hello, protocol buffers!
手書きで再現してみる
str.pb.go
のfile_str_v1_str_proto_rawDesc
が一番謎だと思いますが、protocolbuffers/protobuf-goの実装を見ながら、手で作ってみます。結果は次の通りです。やたらと定数の定義があるのは、internalな定数でインポートできないためです。
main.go
package main
import (
"fmt"
"log"
"reflect"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/runtime/protoimpl"
"google.golang.org/protobuf/types/descriptorpb"
)
const (
FileDescriptorProto_Name_field_number protoreflect.FieldNumber = 1
FileDescriptorProto_Package_field_number protoreflect.FieldNumber = 2
FileDescriptorProto_MessageType_field_number protoreflect.FieldNumber = 4
FileDescriptorProto_Options_field_number protoreflect.FieldNumber = 8
FileDescriptorProto_Syntax_field_number protoreflect.FieldNumber = 12
)
const DescriptorProto_Field_field_number protoreflect.FieldNumber = 2
const FileOptions_GoPackage_field_number protoreflect.FieldNumber = 11
const (
FieldDescriptorProto_Name_field_number protoreflect.FieldNumber = 1
FieldDescriptorProto_Number_field_number protoreflect.FieldNumber = 3
FieldDescriptorProto_Label_field_number protoreflect.FieldNumber = 4
FieldDescriptorProto_Type_field_number protoreflect.FieldNumber = 5
FieldDescriptorProto_JsonName_field_number protoreflect.FieldNumber = 10
FieldDescriptorProto_Options_field_number protoreflect.FieldNumber = 8
)
type Str struct {
//lint:ignore U1000 state is used in ProtoReflect
state protoimpl.MessageState
StrValue string `protobuf:"bytes,1,opt,name=str_value,json=strValue,proto3" json:"str_value,omitempty"`
}
func (x *Str) ProtoReflect() protoreflect.Message {
mi := &file_str_v1_str_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)
}
var file_str_v1_str_proto_rawDesc []byte
var file_str_v1_str_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
func init() {
var rawDesc []byte
rawDesc = protowire.AppendTag(rawDesc, FileDescriptorProto_Name_field_number, protowire.BytesType)
rawDesc = protowire.AppendBytes(rawDesc, []byte("str/v1/str.proto"))
rawDesc = protowire.AppendTag(rawDesc, FileDescriptorProto_Package_field_number, protowire.BytesType)
rawDesc = protowire.AppendBytes(rawDesc, []byte("str.v1"))
var messageDesc []byte
messageDesc = protowire.AppendTag(messageDesc, FileDescriptorProto_Name_field_number, protowire.BytesType)
messageDesc = protowire.AppendBytes(messageDesc, []byte("Str"))
var fieldDesc []byte
fieldDesc = protowire.AppendTag(fieldDesc, FieldDescriptorProto_Name_field_number, protowire.BytesType)
fieldDesc = protowire.AppendBytes(fieldDesc, []byte("str_value"))
fieldDesc = protowire.AppendTag(fieldDesc, FieldDescriptorProto_Number_field_number, protowire.VarintType)
fieldDesc = protowire.AppendVarint(fieldDesc, 1)
fieldDesc = protowire.AppendTag(fieldDesc, FieldDescriptorProto_Label_field_number, protowire.VarintType)
fieldDesc = protowire.AppendVarint(fieldDesc, uint64(protoreflect.Optional))
fieldDesc = protowire.AppendTag(fieldDesc, FieldDescriptorProto_Type_field_number, protowire.VarintType)
fieldDesc = protowire.AppendVarint(fieldDesc, uint64(protoreflect.StringKind))
fieldDesc = protowire.AppendTag(fieldDesc, FieldDescriptorProto_JsonName_field_number, protowire.BytesType)
fieldDesc = protowire.AppendBytes(fieldDesc, []byte("strValue"))
messageDesc = protowire.AppendTag(messageDesc, DescriptorProto_Field_field_number, protowire.BytesType)
messageDesc = protowire.AppendBytes(messageDesc, fieldDesc)
rawDesc = protowire.AppendTag(rawDesc, FileDescriptorProto_MessageType_field_number, protowire.BytesType)
rawDesc = protowire.AppendBytes(rawDesc, messageDesc)
var optDesc []byte
optDesc = protowire.AppendTag(optDesc, FileOptions_GoPackage_field_number, protowire.BytesType)
optDesc = protowire.AppendBytes(optDesc, []byte("example/gen/str/v1;strv1"))
rawDesc = protowire.AppendTag(rawDesc, FileDescriptorProto_Options_field_number, protowire.BytesType)
rawDesc = protowire.AppendBytes(rawDesc, optDesc)
rawDesc = protowire.AppendTag(rawDesc, FileDescriptorProto_Syntax_field_number, protowire.BytesType)
rawDesc = protowire.AppendBytes(rawDesc, []byte("proto3"))
file_str_v1_str_proto_rawDesc = rawDesc
}
func main() {
a := (*Str)(nil)
t := reflect.TypeOf(a)
s := &Str{StrValue: "hello, protocol buffers"}
o := protoimpl.DescBuilder{
GoPackagePath: "main",
RawDescriptor: file_str_v1_str_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
}.Build()
f := o.File
fmt.Println(f.Syntax())
fmt.Println(f.Path())
opts := f.Options().(*descriptorpb.FileOptions)
fmt.Println(opts.GetGoPackage())
fmt.Println(f.Package())
messages := f.Messages()
fmt.Println(messages.Len())
message := messages.Get(0)
fmt.Println(message.FullName())
fields := message.Fields()
fmt.Println(fields.Len())
field := fields.Get(0)
fmt.Println(field.FullName())
fmt.Println(field.Kind())
fmt.Println(field.Cardinality())
fmt.Println(field.JSONName())
fmt.Println(field.TextName())
mi := &file_str_v1_str_proto_msgTypes[0]
mi.GoReflectType = t
mi.Desc = &o.Messages[0]
out, err := proto.Marshal(s)
if err != nil {
log.Fatalln("Failed to encode str", err)
}
fmt.Println(out)
var ss Str
if err := proto.Unmarshal(out, &ss); err != nil {
log.Fatalln(err)
}
fmt.Printf("%s\n", ss.StrValue)
}
Discussion