Esta página descreve a API de objetos de mensagem que o compilador de buffer de protocolo gera para qualquer definição de protocolo. Leia os guias de idiomas do arquivo proto2 ou proto3 antes de ler este documento.
O compilador de protocolos do Ruby emite arquivos de origem Ruby que usam uma DSL para definir o esquema da mensagem. No entanto, a DSL ainda está sujeita a mudanças. Neste guia, descrevemos apenas a API das mensagens geradas, e não a DSL.
Invocação do compilador
O compilador de buffer de protocolo produz a saída do Ruby quando invocado com a sinalização de linha de comando --ruby_out=
. O parâmetro para a opção --ruby_out=
é o diretório em que você quer que o compilador grave a saída do Ruby. O compilador cria um arquivo .rb
para cada entrada de arquivo .proto
. Os nomes dos arquivos de saída são calculados com base no nome do arquivo .proto
e fazendo duas mudanças:
- A extensão (
.proto
) foi substituída por_pb.rb
. - O caminho do proto (especificado com a sinalização de linha de comando
--proto_path=
ou-I
) é substituído pelo caminho de saída (especificado com a sinalização--ruby_out=
).
Então, por exemplo, digamos que você invoque o compilador da seguinte maneira:
protoc --proto_path=src --ruby_out=build/gen src/foo.proto src/bar/baz.proto
O compilador lerá os arquivos src/foo.proto
e src/bar/baz.proto
e produzirá dois arquivos de saída: build/gen/foo_pb.rb
e build/gen/bar/baz_pb.rb
. O compilador criará automaticamente o diretório build/gen/bar
, se necessário, mas não criará build
ou build/gen
. Eles já devem existir.
Entrega de pacotes
O nome do pacote definido no arquivo .proto
é usado para gerar uma estrutura de módulos para as mensagens geradas. Dados de um arquivo como este:
package foo_bar.baz; message MyMessage {}
O compilador do protocolo gera uma mensagem de saída com o nome FooBar::Baz::MyMessage
.
Mensagens
Se a declaração for simples:
message Foo {}
O compilador de buffer de protocolo gera uma classe chamada Foo
. A classe gerada é derivada da classe Object
do Ruby (os protos não têm uma classe base comum). Ao contrário do C++ e Java, o código gerado pelo Ruby não é afetado pela opção optimize_for
no arquivo .proto
. Na verdade, todo o código do Ruby é otimizado para o tamanho do código.
Não crie suas próprias subclasses Foo
. As classes geradas não são projetadas para criar subclasses e podem gerar problemas de "classe base frágil".
As classes de mensagem do Ruby definem os acessadores para cada campo, além de fornecer os seguintes métodos padrão:
Message#dup
,Message#clone
: realiza uma cópia superficial desta mensagem e retorna a nova cópia.Message#==
: realiza uma comparação de igualdade profunda entre duas mensagens.Message#hash
: calcula um hash superficial do valor da mensagem.Message#to_hash
,Message#to_h
: converte o objeto em um objetoHash
ruby. Apenas a mensagem de nível superior é convertida.Message#inspect
: retorna uma string legível que representa essa mensagem.Message#[]
,Message#[]=
: recebe ou define um campo por nome de string. No futuro, isso provavelmente também será usado para obter/definir extensões.
As classes de mensagem também definem os seguintes métodos como estáticos. Em geral, preferimos métodos estáticos, já que os métodos regulares podem entrar em conflito com os nomes de campos definidos no arquivo .proto.
Message.decode(str)
: decodifica um protobuf binário para essa mensagem e o retorna em uma nova instância.Message.encode(proto)
: serializa um objeto de mensagem dessa classe para uma string binária.Message.decode_json(str)
: decodifica uma string de texto JSON para essa mensagem e a retorna em uma nova instância.Message.encode_json(proto)
: serializa um objeto de mensagem dessa classe para uma string de texto JSON.Message.descriptor
: retorna o objetoGoogle::Protobuf::Descriptor
desta mensagem.
Ao criar uma mensagem, você pode inicializar campos no construtor. Veja um exemplo de como criar e usar uma mensagem:
message = MyMessage.new(:int_field => 1, :string_field => "String", :repeated_int_field => [1, 2, 3, 4], :submessage_field => SubMessage.new(:foo => 42)) serialized = MyMessage.encode(message) message2 = MyMessage.decode(serialized) raise unless message2.int_field == 1
Tipos aninhados
Uma mensagem pode ser declarada dentro de outra. Por exemplo:
message Foo {
message Bar {
}
}
Nesse caso, a classe Bar
é declarada como dentro do Foo
. Portanto, você pode se referir a ela como Foo::Bar
.
Campos
Para cada campo em um tipo de mensagem, há métodos do acessador para definir e receber o campo. Dessa forma, considerando um campo foo
, é possível escrever:
message.foo = get_value() print message.foo
Sempre que você define um campo, o valor é verificado em relação ao tipo declarado desse campo. Se o valor for do tipo errado (ou estiver fora do intervalo), será gerada uma exceção.
Singular
Para campos primitivos singulares (números, strings e booleanos), o valor atribuído ao campo precisa ser do tipo correto e estar no intervalo apropriado:
- Tipos de número: o valor deve ser
Fixnum
,Bignum
ouFloat
. O valor atribuído precisa ser representado exatamente no tipo de destino. Portanto, atribuir1.0
a um campo int32 é aceitável, mas atribuir1.2
não. - Campos booleanos: o valor precisa ser
true
oufalse
. Nenhum outro valor será convertido implicitamente em verdadeiro/falso. - Campos de bytes: o valor atribuído precisa ser um objeto
String
. A biblioteca protobuf vai duplicar a string, converter para a codificação ASCII-8BIT e congelar. - Campos de string: o valor atribuído precisa ser um objeto
String
. A biblioteca protobuf vai duplicar, converter e congelar a string para UTF-8.
Nenhuma chamada automática #to_s
, #to_i
etc. realizará uma conversão automática. Converta os valores primeiro, se necessário.
Verificando presença
Ao usar campos "opcionais", a presença do campo é verificada chamando um método has_...?
gerado. A definição de qualquer valor, até mesmo o valor padrão, marca o campo como presente. Os campos podem ser apagados chamando outro método clear_...
gerado. Por exemplo, para uma mensagem MyMessage
com um campo int32 foo
:
m = MyMessage.new raise unless !m.has_foo? m.foo = 0 raise unless m.has_foo? m.clear_foo raise unless !m.has_foo?
Campos de mensagens individuais
Para submensagens, os campos não definidos retornam nil
, então sempre é possível saber se a mensagem foi definida explicitamente ou não. Para limpar um campo de submensagem, defina o valor explicitamente como nil
.
if message.submessage_field.nil? puts "Submessage field is unset." else message.submessage_field = nil puts "Cleared submessage field." end
Além de comparar e atribuir nil
, as mensagens geradas têm os métodos has_...
e clear_...
, que se comportam da mesma forma que os básicos:
if message.has_submessage_field? raise unless message.submessage_field == nil puts "Submessage field is unset." else raise unless message.submessage_field != nil message.clear_submessage_field raise unless message.submessage_field == nil puts "Cleared submessage field." end
Quando você atribui uma submensagem, ela precisa ser um objeto de mensagem gerado do tipo correto.
É possível criar ciclos de mensagem quando você atribui submensagens. Por exemplo:
// foo.proto message RecursiveMessage { RecursiveMessage submessage = 1; } # test.rb require 'foo' message = RecursiveSubmessage.new message.submessage = message
Se você tentar serializar isso, a biblioteca detectará o ciclo e não serializará.
Campos repetidos
Os campos repetidos são representados usando uma classe personalizada Google::Protobuf::RepeatedField
. Essa classe funciona como um Array
Ruby e se mistura em Enumerable
. Ao contrário de uma matriz Ruby comum, RepeatedField
é construído com um tipo específico e espera que todos os membros da matriz tenham o tipo correto. Os tipos e intervalos são verificados como campos de mensagem.
int_repeatedfield = Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3]) raise unless !int_repeatedfield.empty? # Raises TypeError. int_repeatedfield[2] = "not an int32" # Raises RangeError int_repeatedfield[2] = 2**33 message.int32_repeated_field = int_repeatedfield # This isn't allowed; the regular Ruby array doesn't enforce types like we need. message.int32_repeated_field = [1, 2, 3, 4] # This is fine, since the elements are copied into the type-safe array. message.int32_repeated_field += [1, 2, 3, 4] # The elements can be cleared without reassigning. int_repeatedfield.clear raise unless int_repeatedfield.empty?O tipo
RepeatedField
oferece suporte aos mesmos métodos que um Array
Ruby comum. É possível convertê-lo em uma matriz Ruby regular com repeated_field.to_a
.
Ao contrário dos campos singulares, os métodos has_...?
nunca são gerados para campos repetidos.
Campos do mapa
Os campos do mapa são representados por uma classe especial que atua como uma Hash
(Google::Protobuf::Map
) Ruby. Ao contrário de um hash Ruby comum, Map
é construído com um tipo específico para a chave e o valor e espera que todas as chaves e valores do mapa tenham o tipo correto. Os tipos e intervalos são verificados como campos de mensagem e elementos RepeatedField
.
int_string_map = Google::Protobuf::Map.new(:int32, :string) # Returns nil; items is not in the map. print int_string_map[5] # Raises TypeError, value should be a string int_string_map[11] = 200 # Ok. int_string_map[123] = "abc" message.int32_string_map_field = int_string_map
Enumerações
Como o Ruby não tem enumerações nativas, criamos um módulo para cada enum com constantes para definir os valores. Considerando o arquivo .proto
:
message Foo { enum SomeEnum { VALUE_A = 0; VALUE_B = 5; VALUE_C = 1234; } optional SomeEnum bar = 1; }Você pode consultar valores de enumeração da seguinte maneira:
print Foo::SomeEnum::VALUE_A # => 0 message.bar = Foo::SomeEnum::VALUE_A
Você pode atribuir um número ou símbolo a um campo de enumeração. Ao ler o valor de volta, ele será um símbolo se o valor da enumeração for conhecido ou um número se for desconhecido. Como proto3 usa semântica de enumeração aberta, qualquer número pode ser atribuído a um campo de enumeração, mesmo que ele não tenha sido definido na enumeração.
message.bar = 0 puts message.bar.inspect # => :VALUE_A message.bar = :VALUE_B puts message.bar.inspect # => :VALUE_B message.bar = 999 puts message.bar.inspect # => 999 # Raises: RangeError: Unknown symbol value for enum field. message.bar = :UNDEFINED_VALUE # Switching on an enum value is convenient. case message.bar when :VALUE_A # ... when :VALUE_B # ... when :VALUE_C # ... else # ... endUm módulo de enumeração também define os seguintes métodos utilitários:
Enum#lookup(number)
: procura o número fornecido e retorna o nome dele ounil
, se nenhum for encontrado. Se mais de um nome tiver esse número, retornará o primeiro que foi definido.Enum#resolve(symbol)
: retorna o número para o nome da enumeração ounil
caso nenhum tenha sido encontrado.Enum#descriptor
: retorna o descritor para essa enumeração.
Oneof
Você recebeu uma mensagem com uma destas opções:
message Foo { oneof test_oneof { string name = 1; int32 serial_number = 2; } }
A classe Ruby correspondente a Foo
terá membros com o nome name
e serial_number
com métodos do acessador, assim como campos comuns. No entanto, ao contrário dos campos normais, no máximo um dos campos em um campo pode ser definido por vez. Portanto, definir um campo limpará os outros.
message = Foo.new # Fields have their defaults. raise unless message.name == "" raise unless message.serial_number == 0 raise unless message.test_oneof == nil message.name = "Bender" raise unless message.name == "Bender" raise unless message.serial_number == 0 raise unless message.test_oneof == :name # Setting serial_number clears name. message.serial_number = 2716057 raise unless message.name == "" raise unless message.test_oneof == :serial_number # Setting serial_number to nil clears the oneof. message.serial_number = nil raise unless message.test_oneof == nil
Para mensagens proto2, um dos membros também tem métodos has_...?
individuais:
message = Foo.new raise unless !message.has_test_oneof? raise unless !message.has_name? raise unless !message.has_serial_number? raise unless !message.has_test_oneof? message.name = "Bender" raise unless message.has_test_oneof? raise unless message.has_name? raise unless !message.has_serial_number? raise unless !message.has_test_oneof?