gpb는 Erlang용 Google 프로토콜 버퍼 정의 파일용 컴파일러입니다.
바로가기: API 문서 ~ hex.pm의 gpb
x.proto
protobuf 파일이 있다고 가정해 보겠습니다.
message Person {
required string name = 1 ;
required int32 id = 2 ;
optional string email = 3 ;
}
다양한 방법으로 이 정의에 대한 코드를 생성할 수 있습니다. 여기서는 명령줄 도구를 사용합니다. 철근과의 통합에 대한 자세한 내용은 아래를 참조하세요.
# .../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)
기록에서 한 단계 앞선 값을 참조합니다.
프로토부프 유형 | 얼랭 유형 |
---|---|
더블, 플로트 | 플로트() | 무한대 | '-무한대' | 난 인코딩 시 정수도 허용됩니다. |
int32, int64 단위32, 단위64 신트32, 신트64 고정32, 고정64 고정32, 고정64 | 정수() |
부울 | 사실 | 거짓 인코딩 시 정수 1과 0도 허용됩니다. |
열거형 | 원자() 알 수 없는 열거형이 정수()로 디코딩됩니다. |
메시지 | 레코드(따라서 Tuple()) 또는 지도(-maps) 옵션이 지정된 경우 map() |
끈 | 유니코드 문자열, 즉 정수 목록 또는 strings_as_binaries(-strbin) 옵션이 지정된 경우 바이너리() 인코딩 시 iolist도 허용됩니다. |
바이트 | 바이너리() 인코딩 시 iolist도 허용됩니다. |
중 하나 | {ChosenFieldName, 값} 또는 ChosenFieldName => {maps_oneof,Flat}(-maps_oneof flat) 옵션이 지정된 경우의 값 |
지도<_,_> | 2-튜플의 순서가 지정되지 않은 목록, [{Key,Value}] 또는 map()(maps(-maps) 옵션이 지정된 경우) |
반복되는 필드는 목록으로 표시됩니다.
선택 필드는 값으로 표시되거나 설정되지 않은 경우 undefined
으로 표시됩니다. 그러나 맵의 경우 maps_unset_optional
옵션이 omitted
됨으로 설정된 경우 메시지를 인코딩할 때 unset 옵션 값이 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_<field>()
메서드도 포함되어 있으므로 필드가 실제로 존재하는지 여부를 검사할 수 있습니다.
그러나 Erlang에서 필드를 설정하고 읽는 자연스러운 방법은 레코드(또는 맵)에 대한 구문을 사용하는 것입니다. 이는 필드가 존재하는지 여부를 전달하는 동시에 필드를 읽는 좋은 방법을 남기지 않습니다. 기본값.
따라서 gpb
의 접근 방식은 다음 중 하나를 선택해야 한다는 것입니다. 일반적으로 선택적 필드가 존재하는지 여부를 확인하는 것은 가능합니다(예: 값이 undefined
는지 확인). 그러나 대신 기본값으로 디코딩하는 옵션이 컴파일러에 있습니다. 이 경우 필드가 있는지 여부를 확인할 수 있는 기능을 잃게 됩니다. 옵션은 각각 default=<x>
값으로 디코딩하거나 유형별 기본값으로 디코딩하기 위한 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
마지막 대안은 별로 유용하지 않을 수도 있지만 여전히 가능하며 완전성을 위해 구현됩니다.
구글의 참고자료
proto3의 경우 필드에 required
또는 default=<x>
없습니다. 대신, optional
으로 표시하지 않는 한 모든 스칼라 필드, 문자열 및 바이트는 암시적으로 선택 사항입니다. 디코딩 시 이러한 필드가 디코딩할 바이너리에 누락된 경우 항상 유형별 기본값으로 디코딩됩니다. 인코딩 시 이러한 필드는 유형별 기본값과 다른 값을 갖는 경우에만 결과 인코딩 바이너리에 포함됩니다. 모든 필드가 암시적으로 선택 사항이더라도 개념적 수준에서는 이러한 모든 필드에 항상 값이 있다고 말할 수도 있습니다. 디코딩 시에는 인코딩 시 값이 유형별 값으로 존재했는지 여부를 판단할 수 없습니다.
optional
으로 표시된 필드는 기본적으로 proto2 구문과 동일한 방식으로 표시됩니다. 레코드에서 필드가 설정되지 않은 경우 필드는 undefined
값을 가지며, 맵에서는 설정되지 않은 경우 필드가 존재하지 않습니다.
"누락된" 데이터를 검색해야 하는 경우 제가 본 권장 사항은 has_<field>
부울 필드를 정의하고 적절하게 설정하는 것입니다. 또 다른 대안은 잘 알려진 래퍼 메시지를 사용하는 것입니다.
하위 메시지이자 필드 중 하나인 필드에는 유형별 기본값이 없습니다. 설정되지 않은 하위 메시지 필드는 해당 하위 메시지에 설정된 하위 메시지 필드와 다르게 인코딩되고 다르게 디코딩됩니다. 이는 하위 메시지에 필드가 없는 경우에도 마찬가지입니다. 이는 필드 중 하나와 약간 유사하게 작동합니다. 대체 필드 중 하나가 설정되지 않았거나 그 중 하나가 설정되었습니다. 인코딩된 형식은 다르며, 디코딩 시 차이점을 알 수 있습니다.
프로토콜 버퍼 정의 파일을 구문 분석하고 다음을 생성할 수 있습니다.
프로토콜 버퍼 정의 파일의 기능: gpb는 다음을 지원합니다.
packed
및 default
옵션allow_alias
열거형 옵션(항상 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_<EnumName>(Value)
, enum_value_by_symbol(EnumName, Enum)
및 enum_value_by_symbol_<EnumName>(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')
유형이 속한 proto를 쿼리하는 몇 가지 함수도 있습니다. 각 유형은 문자열인 일부 "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 프로토콜 버퍼 컴파일러가 동일한 proto에 대해 생성하는 것과 바이트 단위로 동일하지는 않지만 대략 동일해야 합니다.
잘못 인코딩된 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 파일의 proto 컴파일러 섹션을 참조하세요.
2.6.0 이전 철근 버전의 경우 아래 텍스트에 진행 방법이 설명되어 있습니다.
예를 들어 .proto 파일을 proto/
하위 디렉터리에 배치합니다. src/ 이외의 하위 디렉터리는 괜찮습니다. rebar는 src/ 하위 디렉터리에서 찾은 모든 .proto에 대해 다른 protobuf 컴파일러를 사용하려고 시도하기 때문입니다. 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
명령의 버전 번호는 다음과 같습니다.
<x>.<y>.<z>
(Github의 마스터)<x>.<y>.<z>-<n>-g<sha>
(분기 또는 릴리스 간) Github의 gpb 마스터 브랜치에 있는 버전 번호는 reltool과 호환되도록 항상 점이 있는 정수로만 사용됩니다. 즉, Github의 마스터 브랜치에 푸시할 때마다 릴리스로 간주되어 버전 번호가 올라갑니다. 이를 보장하기 위해 도우미 하위 디렉터리에 pre-push
git 후크와 두 개의 스크립트 install-git-hooks
및 tag-next-minor-vsn
이 있습니다. ChangeLog 파일은 반드시 모든 부 버전 변경 사항을 반영하는 것은 아니며 중요한 업데이트만 반영합니다.
새 버전을 만들 때 업데이트할 위치:
gpb 빌드 프로세스에서는 버전 번호 지정 섹션에 설명된 대로 올바른 버전 번호 지정을 얻기 위해 태그가 있는 (얕지 않은) git 작업 트리를 기대하지만 git 외부에서 빌드하는 것도 가능합니다. 그렇게 하려면 다음 두 가지 옵션이 있습니다.
gpb.vsn
파일을 생성하여 수동으로 버전을 설정합니다.helpers/mk-versioned-archive
스크립트를 사용하여 버전이 지정된 아카이브를 만든 다음 아카이브의 압축을 풀고 그 안에 빌드하세요. Git 작업 트리에서 버전이 지정된 아카이브를 생성하면 버전이 자동으로 설정됩니다. 그렇지 않으면 수동으로 지정해야 합니다. 사용할 옵션에 대한 정보를 보려면 mk-versioned-archive --help
실행하세요.
Github에서 다운로드 시 mk-versioned-archive 스크립트를 이용하여 gpb- <xyz> .tar.gz 아카이브가 생성되어 있으므로, 그냥 압축을 풀고 직접 빌드하는 것이 가능합니다.
Github의 자동 소스 코드 zip 또는 tar.gz 아카이브를 사용하는 경우 위에서 설명한 대로 gpb.vsn
파일을 생성하거나 mk-versioned-archive
스크립트 및 --override-version=<x>
사용하여 버전이 지정된 아카이브를 다시 생성해야 합니다. --override-version=<x>
옵션(또는 디렉토리 이름에 적절한 버전이 포함된 경우 --override-version-from-cwd-path
옵션을 사용할 수도 있습니다.)
기여를 환영합니다. 풀 요청, git 패치 또는 git 페치 요청을 선호합니다. 다음은 몇 가지 지침입니다.
rebar clean; rebar eunit && rebar doc
자세한 내용은 변경 로그를 참조하세요.
maps_unset_optional
옵션의 기본값이 present_undefined
에서 omitted
됨으로 변경되었습니다. 이는 지도(-maps) 옵션으로 생성된 코드에만 적용됩니다. 이 옵션을 이미 명시적으로 설정한 프로젝트는 영향을 받지 않습니다. present_undefined
기본값을 사용하는 프로젝트는 4.0.0으로 업그레이드하려면 명시적으로 옵션을 설정해야 합니다.
유형 사양의 경우 가능한 경우 이를 생성하도록 기본값이 변경되었습니다. 유형 사양 생성을 방지하려면 {type_specs,false}
(-no_type) 옵션을 사용할 수 있습니다.