Gpb adalah kompiler untuk file definisi buffer protokol Google untuk Erlang.
Pintasan: dokumentasi API ~ gpb di hex.pm
Katakanlah kita memiliki file protobuf, x.proto
message Person {
required string name = 1 ;
required int32 id = 2 ;
optional string email = 3 ;
}
Kita dapat menghasilkan kode untuk definisi ini dalam beberapa cara berbeda. Di sini kita menggunakan alat baris perintah. Untuk info tentang integrasi dengan rebar, lihat lebih jauh ke bawah.
# .../gpb/bin/protoc-erl -I. x.proto
Sekarang kita punya x.erl
dan x.hrl
. Pertama kita kompilasi dan kemudian kita bisa mencobanya di shell 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] " }
Di shell Erlang, rr("x.hrl")
membaca definisi rekaman, dan v(-1)
mereferensikan nilai satu langkah lebih awal dalam sejarah.
Tipe Protobuf | Tipe Erlang |
---|---|
ganda, mengapung | mengapung() | tak terhingga | '-tak terhingga' | nan Saat pengkodean, bilangan bulat juga diterima |
int32, int64 uint32, uint64 sint32, sint64 diperbaiki32, diperbaiki64 diperbaiki32, diperbaiki64 | bilangan bulat() |
bodoh | benar | PALSU Saat pengkodean, bilangan bulat 1 dan 0 juga diterima |
enum | atom() enum yang tidak diketahui didekodekan ke integer() |
pesan | catatan (dengan demikian Tuple()) atau map() jika opsi peta (-maps) ditentukan |
rangkaian | string unicode, jadi daftar bilangan bulat atau biner() jika opsi strings_as_binaries (-strbin) ditentukan Saat pengkodean, iolist juga diterima |
byte | biner() Saat pengkodean, iolist juga diterima |
salah satu dari | {ChosenFieldName, Nilai} atau ChosenFieldName => Nilai jika opsi {maps_oneof,flat} (-maps_oneof flat) ditentukan |
peta<_,_> | Daftar 2 tupel yang tidak berurutan, [{Key,Value}] atau map(), jika opsi peta (-maps) ditentukan |
Bidang berulang direpresentasikan sebagai daftar.
Bidang opsional direpresentasikan sebagai nilai atau undefined
jika tidak disetel. Namun, untuk peta, jika opsi maps_unset_optional
disetel ke omitted
, maka nilai opsional yang tidak disetel akan dihilangkan dari peta, alih-alih disetel ke undefined
saat menyandikan pesan. Saat mendekode pesan, bahkan dengan maps_unset_optional
disetel ke omitted
, nilai default akan disetel di peta yang didekode.
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 }
Konstruksi ini pertama kali muncul di protobuf Google versi 2.6.0.
message m3 {
oneof u {
int32 a = 1 ;
string b = 2 ;
}
}
Bidang oneof secara otomatis selalu opsional.
# 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 " }
#{}
Jangan bingung dengan peta Erlang. Konstruksi ini pertama kali muncul di Google protobuf versi 3.0.0 (untuk sintaksis proto2
dan proto3
)
message m4 {
map < uint32 , string > f = 1 ;
}
Untuk catatan, urutan item tidak ditentukan saat decoding.
# m4 { f = []}
# m4 { f = [{ 1 , " a " }, { 2 , " b " }, { 13 , " hello " }]}
% % With the maps option
#{ f => #{}}
#{ f => #{ 1 => " a " , 2 => " b " , 13 => " hello " }}
default
Ini menjelaskan cara kerja decoding untuk bidang opsional yang tidak ada dalam biner untuk didekode.
Dokumentasi untuk protobuf Google menyatakan bahwa kode ini didekodekan ke nilai default jika ditentukan, atau ke default khusus tipe bidang. Kode yang dihasilkan oleh kompiler protobuf Google juga berisi metode has_
sehingga seseorang dapat memeriksa apakah suatu bidang benar-benar ada atau tidak.
Namun, di Erlang, cara alami untuk menyetel dan membaca kolom adalah dengan hanya menggunakan sintaksis untuk rekaman (atau peta), dan hal ini tidak memberikan cara yang baik untuk sekaligus menyampaikan apakah suatu kolom ada atau tidak dan untuk membaca default.
Jadi pendekatan dalam gpb
adalah Anda harus memilih: salah satu atau. Biasanya, dimungkinkan untuk melihat apakah kolom opsional ada atau tidak, misalnya dengan memeriksa apakah nilainya undefined
. Namun ada opsi bagi kompiler untuk mendekode ke default, dalam hal ini Anda kehilangan kemampuan untuk melihat apakah suatu bidang ada atau tidak. Opsinya adalah defaults_for_omitted_optionals
dan type_defaults_for_omitted_optionals
, untuk mendekode ke nilai default=
, atau untuk masing-masing mengetik default tertentu.
Cara kerjanya seperti ini:
message o1 {
optional uint32 a = 1 [ default = 33 ];
optional uint32 b = 2 ; // the type-specific default is 0
}
Mengingat data biner <<>>
, yaitu, bidang a
maupun b
tidak ada, maka panggilan decode_msg(Input, o1)
menghasilkan:
# 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
Alternatif terakhir mungkin tidak terlalu berguna, namun masih memungkinkan, dan diterapkan untuk kelengkapan.
Referensi Google
Untuk proto3, tidak ada bidang required
atau default=
. Sebaliknya, kecuali ditandai dengan optional
, semua bidang skalar, string, dan byte secara implisit bersifat opsional. Saat mendekode, jika bidang tersebut tidak ada dalam biner untuk didekode, bidang tersebut selalu didekode ke nilai default tipe spesifik. Pada pengkodean, bidang tersebut hanya disertakan dalam biner yang dikodekan jika memiliki nilai yang berbeda dari nilai default tipe spesifik. Meskipun semua bidang secara implisit bersifat opsional, dapat juga dikatakan bahwa pada tingkat konseptual, semua bidang tersebut selalu memiliki nilai. Saat decoding, tidak mungkin untuk menentukan apakah pada saat pengkodean, ada nilai---dengan nilai tipe spesifik---atau tidak.
Bidang yang ditandai sebagai optional
pada dasarnya direpresentasikan dengan cara yang sama seperti dalam sintaksis proto2; dalam sebuah catatan, bidang tersebut memiliki nilai undefined
jika tidak disetel, dan dalam peta, bidang tersebut tidak ada jika tidak disetel.
Rekomendasi yang pernah saya lihat jika Anda memerlukan deteksi data "hilang", adalah mendefinisikan bidang boolean has_
dan mengaturnya dengan tepat. Alternatif lain adalah dengan menggunakan pesan pembungkus yang terkenal.
Bidang yang merupakan sub-pesan dan salah satu bidang, tidak memiliki jenis default tertentu. Bidang sub-pesan yang tidak disetel akan dikodekan secara berbeda dari bidang sub-pesan yang disetel ke sub-pesan, dan dikodekan secara berbeda. Hal ini berlaku bahkan ketika sub-pesan tidak memiliki kolom. Cara kerjanya agak mirip untuk salah satu bidang. Entah tidak ada satu pun bidang alternatif yang disetel, atau salah satunya disetel. Format yang dikodekan berbeda, dan pada decoding dimungkinkan untuk membedakannya.
Mem-parsing file definisi buffer protokol dan dapat menghasilkan:
Fitur file definisi buffer protokol: gpb mendukung:
packed
dan default
untuk bidangallow_alias
enum (diperlakukan seolah-olah selalu disetel benar)oneof
(diperkenalkan di protobuf 2.6.0)map<_,_>
(diperkenalkan di protobuf 3.0.0)gpb membaca tetapi mengabaikan:
packed
atau default
gpb tidak mendukung:
Karakteristik gpb:
bytes
secara opsional atau kondisional, agar sistem runtime dapat membebaskan biner pesan yang lebih besar.package
dengan menambahkan nama paket ke setiap jenis pesan yang ada di dalamnya (jika ditentukan), yang berguna untuk menghindari bentrokan nama jenis pesan di seluruh paket. Lihat opsi use_packages
atau opsi baris perintah -pkgs
.#field{}
di gpb.hrl untuk fungsi get_msg_defs
, namun hal ini mungkin untuk dihindari ketergantungan ini dengan menggunakan juga opsi defs_as_proplists
atau -pldefs
.Introspeksi
gpb menghasilkan beberapa fungsi untuk memeriksa pesan, enum, dan layanan:
get_msg_defs()
(atau get_proto_defs()
jika introspect_get_proto_defs
disetel), get_msg_names()
, get_enum_names()
find_msg_def(MsgName)
dan fetch_msg_def(MsgName)
find_enum_def(MsgName)
dan fetch_enum_def(MsgName)
enum_symbol_by_value(EnumName, Value)
,enum_symbol_by_value_(Value)
, enum_value_by_symbol(EnumName, Enum)
dan 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)
Ada juga beberapa fungsi untuk menerjemahkan antara nama yang memenuhi syarat dan nama internal. Ini mempertimbangkan opsi penggantian nama apa pun. Mereka mungkin berguna misalnya dengan refleksi grpc.
fqbin_to_service_name(<<"Package.ServiceName">>)
dan service_name_to_fqbin('ServiceName')
fqbins_to_service_and_rpc_name(<<"Package.ServiceName">>, <<"RpcName">>)
dan service_and_rpc_name_to_fqbins('ServiceName', 'RpcName')
fqbin_to_msg_name(<<"Package.MsgName">>)
dan msg_name_to_fqbin('MsgName')
fqbin_to_enum_name(<<"Package.EnumName">>)
dan enum_name_to_fqbin('EnumName')
Ada juga beberapa fungsi untuk menanyakan proto apa yang dimiliki suatu tipe. Setiap tipe dimiliki oleh beberapa "name"
yang berupa string, biasanya nama file, tanpa ekstensi, misalnya "name"
jika file protonya adalah "name.proto"
.
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", ...]
Ada juga beberapa fungsi informasi versi:
gpb:version_as_string()
, gpb:version_as_list()
dan gpb:version_source()
GeneratedCode:version_as_string()
, GeneratedCode:version_as_list()
danGeneratedCode:version_source()
?gpb_version
(dalam gpb_version.hrl)?'GeneratedCode_gpb_version'
(di GeneratedCode.hrl)Gpb juga dapat menghasilkan deskripsi mandiri dari file proto. Deskripsi mandiri adalah deskripsi file proto, yang dikodekan ke biner menggunakan descriptor.proto yang disertakan dengan pustaka buffering protokol Google. Perlu diperhatikan bahwa deskripsi mandiri yang dikodekan tersebut tidak akan sama byte demi byte dengan apa yang akan dihasilkan oleh kompiler buffering protokol Google untuk proto yang sama, namun kira-kira harus setara.
Pesan dan kolom protobuf yang dikodekan secara keliru umumnya akan menyebabkan dekoder mogok. Contoh pengkodean yang salah adalah:
Peta
Gpb dapat menghasilkan encoder/decoder untuk peta.
Opsi maps_unset_optional
dapat digunakan untuk menentukan perilaku bidang opsional yang tidak ada: apakah bidang tersebut dihilangkan dari peta, atau apakah bidang tersebut ada, tetapi memiliki nilai undefined
seperti pada rekaman.
Pelaporan kesalahan dalam file .proto
Gpb tidak pandai melaporkan kesalahan, terutama kesalahan referensi, seperti referensi ke pesan yang tidak ditentukan. Anda mungkin ingin memverifikasi terlebih dahulu dengan protoc
bahwa file .proto valid sebelum memasukkannya ke gpb.
Untuk info tentang cara menggunakan gpb dengan rebar3, lihat https://rebar3.org/docs/configuration/plugins/#protocol-buffers
Di rebar ada dukungan gpb sejak versi 2.6.0. Lihat bagian kompiler proto dari file rebar.sample.config di https://github.com/rebar/rebar/blob/master/rebar.config.sample
Untuk versi rebar yang lebih lama---sebelum 2.6.0---teks di bawah ini menjelaskan cara melanjutkan:
Tempatkan file .proto misalnya di subdirektori proto/
. Subdirektori apa pun, selain src/, boleh saja, karena rebar akan mencoba menggunakan kompiler protobuf lain untuk .proto apa pun yang ditemukan di subdirektori src/. Berikut beberapa baris untuk file 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"}]}.
Nomor versi gpb diambil dari tag git terbaru git yang cocok dengan NM di mana N dan M adalah bilangan bulat. Versi ini dimasukkan ke dalam file gpb.app dan juga ke dalam include/gpb_version.hrl. Versi adalah hasil dari perintah
git describe --always --tags --match '[0-9]*.[0-9]*'
Jadi, untuk membuat versi baru gpb, satu-satunya sumber dari mana versi ini diambil adalah tag git. (Jika Anda mengimpor gpb ke sistem kontrol versi lain selain git, atau menggunakan alat build lain selain rebar, Anda mungkin harus menyesuaikan rebar.config dan src/gpb.app.src. Lihat juga bagian di bawah tentang membangun di luar a git pohon kerja untuk info tentang mengekspor gpb dari git.)
Nomor versi dari perintah git describe
di atas akan terlihat seperti ini
..
(pada master di Github)..--g
(di cabang atau di antara rilis) Nomor versi pada cabang master gpb di Github dimaksudkan untuk selalu berupa bilangan bulat dengan titik, agar kompatibel dengan reltool. Dengan kata lain, setiap dorongan ke cabang master Github dianggap sebagai rilis, dan nomor versinya akan diubah. Untuk memastikan hal ini, ada git hook pre-push
dan dua skrip, install-git-hooks
dan tag-next-minor-vsn
, di subdirektori pembantu. File ChangeLog belum tentu mencerminkan semua perubahan versi kecil, hanya pembaruan penting.
Tempat untuk memperbarui saat membuat versi baru:
Proses pembangunan gpb mengharapkan pohon kerja git (yang tidak dangkal), dengan tag, untuk mendapatkan penomoran versi yang benar, seperti yang dijelaskan di bagian Penomoran versi, namun dimungkinkan juga untuk membangun di luar git. Untuk melakukan itu, Anda memiliki dua opsi:
gpb.vsn
, dengan versi di baris pertamahelpers/mk-versioned-archive
, lalu buka paket arsip dan buat di dalamnya. Jika Anda membuat arsip berversi di pohon git work, versinya akan disetel secara otomatis, jika tidak, Anda harus menentukannya secara manual. Jalankan mk-versioned-archive --help
untuk info tentang opsi apa yang akan digunakan.
Saat mengunduh dari Github, arsip gpb-
Jika Anda menggunakan arsip zip atau tar.gz kode sumber otomatis Github, Anda perlu membuat file gpb.vsn
seperti dijelaskan di atas, atau membuat ulang arsip berversi menggunakan skrip mk-versioned-archive
dan --override-version=
opsi --override-version=
(atau mungkin opsi --override-version-from-cwd-path
jika nama direktori berisi versi yang sesuai.)
Kontribusi dipersilahkan, sebaiknya dalam bentuk permintaan tarik atau permintaan git patch atau git ambil. Berikut adalah beberapa garis panduan:
rebar clean; rebar eunit && rebar doc
Lihat ChangeLog untuk detailnya.
Nilai default untuk opsi maps_unset_optional
telah diubah menjadi omitted
, dari present_undefined
Ini hanya berlaku untuk kode yang dihasilkan dengan opsi peta (-maps). Proyek yang telah menetapkan opsi ini secara eksplisit tidak akan terpengaruh. Proyek yang mengandalkan defaultnya adalah present_undefined
perlu menyetel opsi secara eksplisit untuk meningkatkan ke 4.0.0.
Untuk spesifikasi tipe, defaultnya telah diubah untuk menghasilkannya bila memungkinkan. Opsi {type_specs,false}
(-no_type) dapat digunakan untuk menghindari pembuatan spesifikasi tipe.