OBSERVAÇÃO:este site foi descontinuado. O site será desativado após 31 de janeiro de 2023, e o tráfego será redirecionado para o novo site em https://protobuf.dev. Enquanto isso, as atualizações serão feitas apenas para protobuf.dev.

Guia de idiomas

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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 classes Builder 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 enums dentro ou fora de uma definição de mensagem. Esses enums 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 Results é 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 ou repeated. 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 elemento required. 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 e bool 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 e sint64 são compatíveis entre si, mas não com os outros tipos de números inteiros.
  • string e bytes 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 com sfixed32 e fixed64 com sfixed64.
  • Para os campos string, bytes e mensagem, optional é compatível com repeated. Com os dados serializados de um campo repetido como entrada, os clientes que esperam esse campo como optional 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 campo optional 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 com int32, uint32, int64 e uint64 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 valores enum não reconhecidos são descartados quando a mensagem é desserializada, o que faz com que o acessador has.. do campo retorne false e o getter retorne o primeiro valor listado na definição enum 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 um enum 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 novo oneof é 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 novo oneof pode ser seguro se você tiver certeza de que nenhum código define mais de um por vez. Não é seguro mover campos para um oneof existente. Da mesma forma, é seguro alterar um único campo oneof para um campo ou extensão optional.
  • A mudança de um campo entre uma map<K, V> e o campo de mensagem repeated 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 campo repeated produzirão um resultado semanticamente idêntico. No entanto, os clientes que usam a definição de campo map 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étodo set_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á uma sub_message e msg2 terá uma name.

    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 ou required.
  • 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 namespace foo::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 regra go_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ção java_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 nenhum java_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 que foo_bar.proto se torna FooBar.java). Se a opção java_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 (consulte java_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 como SPEED, CODE_SIZE ou LITE_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 com SPEED, mas as operações serão mais lentas. As classes ainda vão implementar exatamente a mesma API pública que usam no modo SPEED. 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 de libprotobuf). 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 modo SPEED. As classes geradas só implementarão a interface MessageLite em cada idioma, que fornece apenas um subconjunto dos métodos da interface Message 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 como true, a mensagem usará um formato binário diferente que será compatível com um formato antigo usado dentro do Google chamado MessageSet. 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 como true 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 como true, 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 diretivas import. 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:

    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 dos IMPORT_PATHs 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: