- Como definir um tipo de mensagem
- Tipos de valor de escala
- Campos opcionais e valores padrão
- Enumerações
- Como usar outros tipos de mensagem
- Tipos aninhados
- Como atualizar um tipo de mensagem
- Extensões
- Um
- Maps
- Pacotes
- Definição de serviços
- Opções
- Como gerar classes
- Local
- Plataformas compatíveis
Este guia descreve como usar a linguagem de buffer de protocolo para estruturar os
dados do buffer de protocolo, incluindo a sintaxe de arquivo .proto
e como gerar classes
de acesso a dados a partir dos arquivos .proto
. Ela abrange a versão proto2 da linguagem dos buffers de protocolo: para mais informações sobre a sintaxe proto3, consulte o Guia da linguagem proto3 (em inglês).
Este é um guia de referência. Para ver um exemplo passo a passo que usa muitos dos recursos descritos neste documento, consulte o tutorial da linguagem escolhida.
Como definir um tipo de mensagem
Primeiro, vamos analisar um exemplo muito simples. Digamos que você queira definir um formato de mensagem de solicitação de pesquisa, em que cada solicitação de pesquisa tem uma string de consulta, a página específica de resultados em que você tem interesse e vários resultados por página. Este é o arquivo .proto
que você usa para definir o tipo de mensagem.
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
A definição da mensagem SearchRequest
especifica três campos (pares de nome/valor), um para cada dado que você quer incluir nesse tipo de mensagem. Cada campo tem um nome e um tipo.
Como especificar tipos de campo
No exemplo acima, todos os campos são tipos escalares: dois números inteiros
(page_number
e result_per_page
) e uma string (query
). No entanto, também é possível
especificar tipos compostos para seus campos, incluindo enumerações
e outros tipos de mensagens.
Atribuir números de campo
Como você pode ver, cada campo na definição da mensagem tem um número único. Esses números são usados para identificar seus campos no formato binário de mensagem e não podem ser alterados quando o tipo de mensagem estiver em uso. Os números de campo no intervalo de 1 a 15 levam um byte para codificar, incluindo o número do campo e o tipo do campo. Saiba mais sobre isso em Codificação do buffer de protocolo. Os números de campo no intervalo 16 a 2047 usam dois bytes. Portanto, reserve os números de campo de 1 a 15 para elementos de mensagem muito frequentes. Lembre-se de deixar espaço para elementos que ocorrem com frequência e que podem ser adicionados no futuro.
O menor número de campo que você pode especificar é 1, e o maior é 229 - 1 ou 536.870.911. Também não é possível usar os números de 19.000
a 19.999 (FieldDescriptor::kFirstReservedNumber
a
FieldDescriptor::kLastReservedNumber
), já que eles são reservados para a implementação
de buffers de protocolo. O compilador de buffer de protocolo vai reclamar se você usar
um desses números reservados na .proto
. Da mesma forma, não é possível usar números de campo reservados anteriormente.
Como especificar regras de campo
Você especifica que os campos da mensagem são um dos seguintes:
required
: uma mensagem bem formada precisa ter exatamente um desse campo.optional
: uma mensagem bem formada pode ter zero ou um desse campo, mas não mais de um.repeated
: este campo pode ser repetido qualquer número de vezes (incluindo zero) em uma mensagem bem formada. A ordem dos valores repetidos será preservada.
Por motivos históricos, os campos de repeated
dos tipos numéricos escalares (por exemplo,
int32
, int64
e enum
) não são codificados da maneira mais eficiente possível. O novo
código precisa usar a opção especial [packed = true]
para ter uma codificação
mais eficiente. Exemplo:
repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];
Saiba mais sobre a codificação packed
em
Codificação do buffer de protocolo.
Obrigatório Sempre: tenha muito cuidado ao marcar campos como
required
. Se em algum momento você quiser parar de escrever ou enviar um campo
obrigatório, será problemático mudar para um campo opcional. Leitores
antigos considerarão mensagens sem esse campo incompletas e poderão
rejeitá-lo ou soltá-lo acidentalmente. Considere escrever
rotinas de validação personalizadas específicas do aplicativo para os buffers.
Um segundo problema com campos obrigatórios aparece quando alguém adiciona um valor a um enum. Nesse caso, o valor de enumeração não reconhecido é tratado como se estivesse ausente, o que também faz com que a verificação de valor necessária falhe.
Como adicionar mais tipos de mensagens
Vários tipos de mensagem podem ser definidos em um único arquivo .proto
. Isso é útil
ao definir várias mensagens relacionadas. Assim, por exemplo, se você quiser
definir o formato da mensagem de resposta que corresponde ao tipo de mensagem SearchResponse
, poderá adicioná-lo ao mesmo .proto
:
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
...
}
Combinar mensagens gera sobrecarga Embora vários tipos de mensagens (como
mensagem, enumeração e serviço) possam ser definidos em um único arquivo .proto
, eles também
podem levar a sobrecarga de dependência quando um grande número de mensagens com dependências
variadas é definido em um único arquivo. É recomendável incluir o menor número possível
de tipos de mensagens por arquivo .proto
.
Adicionar comentários
Para adicionar comentários aos seus arquivos .proto
, use a sintaxe //
e /* ... */
no estilo C/C++.
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
required string query = 1;
optional int32 page_number = 2; // Which page number do we want?
optional int32 result_per_page = 3; // Number of results to return per page.
}
Campos reservados
Se você atualizar um tipo de mensagem removendo ou comentando um campo, os futuros usuários poderão reutilizar o número do campo ao fazer as próprias atualizações no tipo. Isso pode causar problemas graves se posteriormente eles carregarem
versões antigas do mesmo .proto
, incluindo corrupção de dados, bugs de privacidade
e assim por diante. Uma maneira de garantir que isso não aconteça é especificar os números
de campo (e/ou nomes, que também podem causar problemas para a serialização JSON) dos
seus campos excluídos que são reserved
. O compilador de buffer de protocolo informará se algum usuário futuro tentar usar esses identificadores de campo.
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
Os intervalos de números de campo reservados são inclusivos (9 to 11
é igual a 9, 10,
11
). Não é possível misturar nomes e números de campo na mesma instrução reserved
.
O que é gerado pelo seu .proto
?
Quando você executa o compilador de buffer de protocolo em um .proto
, o
compilador gera o código na linguagem escolhida, com os
tipos de mensagens descritos no arquivo, incluindo receber e definir valores
de campo, serializar as mensagens para um stream de saída e analisar suas mensagens
de um stream de entrada.
- Para C++, o compilador gera um arquivo
.h
e.cc
de cada.proto
, com uma classe para cada tipo de mensagem descrita no arquivo. - Para Java, o compilador gera um arquivo
.java
com uma classe para cada tipo de mensagem, bem como classesBuilder
especiais para criar instâncias de classe de mensagem. - O Python é um pouco diferente. O compilador Python gera um módulo
com um descritor estático de cada tipo de mensagem no
.proto
, que é usado com uma metaclasse para criar a classe de acesso a dados Python necessária no ambiente de execução. - Para Go, o compilador gera um arquivo
.pb.go
com um tipo para cada tipo de mensagem no seu arquivo.
Saiba mais sobre como usar as APIs de cada idioma seguindo o tutorial para o idioma escolhido. Para ver mais detalhes da API, consulte a Referência da API relevante.
Tipos de valor de escala
Um campo de mensagem escalar pode ter um dos seguintes tipos: a tabela mostra o tipo especificado no arquivo .proto
e o tipo correspondente na classe gerada automaticamente:
Tipo de .proto | Observações | Tipo de C++ | Tipo de Java | Tipo do Python[2] | Tipo de Go |
---|---|---|---|---|---|
double | double | double | float | *float64 | |
float | float | float | float | *float32 | |
int32 | Usa codificação de comprimento variável. Ineficiente para codificar números negativos: se o campo provavelmente tiver valores negativos, use sint32. | int32 | int | int | *int32 |
int64 | Usa codificação de comprimento variável. Ineficiente para codificar números negativos: se o campo tiver probabilidade de ter valores negativos, use sint64. | int64 | long | int/long[3] | *int64 |
uint32 | Usa codificação de comprimento variável. | uint32 | int[1] | int/long[3] | *uint32 |
uint64 | Usa codificação de comprimento variável. | uint64 | longo[1] | int/long[3] | *uint64. |
Sint32 | Usa codificação de comprimento variável. Valor int assinado. Eles codificam números negativos com mais eficiência do que os int32s normais. | int32 | int | int | *int32 |
St64 | Usa codificação de comprimento variável. Valor int assinado. Eles codificam números de maneira mais eficiente do que os int64s normais. | int64 | long | int/long[3] | *int64 |
corrigido32 | Sempre quatro bytes. Mais eficiente que uint32 se os valores geralmente forem maiores que 228 | uint32 | int[1] | int/long[3] | *uint32 |
corrigido64 | Sempre oito bytes. Mais eficiente que uint64 se os valores geralmente forem maiores que 256 | uint64 | longo[1] | int/long[3] | *uint64. |
fixo | Sempre quatro bytes. | int32 | int | int | *int32 |
recortado em 64 | Sempre oito bytes. | int64 | long | int/long[3] | *int64 |
bool | bool | boolean | bool | *bool | |
string | Uma string precisa sempre conter texto codificado em UTF-8. | string | String | Unicode (Python 2) ou str (Python 3) | *string |
bytes | Pode conter qualquer sequência arbitrária de bytes. | string | ByteString | bytes | []byte |
Saiba mais sobre como esses tipos são codificados ao serializar a mensagem em Codificação do buffer de protocolo.
[1] Em Java, os números inteiros de 32 e 64 bits não assinados são representados usando as contrapartes assinadas, com o bit superior sendo armazenado apenas no bit.
[2] Em todos os casos, a definição de valores para um campo executa uma verificação de tipo para garantir que ela é válida.
[3] Números inteiros de 62 bits ou não assinados de 32 bits são sempre representados como longos quando decodificados, mas podem ser um int se um int for fornecido ao definir o campo. Em todos os casos, o valor precisa caber no tipo representado quando definido. Consulte [2].
Campos opcionais e valores padrão
Como mencionado acima, os elementos na descrição de uma mensagem podem ser rotulados como optional
.
Uma mensagem bem formada pode ou não conter um elemento opcional. Quando uma mensagem
for analisada, se ela não contiver um elemento opcional, acessar o
campo correspondente no objeto analisado retornará o valor padrão
desse campo. O valor padrão pode ser especificado como parte da descrição da mensagem.
Por exemplo, digamos que você queira fornecer um valor padrão de 10 para o
valor result_per_page
de SearchRequest
.
optional int32 result_per_page = 3 [default = 10];
Se o valor padrão não for especificado para um elemento opcional, um valor padrão específico do tipo será usado: para strings, o valor padrão vai ser a string vazia. Para bytes, o valor padrão é a string vazia de bytes. Para booleanos, o valor padrão é "false". Para tipos numéricos, o valor padrão é zero. Para enums, o valor padrão é o primeiro valor listado na definição do tipo de enum. Isso significa que é preciso ter cuidado ao adicionar um valor ao início de uma lista de valores de enumeração. Consulte a seção Como atualizar um tipo de mensagem para ver orientações sobre como alterar as definições com segurança.
Enumerações
Ao definir um tipo de mensagem, talvez você queira que um dos campos tenha apenas uma de uma lista predefinida de valores. Por exemplo, digamos que você queira adicionar
um campo corpus
para cada SearchRequest
, em que o corpus pode ser UNIVERSAL
,
WEB
, IMAGES
, LOCAL
, NEWS
, PRODUCTS
ou VIDEO
. Para fazer isso, basta
adicionar um enum
à definição da mensagem. Um campo com um tipo enum
só pode ter um de um conjunto especificado de constantes como o valor dele. Se você tentar
fornecer um valor diferente, o analisador vai tratá-lo como um campo desconhecido.
No exemplo a seguir, adicionamos uma enum
chamada Corpus
com todos os valores possíveis e um campo do tipo Corpus
:
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
}
Você pode definir aliases atribuindo o mesmo valor a diferentes constantes de enumeração.
Para fazer isso, defina a opção allow_alias
como true
. Caso contrário, o
compilador de buffer de protocolo gera uma mensagem de erro quando aliases são encontrados.
Embora todos os valores de alias sejam válidos durante a desserialização, o primeiro valor é
sempre usado na serialização.
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
ENAA_FINISHED = 2;
}
As constantes do enumerador precisam estar no intervalo de um número inteiro de 32 bits. Como os valores enum
usam codificação variável no fio,
os valores negativos são ineficientes e, portanto, não são recomendados. Você pode definir enum
s dentro ou fora de uma definição de mensagem. Esses enum
s podem ser reutilizados em qualquer definição de mensagem no arquivo .proto
. Também é possível usar
um tipo enum
declarado em uma mensagem como o tipo de um campo em uma mensagem
diferente, usando a sintaxe _MessageType_._EnumType_
.
Quando você executa o compilador de buffer de protocolo em um .proto
que usa um enum
, o
código gerado tem um enum
correspondente para Java ou C++ ou uma classe
EnumDescriptor
especial para Python que é usada para criar um conjunto de constantes
simbólicas com valores inteiros na classe gerada pelo ambiente de execução.
Para mais informações sobre como trabalhar com mensagens enum
nos seus
aplicativos, consulte o
guia de código gerado para sua
linguagem escolhida.
Valores reservados
Se você atualizar um tipo de enumeração removendo completamente uma entrada de enumeração ou
comentando-a, os usuários futuros poderão reutilizar o valor numérico ao fazer
as próprias atualizações no tipo. Isso pode causar problemas graves se posteriormente eles carregarem
versões antigas do mesmo .proto
, incluindo corrupção de dados, bugs de privacidade
e assim por diante. Uma maneira de garantir que isso não aconteça é especificar os valores numéricos (e/ou nomes, que também podem causar problemas para a serialização JSON) das suas entradas excluídas são reserved
. O compilador de buffer de protocolo reclamará
se algum usuário futuro tentar usar esses identificadores. É possível especificar que o intervalo de valores numéricos reservado vá para o valor máximo possível usando a palavra-chave max
.
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
Não é possível misturar nomes de campos e valores numéricos na mesma instrução reserved
.
Como usar outros tipos de mensagem
Você pode usar outros tipos de mensagem como campos. Por exemplo, digamos que você queira incluir mensagens Result
em cada mensagem SearchResponse
. Para fazer isso, defina um tipo de mensagem Result
na mesma .proto
e especifique um campo do tipo Result
em SearchResponse
:
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
Como importar definições
No exemplo acima, o tipo de mensagem Result
é definido no mesmo arquivo que SearchResponse
. E se o tipo de mensagem que você quer usar como tipo de campo já estiver definido em outro arquivo .proto
?
Importe definições de outros arquivos .proto
para usá-las. Para importar
as definições de outro .proto
, adicione uma instrução de importação na parte de cima do
arquivo:
import "myproject/other_protos.proto";
Por padrão, só é possível usar definições de arquivos .proto
importados diretamente.
No entanto, às vezes é necessário mover um arquivo .proto
para um novo local.
Em vez de mover o arquivo .proto
diretamente e atualizar todos os sites de chamada em
uma única mudança, é possível colocar um arquivo .proto
de marcador no local antigo para
encaminhar todas as importações para o novo local usando a noção import public
.
A funcionalidade de importação pública não está disponível em Java.
As dependências import public
podem ser usadas de maneira transitiva por qualquer código
que importe o proto que contém a instrução import public
. Exemplo:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
O compilador de protocolo procura arquivos importados em um conjunto de diretórios
especificado na linha de comando do compilador de protocolos usando a sinalização
-I
/--proto_path
. Se nenhuma sinalização for fornecida, ela procurará no diretório em que o compilador foi invocado. Em geral, defina a sinalização --proto_path
na raiz do projeto e use nomes totalmente qualificados para todas as importações.
Como usar tipos de mensagem proto3
É possível importar tipos de mensagens proto3 e usá-los nas suas mensagens proto2 e vice-versa. No entanto, enumerações proto2 não podem ser usadas na sintaxe proto3.
Tipos aninhados
É possível definir e usar tipos de mensagem dentro de outros tipos de mensagem, como no
exemplo a seguir. Aqui, a mensagem Result
é definida dentro da
mensagem SearchResponse
:
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
Se você quiser reutilizar esse tipo de mensagem fora do tipo de mensagem pai, refere-se a ele como _Parent_._Type_
:
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}
Você pode aninhar mensagens profundamente. No exemplo abaixo, os dois tipos aninhados chamados Inner
são totalmente independentes, já que são definidos em mensagens diferentes:
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
optional int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
optional string name = 1;
optional bool flag = 2;
}
}
}
Grupos
O recurso de grupos foi descontinuado e não deve ser usado ao criar novos tipos de mensagem. Use os tipos de mensagens aninhados.
Os grupos são outra forma de aninhar informações nas definições das suas mensagens. Por
exemplo, outra maneira de especificar um SearchResponse
contendo um número de
Result
s é o seguinte:
message SearchResponse {
repeated group Result = 1 {
required string url = 2;
optional string title = 3;
repeated string snippets = 4;
}
}
Um grupo simplesmente combina um tipo de mensagem aninhada e um campo em uma única
declaração. No código, você pode tratar essa mensagem como se ela tivesse um campo de tipo Result
chamado result
(o último nome é convertido em letras minúsculas para que não entre em conflito com o primeiro). Portanto, esse exemplo é
exatamente equivalente ao SearchResponse
acima, exceto pelo fato da mensagem ter um
formato de fio diferente.
Como atualizar um tipo de mensagem
Se um tipo de mensagem existente não atende mais a todas as suas necessidades, por exemplo, você quer que o formato da mensagem tenha um campo extra, mas ainda quer usar o código criado com o formato antigo, não se preocupe. É muito simples atualizar os tipos de mensagem sem corromper nenhum código existente quando você usa o formato de rede binária.
Se você estiver usando o formato de transmissão binária, verifique as seguintes regras:
- Não altere os números dos campos existentes.
- Todos os novos campos adicionados precisam ser
optional
ourepeated
. Isso significa que qualquer mensagem serializada por código usando o formato de mensagem "antigo" pode ser analisada pelo novo código gerado, já que não vai faltar nenhum elementorequired
. Configure valores padrão adequados para esses elementos de modo que o novo código possa interagir corretamente com as mensagens geradas pelo código antigo. Da mesma forma, as mensagens criadas pelo novo código podem ser analisadas pelo código antigo: os binários antigos ignoram o novo campo durante a análise. No entanto, os campos desconhecidos não serão descartados e, se a mensagem for serializada posteriormente, os campos desconhecidos serão serializados com ela. Assim, se a mensagem for transmitida para o novo código, os novos campos ainda estarão disponíveis. - Os campos não obrigatórios podem ser removidos, desde que o número do campo não seja usado novamente no tipo de mensagem atualizado. Você pode renomear o campo,
talvez adicionando o prefixo "OBSOLETE_", ou tornar o número do campo
reservado para que futuros usuários do
.proto
não possam reutilizar o número acidentalmente. - Um campo não obrigatório pode ser convertido em uma extensão e vice-versa, desde que o tipo e o número permaneçam os mesmos.
int32
,uint32
,int64
,uint64
ebool
são compatíveis. Isso significa que é possível mudar um campo de um desses tipos para outro sem comprometer a compatibilidade com versões anteriores ou futuras. Se um número for analisado no fio que não cabe no tipo correspondente, você terá o mesmo efeito que se tivesse transmitido o número para esse tipo em C++. Por exemplo, se um número de 64 bits for lido como um int32, ele será truncado para 32 bits.sint32
esint64
são compatíveis entre si, mas não com os outros tipos de números inteiros.string
ebytes
são compatíveis, desde que os bytes sejam UTF-8 válidos.- As mensagens incorporadas serão compatíveis com
bytes
se os bytes tiverem uma versão codificada da mensagem. fixed32
é compatível comsfixed32
efixed64
comsfixed64
.- Para os campos
string
,bytes
e mensagem,optional
é compatível comrepeated
. Com os dados serializados de um campo repetido como entrada, os clientes que esperam esse campo comooptional
vão receber o último valor de entrada se for um campo de tipo primitivo ou mesclar todos os elementos de entrada se for um campo de tipo de mensagem. Geralmente, isso não é seguro para tipos numéricos, incluindo bools e enumerações. Os campos repetidos de tipos numéricos podem ser serializados no formato empacotado, que não será analisado corretamente quando um campooptional
for esperado. - Normalmente, alterar um valor padrão é aceitável, desde que esses valores nunca sejam enviados por transferência. Assim, se um programa receber uma mensagem em que um determinado campo não esteja definido, o programa verá o valor padrão conforme definido na versão do protocolo. Ele NÃO verá o valor padrão que foi definido no código do remetente.
enum
é compatível comint32
,uint32
,int64
euint64
em termos de formato eletrônico (os valores serão truncados se não caberem), mas o código do cliente pode tratá-los de maneira diferente quando a mensagem for desserializada. Os valoresenum
não reconhecidos são descartados quando a mensagem é desserializada, o que faz com que o acessadorhas..
do campo retorne false e o getter retorne o primeiro valor listado na definiçãoenum
ou o valor padrão, se houver um. No caso de campos de enumeração repetidos, todos os valores não reconhecidos são removidos da lista. No entanto, um campo inteiro sempre preservará o valor dele. Por isso, é necessário ter muito cuidado ao fazer upgrade de um número inteiro para umenum
em termos de recebimento de valores de enumeração fora dos limites no fio.- Nas implementações atuais do Java e do C++, quando os valores
enum
não reconhecidos são removidos, eles são armazenados com outros campos desconhecidos. Isso poderá causar um comportamento estranho se esses dados forem serializados e analisados novamente por um cliente que reconhece esses valores. No caso de campos opcionais, mesmo que um novo valor tenha sido gravado após a desserialização da mensagem original, o valor antigo ainda será lido por clientes que o reconhecem. No caso de campos repetidos, os valores antigos aparecerão depois de todos os valores reconhecidos e adicionados recentemente, o que significa que a ordem não será preservada. - A mudança de um único campo ou extensão do
optional
para um membro de um novooneof
é compatível com binários, mas, em algumas linguagens, como a Go, a API do código gerado será alterada de maneira incompatível. Por esse motivo, o Google não faz essas mudanças nas APIs públicas, conforme documentado na AIP-180 (em inglês). Com o mesmo aviso sobre compatibilidade de origem, mover vários campos para um novooneof
pode ser seguro se você tiver certeza de que nenhum código define mais de um por vez. Não é seguro mover campos para umoneof
existente. Da mesma forma, é seguro alterar um único campooneof
para um campo ou extensãooptional
. - A mudança de um campo entre uma
map<K, V>
e o campo de mensagemrepeated
correspondente é compatível com binários (consulte Mapas abaixo, para ver o layout das mensagens e outras restrições). No entanto, a segurança da alteração depende do aplicativo: ao desserializar e reserializar uma mensagem, os clientes que usam a definição de camporepeated
produzirão um resultado semanticamente idêntico. No entanto, os clientes que usam a definição de campomap
podem reordenar entradas e soltar entradas com chaves duplicadas.
Extensões
Com as extensões, é possível declarar que um intervalo de números de campo em uma mensagem está
disponível para extensões de terceiros. Uma extensão é um marcador de posição para um campo
cujo tipo não é definido pelo arquivo .proto
original. Isso permite que outros
arquivos .proto
sejam adicionados à definição da mensagem definindo os tipos de alguns
ou de todos os campos com esses números de campo. Vejamos um exemplo:
message Foo {
// ...
extensions 100 to 199;
}
Isso informa que o intervalo de números de campo [100, 199] em Foo
é reservado para
extensões. Outros usuários agora podem adicionar novos campos a Foo
nos próprios arquivos .proto
que importam seu .proto
, usando números de campo dentro do intervalo especificado, por exemplo:
extend Foo {
optional int32 bar = 126;
}
Isso adiciona um campo chamado bar
com o campo número 126 à definição original de Foo
.
Quando as mensagens Foo
do usuário são codificadas, o formato de transmissão é exatamente o mesmo
se o usuário definir o novo campo dentro de Foo
. No entanto, a maneira como você acessa
os campos de extensão no código do aplicativo é um pouco diferente do acesso
a campos regulares. O código de acesso a dados gerado tem acessadores especiais para
trabalhar com extensões. Por exemplo, veja como definir o valor de bar
em C++:
Foo foo;
foo.SetExtension(bar, 15);
Da mesma forma, a classe Foo
define os acessadores com modelo HasExtension()
,
ClearExtension()
, GetExtension()
, MutableExtension()
e
AddExtension()
. Todos têm semânticas que correspondem aos acessadores gerados
correspondentes para um campo normal. Para saber mais sobre como trabalhar com
extensões, consulte a referência de código gerada para a linguagem escolhida.
As extensões podem ser de qualquer tipo, incluindo de mensagens, mas não podem ser mapas ou mapeamentos.
Extensões aninhadas
É possível declarar extensões no escopo de outro tipo:
message Baz {
extend Foo {
optional int32 bar = 126;
}
...
}
Nesse caso, o código C++ para acessar essa extensão é:
Foo foo;
foo.SetExtension(Baz::bar, 15);
Em outras palavras, o único efeito é que bar
é definido no escopo de
Baz
.
Essa é uma fonte comum de confusão: declarar um bloco extend
aninhado dentro
de um tipo de mensagem não implica nenhuma relação entre o tipo externo e o
tipo estendido. O exemplo acima não significa que Baz
é
qualquer tipo de subclasse de Foo
. Isso significa que o símbolo bar
é declarado
no escopo de Baz
. Ele é simplesmente um membro estático.
Um padrão comum é definir extensões dentro do escopo do tipo de campo
de extensão. Por exemplo, veja uma extensão para Foo
do tipo Baz
, em que a
extensão é definida como parte de Baz
:
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
...
}
No entanto, não é necessário definir uma extensão com esse tipo de mensagem. Você também pode fazer o seguinte:
message Baz {
...
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}
Na verdade, é melhor usar essa sintaxe para evitar confusão. Como mencionado acima, a sintaxe aninhada geralmente é confundida com subclasses de usuários que ainda não conhecem extensões.
Como escolher números de extensão
É muito importante garantir que dois usuários não adicionem extensões ao mesmo tipo de mensagem usando o mesmo número de campo. A corrupção de dados pode ocorrer se uma extensão for interpretada acidentalmente como o tipo errado. Para evitar que isso aconteça, defina uma convenção de numeração de extensão para o projeto.
Se a convenção de numeração puder envolver extensões com números de campo muito grandes, especifique o intervalo da extensão até o número máximo de campos possível usando a palavra-chave max
:
message Foo {
extensions 1000 to max;
}
max
é 229 - 1, ou 536.870.911.
Assim como acontece ao escolher números de campo em geral, sua convenção de numeração também precisa
evitar os números de campos 19000 a 19999
(FieldDescriptor::kFirstReservedNumber
a
FieldDescriptor::kLastReservedNumber
), já que eles são reservados para a implementação de
buffers de protocolo. É possível definir um intervalo de extensões que inclui esse
intervalo, mas o compilador de protocolos não permitirá que você defina extensões reais
com esses números.
Oneof
Se você tiver uma mensagem com muitos campos opcionais e em que no máximo um campo será definido ao mesmo tempo, será possível aplicar esse comportamento e economizar memória usando o recurso oneof.
Um dos campos é como campos opcionais, exceto todos os campos em uma só da memória de compartilhamento, e no máximo um campo pode ser definido ao mesmo tempo. Definir um membro
dele remove todos os outros membros automaticamente. É possível verificar qual valor em um
de um está definido (se houver) usando um método especial case()
ou WhichOneof()
,
dependendo do idioma escolhido.
Como usar o Oneof
Para definir um único no .proto
, use a palavra-chave oneof
seguida pelo
nome, neste caso test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
Em seguida, adicione os campos oneof à definição oneof. É possível adicionar campos de qualquer tipo, mas não é possível usar as palavras-chave required
, optional
ou repeated
. Se
você precisar adicionar um campo repetido a um campo desse tipo, use uma mensagem com
o campo repetido.
No código gerado, um dos campos tem os mesmos getters e setters que os
métodos optional
normais. Você também terá um método especial para verificar qual
valor, se houver algum, está definido. Saiba mais sobre a API oneof
para a linguagem escolhida na
referência da API relevante.
Um recurso
Definir um campo "oneof" limpará automaticamente todos os outros membros desse campo. Portanto, se você definir vários campos "oneof", apenas o último campo ainda terá um valor.
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
Se o analisador encontrar vários membros do mesmo elemento na conversa, apenas o último membro visto será usado na mensagem analisada.
Não é possível usar uma extensão.
Um deles não pode ser
repeated
.As APIs Reflection funcionam para um dos campos.
Se você definir um campo oneof com o valor padrão (como definir um campo onet32 um como 0), o "caso" desse campo oneof será definido e o valor será serializado no fio.
Se você estiver usando C++, verifique se o código não causa falhas de memória. O exemplo de código a seguir falhará porque
sub_message
já foi excluído chamando o métodoset_name()
.SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
Novamente em C++, se você
Swap()
duas mensagens com uma delas, cada uma acabará com a outra em um caso. No exemplo abaixo,msg1
terá umasub_message
emsg2
terá umaname
.SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
Problemas de compatibilidade com versões anteriores
Tenha cuidado ao adicionar ou remover um dos campos. Se a verificação do valor de um
devolver retornar None
/NOT_SET
, isso pode significar que ele não foi definido ou
foi configurado para um campo em uma versão diferente do um. Não há como
identificar a diferença, já que não há como saber se um campo desconhecido na
fiação é membro de um deles.
Problemas de reutilização de tags
- Mover campos opcionais de/para um: é possível que algumas das suas informações sejam perdidas (alguns campos serão apagados) depois que a mensagem for serializada e analisada. No entanto, é possível mover um único campo para um novo com segurança e ser capaz de mover vários campos, desde que apenas um campo seja definido. Consulte Como atualizar um tipo de mensagem para mais detalhes.
- Excluir um campo oneof e adicioná-lo novamente: isso pode limpar o campo oneof definido atualmente após a mensagem ser serializada e analisada.
- Dividir ou mesclar um de: isso tem problemas semelhantes à movimentação de campos
optional
normais.
Mapas
Se você quiser criar um mapa associativo como parte da definição de dados, os buffers de protocolo fornecem uma sintaxe de atalho útil:
map<key_type, value_type> map_field = N;
... em que o key_type
pode ser qualquer tipo de string ou integral. Portanto, qualquer
tipo escalar, exceto tipos de ponto flutuante e bytes
. Observe que
enum não é um key_type
válido. O value_type
pode ser qualquer tipo, exceto outro mapa.
Por exemplo, se você quiser criar um mapa de projetos em que cada mensagem Project
esteja associada a uma chave de string, defina-a da seguinte maneira:
map<string, Project> projects = 3;
No momento, a API de mapa gerada está disponível para todos os idiomas compatíveis com o proto2. Saiba mais sobre a API do mapa para o idioma escolhido na Referência da API relevante.
Recursos do Maps
- Extensões não são aceitas em mapas.
- Os mapas não podem ser
repeated
,optional
ourequired
. - A ordem dos formatos de mapa e a iteração do mapa dos valores do mapa são indefinidos. Portanto, não é possível confiar que os itens do mapa estão em uma ordem específica.
- Ao gerar o formato de texto para uma
.proto
, os mapas são classificados por chave. As chaves numéricas são classificadas numericamente. - Ao fazer a análise do fio ou ao mesclar, a última chave vista será usada se houver chaves de mapa duplicadas. Ao analisar um mapa a partir do formato de texto, a análise poderá falhar se houver chaves duplicadas.
Compatibilidade com versões anteriores
A sintaxe do mapa é semelhante à que está descrita abaixo no fio. Assim, as implementações de buffers de protocolo que não aceitam mapas ainda podem processar seus dados:
message MapFieldEntry {
optional key_type key = 1;
optional value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Qualquer implementação de buffers de protocolo com suporte a mapas precisa produzir e aceitar dados que possam ser aceitos pela definição acima.
Entrega de pacotes
É possível adicionar um especificador package
opcional a um arquivo .proto
para evitar conflitos
de nome entre tipos de mensagens de protocolo.
package foo.bar;
message Open { ... }
É possível usar o especificador de pacote ao definir campos do tipo de mensagem:
message Foo {
...
required foo.bar.Open open = 1;
...
}
A forma como um especificador de pacote afeta o código gerado depende da linguagem escolhida:
- Em C++, as classes geradas são encapsuladas em um namespace do C++. Por
exemplo,
Open
está no namespacefoo::bar
. - Em Java, o pacote é usado como o pacote Java, a menos que você forneça explicitamente um
option java_package
no arquivo.proto
. - Em Python, a diretiva
package
é ignorada, porque os módulos do Python são organizados de acordo com a localização deles no sistema de arquivos. - Em Go, a diretiva
package
é ignorada, e o arquivo.pb.go
gerado está no pacote nomeado com base na regrago_proto_library
correspondente.
Mesmo que a diretiva package
não afete diretamente o
código gerado, por exemplo, em Python, ainda é altamente recomendável
especificar o pacote para o arquivo .proto
. Caso contrário, isso pode levar a conflitos
de nomenclatura em descritores e tornar o proto não portátil para outras linguagens.
Resolução de pacotes e nomes
A resolução de nomes de tipo na linguagem de buffer de protocolo funciona como C++: primeiro, o
escopo mais interno é pesquisado, depois o próximo e mais próximo, e assim por diante, com cada
pacote considerado "interno" para o pacote pai. Um "." inicial (por
exemplo, .foo.bar.Baz
) significa começar do escopo mais externo.
O compilador de buffer de protocolo resolve todos os nomes de tipo analisando os arquivos
.proto
importados. O gerador de código para cada linguagem sabe como se referir a cada
tipo nessa linguagem, mesmo que tenha diferentes regras de escopo.
Definição de serviços
Se você quiser usar os tipos de mensagens com um sistema de chamada de procedimento remoto (RPC, na sigla em inglês), é possível definir uma interface de serviço de RPC em um arquivo .proto
. O compilador de buffer de protocolo gerará o código da interface de serviço e stubs na linguagem escolhida. Por exemplo, se você quiser definir um serviço de RPC com um
método que use SearchRequest
e retorne SearchResponse
, defina-o
no arquivo .proto
da seguinte maneira:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
Por padrão, o compilador de protocolos gerará uma interface abstrata
chamada SearchService
e uma implementação de "stub" correspondente. O stub
encaminha todas as chamadas para um RpcChannel
, que, por sua vez, é uma interface abstrata
que você precisa definir como seu próprio sistema de RPC. Por exemplo, você
pode implementar um RpcChannel
que serializa a mensagem e a envia para um
servidor por HTTP. Em outras palavras, o stub gerado fornece uma interface segura para fazer chamadas de RPC baseadas em buffer de protocolo, sem bloquear você em qualquer implementação de RPC específica. Assim, em C++, você pode acabar com um código como
este:
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch() {
// You provide classes MyRpcChannel and MyRpcController, which implement
// the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
channel = new MyRpcChannel("somehost.example.com:1234");
controller = new MyRpcController;
// The protocol compiler generates the SearchService class based on the
// definition given above.
service = new SearchService::Stub(channel);
// Set up the request.
request.set_query("protocol buffers");
// Execute the RPC.
service->Search(controller, &request, &response,
protobuf::NewCallback(&Done));
}
void Done() {
delete service;
delete channel;
delete controller;
}
Todas as classes de serviço também implementam a interface Service
, que fornece uma maneira
de chamar métodos específicos sem saber o nome do método ou os tipos de entrada e saída
no momento da compilação. No lado do servidor, isso pode ser usado para implementar um servidor
RPC com o qual você possa registrar serviços.
using google::protobuf;
class ExampleSearchService : public SearchService {
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done) {
if (request->query() == "google") {
response->add_result()->set_url("http://www.google.com");
} else if (request->query() == "protocol buffers") {
response->add_result()->set_url("http://protobuf.googlecode.com");
}
done->Run();
}
};
int main() {
// You provide class MyRpcServer. It does not have to implement any
// particular interface; this is just an example.
MyRpcServer server;
protobuf::Service* service = new ExampleSearchService;
server.ExportOnPort(1234, service);
server.Run();
delete service;
return 0;
}
Se você não quiser conectar seu próprio sistema RPC atual, poderá usar o
gRPC: um sistema RPC de código aberto de linguagem neutra
e plataforma desenvolvido no Google. O gRPC funciona muito bem com
buffers de protocolo e permite gerar o código RPC relevante diretamente dos seus
arquivos .proto
usando um plug-in especial do compilador de buffer de protocolo. No entanto, como há
possíveis problemas de compatibilidade entre clientes e servidores gerados
com proto2 e proto3, recomendamos que você use o proto3 para definir serviços gRPC. Saiba mais sobre a sintaxe do proto3 no
Guia da linguagem Proto3. Se você quiser usar
o proto2 com o gRPC, precisará usar a versão 3.0.0 ou mais recente do compilador
e das bibliotecas de buffers de protocolo.
Além do gRPC, há diversos outros projetos em andamento para implementar implementações de RPC para buffers de protocolo. Para ver uma lista de links para projetos que conhecemos, consulte a página de wikis de complementos de terceiros.
Opções
Declarações individuais em um arquivo .proto
podem ter várias
opções. As opções não mudam o significado geral de uma declaração, mas podem
afetar a forma como ela é processada em um contexto específico. A lista completa de
opções disponíveis é definida em
/google/protobuf/descriptor.proto
.
Algumas opções são opções no nível do arquivo, o que significa que elas precisam ser escritas no escopo de nível superior, e não dentro de nenhuma mensagem, enumeração ou definição de serviço. Algumas opções são opções no nível da mensagem, o que significa que elas precisam ser escritas dentro das definições da mensagem. Algumas opções são opções no nível do campo, o que significa que elas precisam ser gravadas em definições de campo. As opções também podem ser escritas em tipos de enumeração, valores de enumeração, um de campos, tipos de serviço e métodos de serviço. No entanto, não há opções úteis para nenhum deles.
Veja a seguir algumas das opções mais usadas:
java_package
(opção de arquivo): o pacote que você quer usar para as classes Java geradas. Se nenhuma opçãojava_package
explícita for fornecida no arquivo.proto
, por padrão, o pacote proto (especificado usando a palavra-chave "package" no arquivo.proto
) será usado. No entanto, geralmente, os pacotes proto não são bons pacotes Java, já que não é esperado que eles comecem com nomes de domínio inversos. Essa opção não tem efeito se não estiver gerando código Java.option java_package = "com.example.foo";
java_outer_classname
(opção de arquivo): o nome da classe (e, portanto, do nome do arquivo) para a classe Java do wrapper que você quer gerar. Se nenhumjava_outer_classname
explícito for especificado no arquivo.proto
, o nome da classe será construído convertendo o nome do arquivo.proto
para letras concatenadas (de modo quefoo_bar.proto
se tornaFooBar.java
). Se a opçãojava_multiple_files
for desativada, todas as outras classes/enums/etc. geradas para o arquivo.proto
serão geradas dentro dessa classe Java do wrapper externo como classes/enums/etc. aninhadas. Se não estiver gerando código Java, essa opção não vai ter efeito.option java_outer_classname = "Ponycopter";
java_multiple_files
(opção de arquivo): se for falso, apenas um único arquivo.java
vai ser gerado para esse arquivo.proto
, e todas as classes/enums/etc. Java geradas para as mensagens, serviços e enumerações de nível superior serão aninhadas dentro de uma classe externa (consultejava_outer_classname
). Se for verdadeiro, arquivos.java
separados serão gerados para cada uma das classes/enums/etc. Java geradas para as classes de nível de código que quiser Essa opção não terá efeito se não estiver gerando código Java.option java_multiple_files = true;
optimize_for
(opção de arquivo): pode ser definido comoSPEED
,CODE_SIZE
ouLITE_RUNTIME
. Isso afeta os geradores de código C++ e Java (e possivelmente de terceiros) das seguintes maneiras:SPEED
(padrão): o compilador de buffer de protocolo gerará o código para serializar, analisar e executar outras operações comuns nos seus tipos de mensagens. Esse código é altamente otimizado.CODE_SIZE
: o compilador de buffer de protocolo gerará classes mínimas e confiará em um código compartilhado e baseado em reflexão para implementar serialização, análise e várias outras operações. Assim, o código gerado será muito menor do que comSPEED
, mas as operações serão mais lentas. As classes ainda vão implementar exatamente a mesma API pública que usam no modoSPEED
. Esse modo é mais útil em apps que contêm um número muito grande de arquivos.proto
e não precisam que todos eles sejam inclusivamente rápidos.LITE_RUNTIME
: o compilador de buffer de protocolo gerará classes que dependem apenas da biblioteca de ambiente de execução "lite" (libprotobuf-lite
em vez delibprotobuf
). O ambiente de execução do Lite é muito menor que a biblioteca completa (cerca de uma ordem de magnitude menor), mas omite alguns recursos, como descritores e reflexão. Isso é particularmente útil para apps executados em plataformas restritas, como smartphones. O compilador ainda vai gerar implementações rápidas de todos os métodos, como no modoSPEED
. As classes geradas só implementarão a interfaceMessageLite
em cada idioma, que fornece apenas um subconjunto dos métodos da interfaceMessage
completa.
option optimize_for = CODE_SIZE;
cc_generic_services
,java_generic_services
,py_generic_services
(opções de arquivo): se o compilador de buffer de protocolo precisa ou não gerar um código de serviço abstrato com base nas definições de serviços em C++, Java e Python, respectivamente. Por motivos legados, o padrão étrue
. No entanto, a partir da versão 2.3.0 (janeiro de 2010), é preferível que as implementações de RPC forneçam plug-ins de gerador de código para gerar código mais específico para cada sistema, em vez de depender dos serviços "abstratos".// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(opção de arquivo): ativa a alocação de área para código gerado em C++.message_set_wire_format
(opção de mensagem): se definida comotrue
, a mensagem usará um formato binário diferente que será compatível com um formato antigo usado dentro do Google chamadoMessageSet
. Usuários de fora do Google provavelmente nunca vão precisar usar essa opção. A mensagem precisa ser declarada da seguinte maneira:message Foo { option message_set_wire_format = true; extensions 4 to max; }
packed
(opção de campo): se definido comotrue
em um campo repetido de um tipo numérico básico, uma codificação mais compacta será usada. Não há desvantagem de usar essa opção. No entanto, observe que, antes da versão 2.3.0, os analisadores que recebiam dados compactados quando isso não era esperado o ignorariam. Portanto, não foi possível mudar um campo atual para o formato compactado sem prejudicar a compatibilidade com fio. Na versão 2.3.0 e mais recentes, essa mudança é segura, já que os analisadores de campos em pacotes sempre vão aceitar os dois formatos, mas tenha cuidado se você tiver que lidar com programas antigos usando versões antigas de protobuf.repeated int32 samples = 4 [packed = true];
deprecated
(opção de campo): se definido comotrue
, indica que o campo foi descontinuado e não deve ser usado por um novo código. Na maioria dos idiomas, isso não tem efeito real. Em Java, isso se torna uma anotação@Deprecated
. Para C++, clang-tidy gerará avisos sempre que campos obsoletos forem usados. No futuro, outros geradores de código específicos da linguagem poderão gerar anotações de descontinuação nos acessadores de campo. Isso, por sua vez, fará com que um aviso seja emitido ao compilar o código que tenta usar o campo. Se o campo não for usado por ninguém e você quiser impedir que novos usuários o utilizem, considere substituir a declaração do campo por uma instrução reservada.optional int32 old_field = 6 [deprecated=true];
Opções personalizadas
Buffers de protocolo permitem que você defina e use suas próprias opções. Esse
é um recurso avançado que a maioria das pessoas não precisa. Como as opções são
definidas pelas mensagens definidas em google/protobuf/descriptor.proto
(como
FileOptions
ou FieldOptions
), definir suas próprias opções é apenas uma questão
de estender essas mensagens. Exemplo:
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
Aqui, definimos uma nova opção no nível da mensagem estendendo MessageOptions
.
Em seguida, o nome da opção precisa ser colocado entre parênteses para indicar que é uma extensão. Agora é possível ler o valor de my_option
em
C++ desta forma:
string value = MyMessage::descriptor()->options().GetExtension(my_option);
Aqui, MyMessage::descriptor()->options()
retorna a mensagem de protocolo MessageOptions
para MyMessage
. Ler as opções personalizadas a partir dela é como ler qualquer outra extensão.
Da mesma forma, em Java, poderíamos escrever:
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);
No Python, isso seria:
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]
As opções personalizadas podem ser definidas para cada tipo de construção na linguagem de buffers de protocolo. Veja um exemplo que usa todos os tipos de opção:
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50007;
}
option (my_file_option) = "Hello world!";
message MyMessage {
option (my_message_option) = 1234;
optional int32 foo = 1 [(my_field_option) = 4.5];
optional string bar = 2;
oneof qux {
option (my_oneof_option) = 42;
string quux = 3;
}
}
enum MyEnum {
option (my_enum_option) = true;
FOO = 1 [(my_enum_value_option) = 321];
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO;
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate "option" line.
option (my_method_option).foo = 567;
option (my_method_option).bar = "Some string";
}
}
Se você quiser usar uma opção personalizada em um pacote diferente do definido, é necessário prefixar o nome da opção com o nome do pacote, assim como faria para nomes de tipo. Exemplo:
// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
option (foo.my_option) = "Hello world!";
}
Uma última coisa: como as opções personalizadas são extensões, elas precisam ser atribuídas a números de campo como qualquer outro campo ou extensão. Nos exemplos acima, usamos números de campo no intervalo de 50.000 a 99.999. Esse intervalo é reservado para uso interno em organizações individuais. Portanto, é possível usar números nesse intervalo livremente para aplicativos internos. No entanto, se você pretende usar opções personalizadas em aplicativos públicos, é importante garantir que seus números de campo sejam globalmente exclusivos. Para receber números de campo exclusivos globalmente, envie uma solicitação para adicionar uma entrada ao registro de extensão global protobuf. Geralmente, você só precisa de um número de extensão. Você pode declarar várias opções com apenas um número de extensão colocando-as em uma submensagem:
message FooOptions {
optional int32 opt1 = 1;
optional string opt2 = 2;
}
extend google.protobuf.FieldOptions {
optional FooOptions foo_options = 1234;
}
// usage:
message Bar {
optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
// alternative aggregate syntax (uses TextFormat):
optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}
Além disso, observe que cada tipo de opção (no nível do arquivo, da mensagem, do campo etc.) tem um espaço próprio. Portanto, você pode declarar extensões de FieldOptions e MessageOptions com o mesmo número, por exemplo.
Gerar classes
Para gerar o código Java, Python ou C++ que você precisa trabalhar com os tipos de mensagem definidos em um arquivo .proto
, é necessário executar o compilador de buffer de protocolo protoc
no .proto
. Se você não tiver instalado o compilador,
faça o download do pacote e siga as instruções no
README.
O compilador de protocolo é invocado desta maneira:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
IMPORT_PATH
especifica um diretório em que procurar arquivos.proto
ao resolver diretivasimport
. Se omitido, o diretório atual será usado. É possível especificar vários diretórios de importação transmitindo a opção--proto_path
várias vezes. Eles serão pesquisados em ordem.-I=_IMPORT_PATH_
pode ser usado como um formato curto de--proto_path
.É possível fornecer uma ou mais diretivas de saída:
--cpp_out
gera código C++ emDST_DIR
. Consulte a referência de código gerado em C++ para mais informações.--java_out
gera código Java emDST_DIR
. Consulte a referência de código gerado por Java para mais informações.--python_out
gera código Python emDST_DIR
. Consulte a referência de código gerado pelo Python para mais informações.
Como conveniência, se a
DST_DIR
terminar em.zip
ou.jar
, o compilador gravará a saída em um único arquivo no formato ZIP com o nome fornecido. As saídas.jar
também vão receber um arquivo de manifesto, conforme exigido pela especificação JAR do Java. Se o arquivo de saída já existir, ele será substituído. O compilador não tem inteligência suficiente para adicionar arquivos a um arquivo existente.É necessário fornecer um ou mais arquivos
.proto
como entrada. Vários arquivos.proto
podem ser especificados de uma só vez. Embora os arquivos sejam nomeados em relação ao diretório atual, cada arquivo precisa residir em um dosIMPORT_PATH
s para que o compilador possa determinar o nome canônico.
Local do arquivo
Não coloque arquivos .proto
no mesmo diretório de outras fontes
de idiomas. Considere criar um subpacote proto
para arquivos .proto
, no
pacote raiz para seu projeto.
A localização deve ser independente de idioma
Ao trabalhar com código Java, é útil colocar os arquivos .proto
relacionados no
mesmo diretório da fonte Java. No entanto, se algum código não Java usar os
mesmos protos, o prefixo do caminho não fará mais sentido. Portanto, em geral, coloque os
protos em um diretório independente de linguagem, como //myteam/mypackage
.
A exceção a essa regra é quando fica claro que os protótipos serão usados somente em um contexto Java, como para testes.
Plataformas compatíveis
Para informações sobre:
- os sistemas operacionais, compiladores, sistemas de compilação e versões C++ compatíveis, consulte Política de suporte para C++ básico.
- Para ver as versões do PHP que são compatíveis, consulte Versões do PHP compatíveis.