Protocol Buffers Proto3 文法 早めぐり

f:id:Naotsugu:20190924235738p:plain



blog1.mammb.com

概略

.proto ファイルを作成し、protoc というコンパイラで各言語向けのスタブファイルを作成します。

helloworld.proto の例は以下のようなものです。

syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}


service ブロックに rpc キーワードでRPC(Remote Procedure Call) のインターフェースを定義します。

message キーワードで、やり取りするメッセージフォーマットを定義します。

ファイルの先頭には syntax キーワードで構文を指定します。 指定しなかった場合には proto2 が指定されたものとして扱われます。

package キーワードでパッケージを定義します。

コメントは C形式のコメントが利用できます。 行コメントには //、ブロックコメントには /* */ を使います。



メッセージタイプ

メッセージにはフィールドを定義できます。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

フィールドにはフィールド番号を割り当てます。 この番号はメッセージバイナリ形式での識別子として利用されます。

フィールドを削除した場合などで、削除済みのフィールドを再利用されないようにするには reserved キーワードを使うことでフィールド番号やフィールド名を予約することができます。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}


proto2 では requiredoptional が使えたが proto3 で廃止されました。

スカラー値型

スカラー値型は以下のようなものが利用でき、コンパイル後の各言語との対応は以下のようになります。

.proto Type Java Type Python Type Go Type C# Type
double double float float64 double
float float float float32 float
int32 int int int32 int
int64 long int/long int64 long
uint32 int int/long uint32 uint
uint64 long int/long uint64 ulong
sint32 int int int32 int
sint64 long int/long int64 long
fixed32 int int/long uint32 uint
fixed64 long int/long uint64 ulong
sfixed32 int int int32 int
sfixed64 long int/long int64 long
bool boolean bool bool bool
string String str/unicode string string
bytes ByteString str []byte ByteString



サービス

RPC サービスのインターフェイスを以下のように定義できます。

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

SearchRequest を引数に取り SearchResponse を返す RPC サービスを定義しています。

コンパイラは選択した言語でサービスのインターフェイスとスタブを生成します。



入れ子のメッセージ

メッセージは他のメッセージを使い構造化することができます。

message SearchResponse {
  Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
}


メッセージ内に直接定義することもできます。

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}


内部のメッセージは以下のようにピリオドでつないで使うことができます。

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}



enum

enum キーワードで enum を定義できます。

enum Corpus {
  UNIVERSAL = 0;
  WEB = 1;
  IMAGES = 2;
  LOCAL = 3;
}

フィールド番号として、必ず最初に 0 を指定する必要があります。 フィールド番号 0 のものがデフォルト値として扱われます。

メッセージの中に入れ子で定義することもできます。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
  }
  Corpus corpus = 4;
}

なお、reserved キーワードも前述の通りに使うことができます。



repeated

repeated でリストを定義できます。

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}



map

map でマップを定義できます。

map<string, Project> projects = 3;

maprepeated で修飾することはできません。

oneof

たくさんのフィールドがあり、その内の 1 つだけに値が設定される場合は oneof を使うことでメモリを節約することができます。

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}


proto3 で Optional を表す代替として oneof を使うことができます。

以下のようにフィールドが 1 つの oneof を定義します。

message Request {
  oneof option {
    int64 option_value = 1;
  }
}


以下のように Optional を模倣することができます。

Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

if (optionNotSet.equals(optionCase)){
    // value not set
} else {
    // value set
}



デフォルト値

やり取りするメッセージに特定の要素が含まれていない場合、そのフィールドのデフォルト値が適用されます。

  • 文字列:空の文字列
  • バイト:空のバイトです。
  • bool:false
  • 数値型:ゼロ
  • enums:最初に定義されたフィールド番号 0 の enum値
  • メッセージフィールド:設定されない(言語に依存)
  • 繰り返しフィールド:空



パッケージ

名前のコンフリクトを防ぐために package キーワードでパッケージを定義することができます。

package foo.bar;
message Open { ... }


以下のようにドットで連結して参照することができます。

foo.bar.Open open = 1;


パッケージは、生成されたコードの言語で扱いがことなります。

Go や Java はそのままパッケージとして扱われます(option go_packageoption java_package でソース上のパッケージを上書することができます)。



インポート

他の .proto の定義をインポートするには するには、import キーワードで他の .proto ファイルを指定します。

import "myproject/other_protos.proto";

インポートすると、この .proto 内でのみ インポートした定義を利用することができます。

さらに外側の .proto までインポートした定義を公開するには以下のように public キーワードを指定します。

import public "myproject/other_protos.proto";

これによりインポートした定義を他の .proto に推移させることができます。

オプション

.proto ファイルには option キーワードで注釈をつけることができます。


生成されたJavaクラスに使用するパッケージを指定するには以下のようにします。

option java_package = "com.example.foo";



デフォルトではパッケージのクラスの内部クラスとしてメッセージやサービスが定義されますが、以下の指定をすることで、内部クラスではなく通常のクラスとしてコード生成されます。

option java_multiple_files = true;


生成する外部クラス名を指定するには以下のオプションを設定します。

option java_outer_classname = "Ponycopter";



コンパイラオプション

protoc コンパイラにて .proto ファイルからコード生成を行います。

protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR  path / to / file .proto


IMPORT_PATH には .proto を探すディレクトリを指定します(省略すると現在のディレクトリ)。 --proto_path-I のように省略形を指定することもできます。

DST_DIR は出力先のディレクトリを指定します。それぞれ出力先の言語を並べて指定することができます。

  • --cpp_out で C++ コードを生成
  • --java_out で Java コードを生成
  • --python_out で Python コードを生成
  • --go_out で Go コードを生成
  • --ruby_out で Ruby コードを生成
  • --objc_out で Objective-C コードを生成
  • --csharp_out で C#コードを生成
  • --php_out で PHP コードを生成