gpb は、Erlang 用の Google プロトコル バッファ定義ファイルのコンパイラです。
ショートカット: API ドキュメント ~ hex.pm の gpb
protobuf ファイルx.proto
あるとします。
message Person {
required string name = 1 ;
required int32 id = 2 ;
optional string email = 3 ;
}
この定義のコードはさまざまな方法で生成できます。ここではコマンドラインツールを使用します。 Rebar との統合については、さらに下を参照してください。
# .../gpb/bin/protoc-erl -I. x.proto
これで、 x.erl
とx.hrl
が完成しました。まずコンパイルしてから、Erlang シェルで試してみます。
# erlc -I.../gpb/include x.erl
# erl
Erlang/OTP 19 [erts-8.0.3] [source] [64-bit] [smp:12:12] [async-threads:10] [kernel-poll:false]
Eshell V8.0.3 (abort with ^G)
1> rr("x.hrl").
['Person']
2> x:encode_msg(#'Person'{ name = " abc def " , id = 345 , email = " [email protected] " }).
<< 10 , 7 , 97 , 98 , 99 , 32 , 100 , 101 , 102 , 16 , 217 , 2 , 26 , 13 , 97 , 64 , 101 ,
120 , 97 , 109 , 112 , 108 , 101 , 46 , 99 , 111 , 109 >>
3 > Bin = v ( - 1 ).
<< 10 , 7 , 97 , 98 , 99 , 32 , 100 , 101 , 102 , 16 , 217 , 2 , 26 , 13 , 97 , 64 , 101 ,
120 , 97 , 109 , 112 , 108 , 101 , 46 , 99 , 111 , 109 >>
4 > x : decode_msg ( Bin , 'Person' ).
# 'Person' { name = " abc def " , id = 345 , email = " [email protected] " }
Erlang シェルでは、 rr("x.hrl")
レコード定義を読み取り、 v(-1)
履歴の 1 ステップ前の値を参照します。
プロトバッファのタイプ | アーランの種類 |
---|---|
ダブル、フロート | フロート() |無限 | '-無限大' |ナン エンコード時には整数も受け入れられます |
int32、int64 uint32、uint64 sint32、sint64 固定32、固定64 sfixed32、sfixed64 | 整数() |
ブール | 本当 |間違い エンコード時には、整数 1 と 0 も受け入れられます。 |
列挙型 | 原子() 不明な列挙型は integer() にデコードされます |
メッセージ | レコード (したがって tuple()) または、maps (-maps) オプションが指定されている場合は、map() |
弦 | Unicode 文字列、つまり整数のリスト または strings_as_binaries (-strbin) オプションが指定されている場合は binary() エンコード時には、iolist も受け入れられます |
バイト | バイナリ() エンコード時には、iolist も受け入れられます |
そのうちの一つ | {選択されたフィールド名、値} またはChosenFieldName => {maps_oneof, flat} (-maps_oneof flat) オプションが指定されている場合の値 |
地図<_,_> | 2 つのタプルの順序なしリスト[{Key,Value}] または、map() (maps (-maps) オプションが指定されている場合) |
繰り返されるフィールドはリストとして表されます。
オプションのフィールドは、値として表されるか、設定されていない場合はundefined
として表されます。ただし、マップの場合、オプションmaps_unset_optional
がomitted
に設定されている場合、未設定のオプションの値は、メッセージをエンコードするときにundefined
に設定されるのではなく、マップから省略されます。メッセージをデコードするときに、 maps_unset_optional
がomitted
された場合でも、デコードされたマップにはデフォルト値が設定されます。
message m1 {
repeated uint32 i = 1 ;
required bool b = 2 ;
required eee e = 3 ;
required submsg sub = 4 ;
}
message submsg {
required string s = 1 ;
required bytes b = 2 ;
}
enum eee {
INACTIVE = 0 ;
ACTIVE = 1 ;
}
# m1 { i = [ 17 , 4711 ],
b = true ,
e = 'ACTIVE' ,
sub = # submsg { s = " abc " ,
b = << 0 , 1 , 2 , 3 , 255 >>}}
% % If compiled to with the option maps:
#{ i => [ 17 , 4711 ],
b => true ,
e => 'ACTIVE' ,
sub => #{ s => " abc " ,
b => << 0 , 1 , 2 , 3 , 255 >>}}
message m2 {
optional uint32 i1 = 1 ;
optional uint32 i2 = 2 ;
}
# m2 { i1 = 17 } % i2 is implicitly set to undefined
% % With the maps option
#{ i1 => 17 }
% % With the maps option and the maps_unset_optional set to present_undefined:
#{ i1 => 17 ,
i2 => undefined }
この構造は、Google protobuf バージョン 2.6.0 で初めて登場しました。
message m3 {
oneof u {
int32 a = 1 ;
string b = 2 ;
}
}
oneof フィールドは自動的に常にオプションになります。
# m3 { u = { a , 17 }}
# m3 { u = { b , " hello " }}
# m3 {} % u is implicitly set to undefined
% % With the maps option
#{ u => { a , 17 }}
#{ u => { b , " hello " }}
#{} % If maps_unset_optional = omitted (default)
#{ u => undefined } % With maps_unset_optional set to present_undefined
% % With the {maps_oneof,flat} option (requires maps_unset_optional = omitted)
#{ a => 17 }
#{ b => " hello " }
#{}
Erlang マップと混同しないでください。この構造は、Google protobuf バージョン 3.0.0 ( proto2
構文とproto3
構文の両方) で最初に登場しました。
message m4 {
map < uint32 , string > f = 1 ;
}
レコードの場合、デコード時に項目の順序は定義されません。
# m4 { f = []}
# m4 { f = [{ 1 , " a " }, { 2 , " b " }, { 13 , " hello " }]}
% % With the maps option
#{ f => #{}}
#{ f => #{ 1 => " a " , 2 => " b " , 13 => " hello " }}
default
オプションの設定を解除するこれは、デコードするバイナリに存在しないオプション フィールドのデコードがどのように機能するかを説明します。
Google protobuf のドキュメントには、これらが指定されている場合はデフォルト値に、そうでない場合はフィールドの型固有のデフォルトにデコードされると記載されています。 Google の protobuf コンパイラによって生成されたコードにはhas_
メソッドも含まれているため、フィールドが実際に存在するかどうかを調べることができます。
ただし、Erlang では、フィールドを設定および読み取る自然な方法は、レコード (またはマップ) の構文を使用することです。これでは、フィールドが存在するかどうかを伝え、フィールドを読み取ることを同時に行う良い方法はありません。デフォルト。
したがって、 gpb
でのアプローチは、次のいずれかを選択する必要があるということです。通常、オプションのフィールドが存在するかどうかは、たとえば値がundefined
かどうかをチェックすることによって確認できます。ただし、代わりにデフォルトにデコードするコンパイラのオプションがあり、その場合、フィールドが存在するかどうかを確認できなくなります。オプションは、それぞれ、 default=
値またはタイプ固有のデフォルトにデコードするための、 defaults_for_omitted_optionals
およびtype_defaults_for_omitted_optionals
です。
これは次のように機能します。
message o1 {
optional uint32 a = 1 [ default = 33 ];
optional uint32 b = 2 ; // the type-specific default is 0
}
バイナリ データ<<>>
が指定された場合、つまりフィールドa
もb
存在しない場合、 decode_msg(Input, o1)
呼び出しの結果は次のようになります。
# o1 { a = undefined , b = undefined } % None of the options
# o1 { a = 33 , b = undefined } % with option defaults_for_omitted_optionals
# o1 { a = 33 , b = 0 } % with both defaults_for_omitted_optionals
% and type_defaults_for_omitted_optionals
# o1 { a = 0 , b = 0 } % with only type_defaults_for_omitted_optionals
最後の代替案はおそらくあまり有用ではありませんが、それでも可能であり、完全を期すために実装されています。
Google のリファレンス
proto3 の場合、フィールドにはrequired
もdefault=
もありません。代わりに、 optional
でマークされていない限り、すべてのスカラー フィールド、文字列、およびバイトは暗黙的にオプションになります。デコード時に、デコードするバイナリにそのようなフィールドが欠落している場合、常にタイプ固有のデフォルト値にデコードされます。エンコードの際、そのようなフィールドは、型固有のデフォルト値とは異なる値を持つ場合にのみ、結果としてエンコードされたバイナリに含まれます。すべてのフィールドは暗黙的にオプションですが、概念レベルでは、そのようなフィールドはすべて常に値を持っているとも言えます。デコード時には、エンコード時に値が存在するかどうか、つまり型固有の値が存在するかどうかを判断することはできません。
optional
としてマークされたフィールドは、基本的に proto2 構文と同じ方法で表されます。レコードでは、フィールドが設定されていない場合、フィールドの値はundefined
なります。また、マップでは、フィールドが設定されていない場合、フィールドは存在しません。
「欠落している」データの検出が必要な場合に私が見た推奨事項は、 has_
ブール型フィールドを定義し、それらを適切に設定することです。別の方法として、よく知られているラッパー メッセージを使用することもできます。
サブメッセージおよびフィールドの 1 つであるフィールドには、タイプ固有のデフォルトがありません。設定されていないサブメッセージ フィールドは、サブメッセージに設定されているサブメッセージ フィールドとは異なる方法でエンコードされ、異なる方法でデコードされます。これは、サブメッセージにフィールドがない場合でも当てはまります。 oneof フィールドについても少し同様に機能します。代替フィールドのいずれも設定されていないか、いずれかのフィールドが設定されています。エンコードされた形式が異なるため、デコードすると違いがわかります。
プロトコル バッファ定義ファイルを解析し、以下を生成できます。
プロトコル バッファ定義ファイルの機能: gpb は以下をサポートします。
packed
とdefault
オプションallow_alias
enum オプション (常に true に設定されているかのように扱われます)oneof
(protobuf 2.6.0 で導入)map<_,_>
(protobuf 3.0.0 で導入)gpb は読み取りますが無視します。
packed
またはdefault
以外のオプションgpb は以下をサポートしていません:
GPBの特徴:
bytes
フィールドの内容をコピーできます。package
属性を利用できます。これは、パッケージ間でのメッセージ タイプの名前の衝突を避けるのに役立ちます。 use_packages
オプションまたは-pkgs
コマンド ライン オプションを参照してください。get_msg_defs
関数の gpb.hrl 内の#field{}
レコードに対して依存関係がありますが、これを回避することは可能です。この依存関係には、 defs_as_proplists
または-pldefs
オプションも使用します。内観
gpb は、メッセージ、列挙型、およびサービスを検査するためのいくつかの関数を生成します。
get_msg_defs()
(または、 introspect_get_proto_defs
が設定されている場合はget_proto_defs()
)、 get_msg_names()
、 get_enum_names()
find_msg_def(MsgName)
およびfetch_msg_def(MsgName)
find_enum_def(MsgName)
およびfetch_enum_def(MsgName)
enum_symbol_by_value(EnumName, Value)
、enum_symbol_by_value_(Value)
、 enum_value_by_symbol(EnumName, Enum)
およびenum_value_by_symbol_(Enum)
get_service_names()
、 get_service_def(ServiceName)
、 get_rpc_names(ServiceName)
find_rpc_def(ServiceName, RpcName)
、 fetch_rpc_def(ServiceName, RpcName)
完全修飾名と内部名の間で変換するための関数もいくつかあります。これらでは、名前変更オプションが考慮されます。これらは、たとえば grpc リフレクションで役立つ場合があります。
fqbin_to_service_name(<<"Package.ServiceName">>)
およびservice_name_to_fqbin('ServiceName')
fqbins_to_service_and_rpc_name(<<"Package.ServiceName">>, <<"RpcName">>)
およびservice_and_rpc_name_to_fqbins('ServiceName', 'RpcName')
fqbin_to_msg_name(<<"Package.MsgName">>)
およびmsg_name_to_fqbin('MsgName')
fqbin_to_enum_name(<<"Package.EnumName">>)
およびenum_name_to_fqbin('EnumName')
型がどのプロトに属しているかを問い合わせる関数もいくつかあります。各タイプは文字列である"name"
に属し、通常は拡張子のないファイル名です。たとえば、proto ファイルが"name.proto"
の場合は"name"
です。
get_all_proto_names() -> ["name1", ...]
get_msg_containment("name") -> ['MsgName1', ...]
get_pkg_containment("name") -> 'Package'
get_service_containment("name") -> ['Service1', ...]
get_rpc_containment("name") -> [{'Service1', 'RpcName1}, ...]
get_proto_by_msg_name_as_fqbin(<<"Package.MsgName">>) -> "name"
get_proto_by_enum_name_as_fqbin(<<"Package.EnumName">>) -> "name"
get_protos_by_pkg_name_as_fqbin(<<"Package">>) -> ["name1", ...]
いくつかのバージョン情報関数もあります。
gpb:version_as_string()
、 gpb:version_as_list()
およびgpb:version_source()
GeneratedCode:version_as_string()
、 GeneratedCode:version_as_list()
およびGeneratedCode:version_source()
?gpb_version
(gpb_version.hrl 内)?'GeneratedCode_gpb_version'
(GeneratedCode.hrl 内)gpb は、proto ファイルの自己記述を生成することもできます。自己記述は、Google プロトコル バッファ ライブラリに付属する descriptor.proto を使用してバイナリにエンコードされた proto ファイルの記述です。このようなエンコードされた自己記述は、Google プロトコル バッファ コンパイラが同じプロトコルに対して生成するものとバイトごとに同一ではありませんが、ほぼ同等であるはずであることに注意してください。
誤ってエンコードされた protobuf メッセージとフィールドは、通常、デコーダのクラッシュを引き起こします。このような誤ったエンコーディングの例は次のとおりです。
地図
Gpb はマップのエンコーダー/デコーダーを生成できます。
オプションmaps_unset_optional
使用すると、存在しないオプションフィールドの動作を指定できます。フィールドがマップから省略されるか、または存在するがレコードの場合のように値がundefined
であるかどうかです。
.proto ファイル内のエラーのレポート
Gpb はエラー報告、特に定義されていないメッセージへの参照などのエラーの参照があまり得意ではありません。 .proto ファイルを gpb にフィードする前に、最初にprotoc
でそのファイルが有効であることを確認することをお勧めします。
rebar3 で gpb を使用する方法については、https://rebar3.org/docs/configuration/plugins/#protocol-buffers を参照してください。
rebar では、バージョン 2.6.0 以降 gpb がサポートされています。 https://github.com/rebar/rebar/blob/master/rebar.config.sample にある rebar.sample.config ファイルのプロト コンパイラ セクションを参照してください。
Rebar の古いバージョン (2.6.0 より前のバージョン) の場合、次のテキストに続行方法の概要が記載されています。
たとえば、.proto ファイルをproto/
サブディレクトリに配置します。 rebar は src/ サブディレクトリで見つかった .proto に対して別の protobuf コンパイラを使用しようとするため、src/ 以外のサブディレクトリは問題ありません。 rebar.config
ファイルのいくつかの行を次に示します。
%% -*- erlang -*-
{pre_hooks,
[{compile, "mkdir -p include"}, %% ensure the include dir exists
{compile,
"/path/to/gpb/bin/protoc-erl -I`pwd`/proto"
"-o-erl src -o-hrl include `pwd`/proto/*.proto"
}]}.
{post_hooks,
[{clean,
"bash -c 'for f in proto/*.proto; "
"do "
" rm -f src/$(basename $f .proto).erl; "
" rm -f include/$(basename $f .proto).hrl; "
"done'"}
]}.
{erl_opts, [{i, "/path/to/gpb/include"}]}.
gpb バージョン番号は、NM と一致する git の最新の git タグから取得されます。N と M は整数です。このバージョンは、gpb.app ファイルおよび include/gpb_version.hrl に挿入されます。バージョンはコマンドの結果です
git describe --always --tags --match '[0-9]*.[0-9]*'
したがって、gpb の新しいバージョンを作成する場合、このバージョンがフェッチされる単一のソースは git タグです。 (gpb を git 以外の別のバージョン管理システムにインポートしている場合、または rebar 以外の別のビルド ツールを使用している場合は、それに応じて rebar.config と src/gpb.app.src を調整する必要がある場合があります。 git から gpb をエクスポートする方法については、git ワーク ツリーを参照してください。)
上記のgit describe
コマンドのバージョン番号は次のようになります。
..
(Github のマスター上)..--g
(ブランチ上またはリリース間) Github 上の gpb の master ブランチのバージョン番号は、reltool との互換性を保つために、常にドット付きの整数のみになるように設計されています。言い換えれば、Github のマスター ブランチへの各プッシュはリリースとみなされ、バージョン番号が上がります。これを確実にするために、 pre-push
git フックと 2 つのスクリプト、 install-git-hooks
およびtag-next-minor-vsn
が helpers サブディレクトリにあります。 ChangeLog ファイルには必ずしもすべてのマイナー バージョン バンプが反映されるわけではなく、重要な更新のみが反映されます。
新しいバージョンを作成するときに更新する場所:
gpb ビルド プロセスでは、「バージョン番号付け」セクションで説明されているように、タグ付きの (浅くない) git ワーク ツリーでバージョン番号付けが正しく行われることが期待されますが、git の外部でビルドすることも可能です。そのためには、次の 2 つのオプションがあります。
gpb.vsn
を作成して、バージョンを手動で設定します。helpers/mk-versioned-archive
スクリプトを使用してバージョン付きアーカイブを作成し、そのアーカイブを解凍してその中にビルドします。 git ワーク ツリーでバージョン付きアーカイブを作成した場合、バージョンは自動的に設定されます。それ以外の場合は、手動で指定する必要があります。使用するオプションについては、 mk-versioned-archive --help
実行してください。
Github からダウンロードする場合、 gpb-
Github の自動ソース コードzip または tar.gz アーカイブを使用する場合は、上記のようにgpb.vsn
ファイルを作成するか、 mk-versioned-archive
スクリプトと--override-version=
使用してバージョン付きアーカイブを再作成する必要があります。 --override-version=
オプション (ディレクトリ名に適切なバージョンが含まれている場合は--override-version-from-cwd-path
オプションも使用できます)。
できればプル リクエスト、git パッチ、または git フェッチ リクエストとしての貢献を歓迎します。以下にいくつかのガイドラインを示します。
rebar clean; rebar eunit && rebar doc
詳細については、変更ログを参照してください。
maps_unset_optional
オプションのデフォルト値がpresent_undefined
からomitted
た に変更されました。これは、maps (-maps) オプションで生成されたコードのみに関係します。このオプションをすでに明示的に設定しているプロジェクトは影響を受けません。デフォルトのpresent_undefined
に依存しているプロジェクトは、4.0.0 にアップグレードするためにオプションを明示的に設定する必要があります。
型仕様については、可能な場合に生成するようにデフォルトが変更されました。オプション{type_specs,false}
(-no_type) を使用すると、型仕様の生成を回避できます。