class Google::Protobuf::Descriptor
Message Descriptor
- Descriptor
for short.
Attributes
Public Class Methods
Dynamically define accessors methods for the given OneOf field. Methods with names that conflict with existing methods are skipped. @param oneof_descriptor [OneofDescriptor] Field
to create accessors for.
# File lib/google/protobuf/ffi/message.rb, line 526 def self.add_oneof_accessors_for!(oneof_descriptor) field_name = oneof_descriptor.name.to_sym @oneof_field_names << field_name unless instance_methods(true).include?(field_name) define_method(field_name) do field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) if field_descriptor.nil? return else return field_descriptor.name.to_sym end end define_method("clear_#{field_name}") do field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) unless field_descriptor.nil? clear_internal(field_descriptor) end end define_method("has_#{field_name}?") do !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil? end end end
Decodes the given data (as a string containing bytes in protocol buffers wire format) under the interpretation given by this message class’s definition and returns a message object with the corresponding field values. @param data [String] Binary string in Protobuf
wire format to decode @param options [Hash] options for the decoder @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64)
# File lib/google/protobuf/ffi/message.rb, line 189 def self.decode(data, options = {}) raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String decoding_options = 0 depth = options[:recursion_limit] if depth.is_a? Numeric decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) end message = new mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor) status = Google::Protobuf::FFI.decode_message( data, data.bytesize, message.instance_variable_get(:@msg), mini_table_ptr, Google::Protobuf::FFI.get_extension_registry(message.class.descriptor.send(:pool).descriptor_pool), decoding_options, message.instance_variable_get(:@arena) ) raise ParseError.new "Error occurred during parsing" unless status == :Ok message end
all-seq:
MessageClass.decode_json(data, options = {}) => message
Decodes the given data (as a string containing bytes in protocol buffers wire format) under the interpretation given by this message class’s definition and returns a message object with the corresponding field values.
@param options [Hash] options for the decoder @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error) @return [Message]
# File lib/google/protobuf/ffi/message.rb, line 253 def self.decode_json(data, options = {}) decoding_options = 0 unless options.is_a? Hash if options.respond_to? :to_h options options.to_h else #TODO can this error message be improve to include what was received? raise ArgumentError.new "Expected hash arguments" end end raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?) if options[:ignore_unknown_fields] decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown end message = new pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool status = Google::Protobuf::FFI::Status.new result = Google::Protobuf::FFI.json_decode_message_detecting_nonconformance(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status) case result when Google::Protobuf::FFI::Upb_JsonDecodeResult_OkWithEmptyStringNumerics warn Google::Protobuf::FFI.error_message(status) when Google::Protobuf::FFI::Upb_JsonDecodeResult_Error raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}" end message end
# File lib/google/protobuf/ffi/message.rb, line 615 def self.deep_copy(msg, arena = nil) arena ||= Google::Protobuf::FFI.create_arena encode_internal(msg) do |encoding, size, mini_table_ptr| message = private_constructor(arena) if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok raise ParseError.new "Error occurred copying proto" end message end end
Encodes the given message object to its serialized form in protocol buffers wire format. @param options [Hash] options for the encoder @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64)
# File lib/google/protobuf/ffi/message.rb, line 222 def self.encode(message, options = {}) raise ArgumentError.new "Message of wrong type." unless message.is_a? self raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash encoding_options = 0 depth = options[:recursion_limit] if depth.is_a? Numeric encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) end encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _| if encoding.nil? or encoding.null? raise RuntimeError.new "Exceeded maximum depth (possibly cycle)" else encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze end end end
# File lib/google/protobuf/ffi/message.rb, line 626 def self.encode_internal(msg, encoding_options = 0) temporary_arena = Google::Protobuf::FFI.create_arena mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor) size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr) raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr end
# File lib/google/protobuf/ffi/message.rb, line 283 def self.encode_json(message, options = {}) encoding_options = 0 unless options.is_a? Hash if options.respond_to? :to_h options = options.to_h else #TODO can this error message be improve to include what was received? raise ArgumentError.new "Expected hash arguments" end end if options[:preserve_proto_fieldnames] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames end if options[:emit_defaults] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults end if options[:format_enums_as_integers] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers end buffer_size = 1024 buffer = ::FFI::MemoryPointer.new(:char, buffer_size) status = Google::Protobuf::FFI::Status.new msg = message.instance_variable_get(:@msg) pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) unless status[:ok] raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" end if size >= buffer_size buffer_size = size + 1 buffer = ::FFI::MemoryPointer.new(:char, buffer_size) status.clear size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) unless status[:ok] raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" end if size >= buffer_size raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}" end end buffer.read_string_length(size).force_encoding("UTF-8").freeze end
@param msg_def [::FFI::Pointer] MsgDef pointer to be wrapped @param _ [Object] Unused
# File lib/google/protobuf/ffi/descriptor.rb, line 36 def from_native(msg_def, _ = nil) return nil if msg_def.nil? or msg_def.null? file_def = Google::Protobuf::FFI.get_message_file_def msg_def descriptor_from_file_def(file_def, msg_def) end
# File lib/google/protobuf/ffi/message.rb, line 559 def self.inspect_field(field_descriptor, c_type, message_value) if field_descriptor.sub_message? sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor) sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val]) else convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect end end
@param msg [::FFI::Pointer] Pointer to the Message
# File lib/google/protobuf/ffi/message.rb, line 569 def self.inspect_internal(msg) field_output = [] descriptor.each do |field_descriptor| next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor) if field_descriptor.map? # TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation? message_descriptor = field_descriptor.subtype key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) key_field_type = Google::Protobuf::FFI.get_type(key_field_def) value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) value_field_type = Google::Protobuf::FFI.get_type(value_field_def) message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) iter = ::FFI::MemoryPointer.new(:size_t, 1) iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) key_value_pairs = [] while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do iter_size_t = iter.read(:size_t) key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t) value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t) key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect value_string = inspect_field(value_field_def, value_field_type, value_message_value) key_value_pairs << "#{key_string}=>#{value_string}" end field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}" elsif field_descriptor.repeated? # TODO Adapted - from repeated_field#each - can this be refactored to reduce echo? repeated_field_output = [] message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) array = message_value[:array_val] n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array) 0.upto(n - 1) do |i| element = Google::Protobuf::FFI.get_msgval_at(array, i) repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element) end field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]" else message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value) field_output << "#{field_descriptor.name}: #{rendered_value}" end end "<#{name}: #{field_output.join(', ')}>" end
Great write up of this strategy: See blog.appsignal.com/2018/08/07/ruby-magic-changing-the-way-ruby-creates-objects.html
# File lib/google/protobuf/ffi/descriptor.rb, line 50 def self.new(*arguments, &block) raise "Descriptor objects may not be created from Ruby." end
# File lib/google/protobuf/ffi/descriptor.rb, line 113 def initialize(msg_def, descriptor_pool) @msg_def = msg_def @msg_class = nil @descriptor_pool = descriptor_pool end
Dynamically define accessors methods for every field of @descriptor. Methods with names that conflict with existing methods are skipped.
# File lib/google/protobuf/ffi/message.rb, line 444 def self.setup_accessors! @descriptor.each do |field_descriptor| field_name = field_descriptor.name unless instance_methods(true).include?(field_name.to_sym) # Dispatching to either index_internal or get_field is logically # correct, but slightly slower due to having to perform extra # lookups on each invocation rather than doing it once here. if field_descriptor.map? define_method(field_name) do map_from_field_descriptor field_descriptor end elsif field_descriptor.repeated? define_method(field_name) do repeated_field_from_field_descriptor field_descriptor end elsif field_descriptor.sub_message? define_method(field_name) do message_from_field_descriptor field_descriptor end else define_method(field_name) do scalar_from_field_descriptor field_descriptor end end define_method("#{field_name}=") do |value| index_assign_internal(value, field_descriptor: field_descriptor) end define_method("clear_#{field_name}") do clear_internal(field_descriptor) end if field_descriptor.type == :enum if field_descriptor.repeated? define_method("#{field_name}_const") do return_value = [] repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val| return_value << msg_val[:int32_val] end return_value end else define_method("#{field_name}_const") do message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor message_value[:int32_val] end end end if !field_descriptor.repeated? and field_descriptor.wrapper? define_method("#{field_name}_as_value") do get_field(field_descriptor, unwrap: true) end define_method("#{field_name}_as_value=") do |value| if value.nil? clear_internal(field_descriptor) else index_assign_internal(value, field_descriptor: field_descriptor, wrap: true) end end end if field_descriptor.has_presence? define_method("has_#{field_name}?") do Google::Protobuf::FFI.get_message_has(@msg, field_descriptor) end end end end end
Dynamically define accessors methods for every OneOf field of @descriptor.
# File lib/google/protobuf/ffi/message.rb, line 514 def self.setup_oneof_accessors! @oneof_field_names = [] @descriptor.each_oneof do |oneof_descriptor| self.add_oneof_accessors_for! oneof_descriptor end end
@param value [Descriptor] Descriptor
to convert to an FFI
native type @param _ [Object] Unused
# File lib/google/protobuf/ffi/descriptor.rb, line 26 def to_native(value, _ = nil) msg_def_ptr = value.nil? ? nil : value.instance_variable_get(:@msg_def) return ::FFI::Pointer::NULL if msg_def_ptr.nil? raise "Underlying msg_def was null!" if msg_def_ptr.null? msg_def_ptr end
Private Class Methods
# File lib/google/protobuf/ffi/descriptor.rb, line 138 def self.get_message(msg, descriptor, arena) return nil if msg.nil? or msg.null? message = OBJECT_CACHE.get(msg.address) if message.nil? message = descriptor.msgclass.send(:private_constructor, arena, msg: msg) end message end
# File lib/google/protobuf/ffi/descriptor.rb, line 119 def self.private_constructor(msg_def, descriptor_pool) instance = allocate instance.send(:initialize, msg_def, descriptor_pool) instance end
Public Instance Methods
# File lib/google/protobuf/ffi/message.rb, line 162 def [](name) raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String index_internal name end
# File lib/google/protobuf/ffi/message.rb, line 174 def []=(name, value) raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String index_assign_internal(value, name: name) end
# File lib/google/protobuf/ffi/message.rb, line 36 def build_message_class descriptor = self Class.new(Google::Protobuf::const_get(:AbstractMessage)) do @descriptor = descriptor class << self attr_accessor :descriptor private attr_accessor :oneof_field_names include ::Google::Protobuf::Internal::Convert end alias original_method_missing method_missing def method_missing(method_name, *args) method_missing_internal method_name, *args, mode: :method_missing end def respond_to_missing?(method_name, include_private = false) method_missing_internal(method_name, mode: :respond_to_missing?) || super end ## # Public constructor. Automatically allocates from a new Arena. def self.new(initial_value = nil) instance = allocate instance.send(:initialize, initial_value) instance end ## # Is this object frozen? # Returns true if either this Ruby wrapper or the underlying # representation are frozen. Freezes the wrapper if the underlying # representation is already frozen but this wrapper isn't. def frozen? unless Google::Protobuf::FFI.message_frozen? @msg raise RuntimeError.new "Ruby frozen Message with mutable representation" if super return false end method(:freeze).super_method.call unless super true end ## # Freezes the map object. We have to intercept this so we can freeze the # underlying representation, not just the Ruby wrapper. Returns self. def freeze if method(:frozen?).super_method.call unless Google::Protobuf::FFI.message_frozen? @msg raise RuntimeError.new "Underlying representation of message still mutable despite frozen wrapper" end return self end unless Google::Protobuf::FFI.message_frozen? @msg Google::Protobuf::FFI.message_freeze(@msg, Google::Protobuf::FFI.get_mini_table(self.class.descriptor)) end super end def dup duplicate = self.class.private_constructor(@arena) mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size = mini_table[:size] duplicate.instance_variable_get(:@msg).write_string_length(@msg.read_string_length(size), size) duplicate end alias clone dup def eql?(other) return false unless self.class === other encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown temporary_arena = Google::Protobuf::FFI.create_arena mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size_one = ::FFI::MemoryPointer.new(:size_t, 1) encoding_one = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, encoding_one.to_ptr, size_one) raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding LHS of `eql?()`" unless encoding_status == :Ok size_two = ::FFI::MemoryPointer.new(:size_t, 1) encoding_two = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, encoding_two.to_ptr, size_two) raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding RHS of `eql?()`" unless encoding_status == :Ok if encoding_one.null? or encoding_two.null? raise ParseError.new "Error comparing messages" end size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one.read(:pointer), encoding_two.read(:pointer), size_one.read(:size_t)).zero? end alias == eql? def hash encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown temporary_arena = Google::Protobuf::FFI.create_arena mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) encoding = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, encoding.to_ptr, size_ptr) if encoding_status != :Ok or encoding.null? raise ParseError.new "Error calculating hash" end encoding.read(:pointer).read_string(size_ptr.read(:size_t)).hash end def to_h to_h_internal @msg, self.class.descriptor end ## # call-seq: # Message.inspect => string # # Returns a human-readable string representing this message. It will be # formatted as "<MessageType: field1: value1, field2: value2, ...>". Each # field's value is represented according to its own #inspect method. def inspect self.class.inspect_internal @msg end def to_s self.inspect end ## # call-seq: # Message.[](index) => value # Accesses a field's value by field name. The provided field name # should be a string. def [](name) raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String index_internal name end ## # call-seq: # Message.[]=(index, value) # Sets a field's value by field name. The provided field name should # be a string. # @param name [String] Name of the field to be set # @param value [Object] Value to set the field to def []=(name, value) raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String index_assign_internal(value, name: name) end ## # call-seq: # MessageClass.decode(data, options) => message # # Decodes the given data (as a string containing bytes in protocol buffers wire # format) under the interpretation given by this message class's definition # and returns a message object with the corresponding field values. # @param data [String] Binary string in Protobuf wire format to decode # @param options [Hash] options for the decoder # @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64) def self.decode(data, options = {}) raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String decoding_options = 0 depth = options[:recursion_limit] if depth.is_a? Numeric decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) end message = new mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor) status = Google::Protobuf::FFI.decode_message( data, data.bytesize, message.instance_variable_get(:@msg), mini_table_ptr, Google::Protobuf::FFI.get_extension_registry(message.class.descriptor.send(:pool).descriptor_pool), decoding_options, message.instance_variable_get(:@arena) ) raise ParseError.new "Error occurred during parsing" unless status == :Ok message end ## # call-seq: # MessageClass.encode(msg, options) => bytes # # Encodes the given message object to its serialized form in protocol buffers # wire format. # @param options [Hash] options for the encoder # @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64) def self.encode(message, options = {}) raise ArgumentError.new "Message of wrong type." unless message.is_a? self raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash encoding_options = 0 depth = options[:recursion_limit] if depth.is_a? Numeric encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) end encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _| if encoding.nil? or encoding.null? raise RuntimeError.new "Exceeded maximum depth (possibly cycle)" else encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze end end end ## # all-seq: # MessageClass.decode_json(data, options = {}) => message # # Decodes the given data (as a string containing bytes in protocol buffers wire # format) under the interpretation given by this message class's definition # and returns a message object with the corresponding field values. # # @param options [Hash] options for the decoder # @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error) # @return [Message] def self.decode_json(data, options = {}) decoding_options = 0 unless options.is_a? Hash if options.respond_to? :to_h options options.to_h else #TODO can this error message be improve to include what was received? raise ArgumentError.new "Expected hash arguments" end end raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?) if options[:ignore_unknown_fields] decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown end message = new pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool status = Google::Protobuf::FFI::Status.new result = Google::Protobuf::FFI.json_decode_message_detecting_nonconformance(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status) case result when Google::Protobuf::FFI::Upb_JsonDecodeResult_OkWithEmptyStringNumerics warn Google::Protobuf::FFI.error_message(status) when Google::Protobuf::FFI::Upb_JsonDecodeResult_Error raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}" end message end def self.encode_json(message, options = {}) encoding_options = 0 unless options.is_a? Hash if options.respond_to? :to_h options = options.to_h else #TODO can this error message be improve to include what was received? raise ArgumentError.new "Expected hash arguments" end end if options[:preserve_proto_fieldnames] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames end if options[:emit_defaults] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults end if options[:format_enums_as_integers] encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers end buffer_size = 1024 buffer = ::FFI::MemoryPointer.new(:char, buffer_size) status = Google::Protobuf::FFI::Status.new msg = message.instance_variable_get(:@msg) pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) unless status[:ok] raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" end if size >= buffer_size buffer_size = size + 1 buffer = ::FFI::MemoryPointer.new(:char, buffer_size) status.clear size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) unless status[:ok] raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" end if size >= buffer_size raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}" end end buffer.read_string_length(size).force_encoding("UTF-8").freeze end private # Implementation details below are subject to breaking changes without # warning and are intended for use only within the gem. include Google::Protobuf::Internal::Convert ## # Checks ObjectCache for a sentinel empty frozen Map of the key and # value types matching the field descriptor's MessageDef and returns # the cache entry. If an entry is not found, one is created and added # to the cache keyed by the MessageDef pointer first. # @param field_descriptor [FieldDescriptor] Field to retrieve. def empty_frozen_map(field_descriptor) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor frozen_map = OBJECT_CACHE.get sub_message_def if frozen_map.nil? frozen_map = Google::Protobuf::Map.send(:construct_for_field, field_descriptor) OBJECT_CACHE.try_add(sub_message_def, frozen_map.freeze) end raise "Empty Frozen Map is not frozen" unless frozen_map.frozen? frozen_map end ## # Returns a frozen Map instance for the given field. If the message # already has a value for that field, it is used. If not, a sentinel # (per FieldDescriptor) empty frozen Map is returned instead. # @param field_descriptor [FieldDescriptor] Field to retrieve. def frozen_map_from_field_descriptor(field_descriptor) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor return empty_frozen_map field_descriptor if message_value[:map_val].null? get_map_field(message_value[:map_val], field_descriptor).freeze end ## # Returns a Map instance for the given field. If the message is frozen # the return value is also frozen. If not, a mutable instance is # returned instead. # @param field_descriptor [FieldDescriptor] Field to retrieve. def map_from_field_descriptor(field_descriptor) return frozen_map_from_field_descriptor field_descriptor if frozen? mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena get_map_field(mutable_message_value[:map], field_descriptor) end ## # Checks ObjectCache for a sentinel empty frozen RepeatedField of the # value type matching the field descriptor's MessageDef and returns # the cache entry. If an entry is not found, one is created and added # to the cache keyed by the MessageDef pointer first. # @param field_descriptor [FieldDescriptor] Field to retrieve. def empty_frozen_repeated_field(field_descriptor) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor frozen_repeated_field = OBJECT_CACHE.get sub_message_def if frozen_repeated_field.nil? frozen_repeated_field = Google::Protobuf::RepeatedField.send(:construct_for_field, field_descriptor) OBJECT_CACHE.try_add(sub_message_def, frozen_repeated_field.freeze) end raise "Empty frozen RepeatedField is not frozen" unless frozen_repeated_field.frozen? frozen_repeated_field end ## # Returns a frozen RepeatedField instance for the given field. If the # message already has a value for that field, it is used. If not, a # sentinel (per FieldDescriptor) empty frozen RepeatedField is # returned instead. # @param field_descriptor [FieldDescriptor] Field to retrieve. def frozen_repeated_field_from_field_descriptor(field_descriptor) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor return empty_frozen_repeated_field field_descriptor if message_value[:array_val].null? get_repeated_field(message_value[:array_val], field_descriptor).freeze end ## # Returns a RepeatedField instance for the given field. If the message # is frozen the return value is also frozen. If not, a mutable # instance is returned instead. # @param field_descriptor [FieldDescriptor] Field to retrieve. def repeated_field_from_field_descriptor(field_descriptor) return frozen_repeated_field_from_field_descriptor field_descriptor if frozen? mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena get_repeated_field(mutable_message_value[:array], field_descriptor) end ## # Returns a Message instance for the given field. If the message # is frozen nil is always returned. Otherwise, a mutable instance is # returned instead. # @param field_descriptor [FieldDescriptor] Field to retrieve. def message_from_field_descriptor(field_descriptor) return nil if frozen? return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena sub_message = mutable_message[:msg] sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor Descriptor.send(:get_message, sub_message, sub_message_def, @arena) end ## # Returns a scalar value for the given field. If the message # is frozen the return value is also frozen. # @param field_descriptor [FieldDescriptor] Field to retrieve. def scalar_from_field_descriptor(field_descriptor) c_type = field_descriptor.send(:c_type) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor msg_or_enum_def = c_type == :enum ? Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) : nil return_value = convert_upb_to_ruby message_value, c_type, msg_or_enum_def frozen? ? return_value.freeze : return_value end ## # Dynamically define accessors methods for every field of @descriptor. # Methods with names that conflict with existing methods are skipped. def self.setup_accessors! @descriptor.each do |field_descriptor| field_name = field_descriptor.name unless instance_methods(true).include?(field_name.to_sym) # Dispatching to either index_internal or get_field is logically # correct, but slightly slower due to having to perform extra # lookups on each invocation rather than doing it once here. if field_descriptor.map? define_method(field_name) do map_from_field_descriptor field_descriptor end elsif field_descriptor.repeated? define_method(field_name) do repeated_field_from_field_descriptor field_descriptor end elsif field_descriptor.sub_message? define_method(field_name) do message_from_field_descriptor field_descriptor end else define_method(field_name) do scalar_from_field_descriptor field_descriptor end end define_method("#{field_name}=") do |value| index_assign_internal(value, field_descriptor: field_descriptor) end define_method("clear_#{field_name}") do clear_internal(field_descriptor) end if field_descriptor.type == :enum if field_descriptor.repeated? define_method("#{field_name}_const") do return_value = [] repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val| return_value << msg_val[:int32_val] end return_value end else define_method("#{field_name}_const") do message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor message_value[:int32_val] end end end if !field_descriptor.repeated? and field_descriptor.wrapper? define_method("#{field_name}_as_value") do get_field(field_descriptor, unwrap: true) end define_method("#{field_name}_as_value=") do |value| if value.nil? clear_internal(field_descriptor) else index_assign_internal(value, field_descriptor: field_descriptor, wrap: true) end end end if field_descriptor.has_presence? define_method("has_#{field_name}?") do Google::Protobuf::FFI.get_message_has(@msg, field_descriptor) end end end end end ## # Dynamically define accessors methods for every OneOf field of # @descriptor. def self.setup_oneof_accessors! @oneof_field_names = [] @descriptor.each_oneof do |oneof_descriptor| self.add_oneof_accessors_for! oneof_descriptor end end ## # Dynamically define accessors methods for the given OneOf field. # Methods with names that conflict with existing methods are skipped. # @param oneof_descriptor [OneofDescriptor] Field to create accessors # for. def self.add_oneof_accessors_for!(oneof_descriptor) field_name = oneof_descriptor.name.to_sym @oneof_field_names << field_name unless instance_methods(true).include?(field_name) define_method(field_name) do field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) if field_descriptor.nil? return else return field_descriptor.name.to_sym end end define_method("clear_#{field_name}") do field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) unless field_descriptor.nil? clear_internal(field_descriptor) end end define_method("has_#{field_name}?") do !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil? end end end setup_accessors! setup_oneof_accessors! def self.private_constructor(arena, msg: nil, initial_value: nil) instance = allocate instance.send(:initialize, initial_value, arena, msg) instance end def self.inspect_field(field_descriptor, c_type, message_value) if field_descriptor.sub_message? sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor) sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val]) else convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect end end # @param msg [::FFI::Pointer] Pointer to the Message def self.inspect_internal(msg) field_output = [] descriptor.each do |field_descriptor| next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor) if field_descriptor.map? # TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation? message_descriptor = field_descriptor.subtype key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) key_field_type = Google::Protobuf::FFI.get_type(key_field_def) value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) value_field_type = Google::Protobuf::FFI.get_type(value_field_def) message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) iter = ::FFI::MemoryPointer.new(:size_t, 1) iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) key_value_pairs = [] while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do iter_size_t = iter.read(:size_t) key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t) value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t) key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect value_string = inspect_field(value_field_def, value_field_type, value_message_value) key_value_pairs << "#{key_string}=>#{value_string}" end field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}" elsif field_descriptor.repeated? # TODO Adapted - from repeated_field#each - can this be refactored to reduce echo? repeated_field_output = [] message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) array = message_value[:array_val] n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array) 0.upto(n - 1) do |i| element = Google::Protobuf::FFI.get_msgval_at(array, i) repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element) end field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]" else message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value) field_output << "#{field_descriptor.name}: #{rendered_value}" end end "<#{name}: #{field_output.join(', ')}>" end def self.deep_copy(msg, arena = nil) arena ||= Google::Protobuf::FFI.create_arena encode_internal(msg) do |encoding, size, mini_table_ptr| message = private_constructor(arena) if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok raise ParseError.new "Error occurred copying proto" end message end end def self.encode_internal(msg, encoding_options = 0) temporary_arena = Google::Protobuf::FFI.create_arena mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor) size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr) raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr end def method_missing_internal(method_name, *args, mode: nil) raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode #TODO not being allowed is not the same thing as not responding, but this is needed to pass tests if method_name.to_s.end_with? '=' if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym return false if mode == :respond_to_missing? raise RuntimeError.new "Oneof accessors are read-only." end end original_method_missing(method_name, *args) if mode == :method_missing end def clear_internal(field_def) raise FrozenError.new "can't modify frozen #{self.class}" if frozen? Google::Protobuf::FFI.clear_message_field(@msg, field_def) end # Accessor for field by name. Does not delegate to methods setup by # self.setup_accessors! in order to avoid conflicts with bad field # names e.g. `dup` or `class` which are perfectly valid for proto # fields. def index_internal(name) field_descriptor = self.class.descriptor.lookup(name) get_field field_descriptor unless field_descriptor.nil? end #TODO - well known types keeps us on our toes by overloading methods. # How much of the public API needs to be defended? def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false) raise FrozenError.new "can't modify frozen #{self.class}" if frozen? if field_descriptor.nil? field_descriptor = self.class.descriptor.lookup(name) if field_descriptor.nil? raise ArgumentError.new "Unknown field: #{name}" end end unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap raise RuntimeError.new "allocation failed" end end ## # @param initial_value [Object] initial value of this Message # @param arena [Arena] Optional; Arena where this message will be allocated # @param msg [::FFI::Pointer] Optional; Message to initialize; creates # one if omitted or nil. def initialize(initial_value = nil, arena = nil, msg = nil) @arena = arena || Google::Protobuf::FFI.create_arena @msg = msg || Google::Protobuf::FFI.new_message_from_def(Google::Protobuf::FFI.get_mini_table(self.class.descriptor), @arena) unless initial_value.nil? raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each field_def_ptr = ::FFI::MemoryPointer.new :pointer oneof_def_ptr = ::FFI::MemoryPointer.new :pointer initial_value.each do |key, value| raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class unless Google::Protobuf::FFI.find_msg_def_by_name self.class.descriptor, key.to_s, key.to_s.bytesize, field_def_ptr, oneof_def_ptr raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry." end raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null? raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null? field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0) next if value.nil? if field_descriptor.map? index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, arena: @arena, value: value), name: key.to_s) elsif field_descriptor.repeated? index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, arena: @arena, values: value), name: key.to_s) else index_assign_internal(value, name: key.to_s) end end end # Should always be the last expression of the initializer to avoid # leaking references to this object before construction is complete. Google::Protobuf::OBJECT_CACHE.try_add @msg.address, self end ## # Gets a field of this message identified by FieldDescriptor. # # @param field [FieldDescriptor] Field to retrieve. # @param unwrap [Boolean](false) If true, unwraps wrappers. def get_field(field, unwrap: false) if field.map? map_from_field_descriptor field elsif field.repeated? repeated_field_from_field_descriptor field elsif field.sub_message? return nil unless Google::Protobuf::FFI.get_message_has @msg, field if unwrap if field.has?(self) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field fields = Google::Protobuf::FFI.field_count(sub_message_def) raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1 value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1 message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def) else nil end else message_from_field_descriptor field end else scalar_from_field_descriptor field end end ## # Gets a RepeatedField from the ObjectCache or creates a new one. # @param array [::FFI::Pointer] Pointer to the upb_Array # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field def get_repeated_field(array, field) return nil if array.nil? or array.null? repeated_field = OBJECT_CACHE.get(array.address) if repeated_field.nil? repeated_field = RepeatedField.send(:construct_for_field, field, arena: @arena, array: array) repeated_field.freeze if frozen? end repeated_field end ## # Gets a Map from the ObjectCache or creates a new one. # @param map [::FFI::Pointer] Pointer to the upb_Map # @param field [Google::Protobuf::FieldDescriptor] Type of the map field def get_map_field(map, field) return nil if map.nil? or map.null? map_field = OBJECT_CACHE.get(map.address) if map_field.nil? map_field = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map) map_field.freeze if frozen? end map_field end end end
# File lib/google/protobuf/ffi/message.rb, line 651 def clear_internal(field_def) raise FrozenError.new "can't modify frozen #{self.class}" if frozen? Google::Protobuf::FFI.clear_message_field(@msg, field_def) end
# File lib/google/protobuf/ffi/message.rb, line 94 def dup duplicate = self.class.private_constructor(@arena) mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size = mini_table[:size] duplicate.instance_variable_get(:@msg).write_string_length(@msg.read_string_length(size), size) duplicate end
# File lib/google/protobuf/ffi/descriptor.rb, line 78 def each &block n = Google::Protobuf::FFI.field_count(self) 0.upto(n-1) do |i| yield(Google::Protobuf::FFI.get_field_by_index(self, i)) end nil end
# File lib/google/protobuf/ffi/descriptor.rb, line 70 def each_oneof &block n = Google::Protobuf::FFI.oneof_count(self) 0.upto(n-1) do |i| yield(Google::Protobuf::FFI.get_oneof_by_index(self, i)) end nil end
Checks ObjectCache for a sentinel empty frozen Map
of the key and value types matching the field descriptor’s MessageDef and returns the cache entry. If an entry is not found, one is created and added to the cache keyed by the MessageDef pointer first. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 342 def empty_frozen_map(field_descriptor) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor frozen_map = OBJECT_CACHE.get sub_message_def if frozen_map.nil? frozen_map = Google::Protobuf::Map.send(:construct_for_field, field_descriptor) OBJECT_CACHE.try_add(sub_message_def, frozen_map.freeze) end raise "Empty Frozen Map is not frozen" unless frozen_map.frozen? frozen_map end
Checks ObjectCache for a sentinel empty frozen RepeatedField
of the value type matching the field descriptor’s MessageDef and returns the cache entry. If an entry is not found, one is created and added to the cache keyed by the MessageDef pointer first. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 381 def empty_frozen_repeated_field(field_descriptor) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor frozen_repeated_field = OBJECT_CACHE.get sub_message_def if frozen_repeated_field.nil? frozen_repeated_field = Google::Protobuf::RepeatedField.send(:construct_for_field, field_descriptor) OBJECT_CACHE.try_add(sub_message_def, frozen_repeated_field.freeze) end raise "Empty frozen RepeatedField is not frozen" unless frozen_repeated_field.frozen? frozen_repeated_field end
# File lib/google/protobuf/ffi/message.rb, line 103 def eql?(other) return false unless self.class === other encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown temporary_arena = Google::Protobuf::FFI.create_arena mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size_one = ::FFI::MemoryPointer.new(:size_t, 1) encoding_one = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, encoding_one.to_ptr, size_one) raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding LHS of `eql?()`" unless encoding_status == :Ok size_two = ::FFI::MemoryPointer.new(:size_t, 1) encoding_two = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, encoding_two.to_ptr, size_two) raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding RHS of `eql?()`" unless encoding_status == :Ok if encoding_one.null? or encoding_two.null? raise ParseError.new "Error comparing messages" end size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one.read(:pointer), encoding_two.read(:pointer), size_one.read(:size_t)).zero? end
# File lib/google/protobuf/ffi/descriptor.rb, line 62 def file_descriptor @descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.get_message_file_def(@msg_def)) end
Freezes the map object. We have to intercept this so we can freeze the underlying representation, not just the Ruby wrapper. Returns self.
# File lib/google/protobuf/ffi/message.rb, line 81 def freeze if method(:frozen?).super_method.call unless Google::Protobuf::FFI.message_frozen? @msg raise RuntimeError.new "Underlying representation of message still mutable despite frozen wrapper" end return self end unless Google::Protobuf::FFI.message_frozen? @msg Google::Protobuf::FFI.message_freeze(@msg, Google::Protobuf::FFI.get_mini_table(self.class.descriptor)) end super end
Is this object frozen? Returns true if either this Ruby wrapper or the underlying representation are frozen. Freezes the wrapper if the underlying representation is already frozen but this wrapper isn’t.
# File lib/google/protobuf/ffi/message.rb, line 69 def frozen? unless Google::Protobuf::FFI.message_frozen? @msg raise RuntimeError.new "Ruby frozen Message with mutable representation" if super return false end method(:freeze).super_method.call unless super true end
Returns a frozen Map
instance for the given field. If the message already has a value for that field, it is used. If not, a sentinel (per FieldDescriptor
) empty frozen Map
is returned instead. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 358 def frozen_map_from_field_descriptor(field_descriptor) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor return empty_frozen_map field_descriptor if message_value[:map_val].null? get_map_field(message_value[:map_val], field_descriptor).freeze end
Returns a frozen RepeatedField
instance for the given field. If the message already has a value for that field, it is used. If not, a sentinel (per FieldDescriptor
) empty frozen RepeatedField
is returned instead. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 398 def frozen_repeated_field_from_field_descriptor(field_descriptor) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor return empty_frozen_repeated_field field_descriptor if message_value[:array_val].null? get_repeated_field(message_value[:array_val], field_descriptor).freeze end
Gets a field of this message identified by FieldDescriptor
.
@param field [FieldDescriptor] Field
to retrieve. @param unwrap [Boolean](false) If true, unwraps wrappers.
# File lib/google/protobuf/ffi/message.rb, line 727 def get_field(field, unwrap: false) if field.map? map_from_field_descriptor field elsif field.repeated? repeated_field_from_field_descriptor field elsif field.sub_message? return nil unless Google::Protobuf::FFI.get_message_has @msg, field if unwrap if field.has?(self) sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field fields = Google::Protobuf::FFI.field_count(sub_message_def) raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1 value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1 message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def) else nil end else message_from_field_descriptor field end else scalar_from_field_descriptor field end end
Gets a Map
from the ObjectCache or creates a new one. @param map [::FFI::Pointer] Pointer to the upb_Map @param field [Google::Protobuf::FieldDescriptor] Type of the map field
# File lib/google/protobuf/ffi/message.rb, line 772 def get_map_field(map, field) return nil if map.nil? or map.null? map_field = OBJECT_CACHE.get(map.address) if map_field.nil? map_field = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map) map_field.freeze if frozen? end map_field end
Gets a RepeatedField
from the ObjectCache or creates a new one. @param array [::FFI::Pointer] Pointer to the upb_Array @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field
# File lib/google/protobuf/ffi/message.rb, line 758 def get_repeated_field(array, field) return nil if array.nil? or array.null? repeated_field = OBJECT_CACHE.get(array.address) if repeated_field.nil? repeated_field = RepeatedField.send(:construct_for_field, field, arena: @arena, array: array) repeated_field.freeze if frozen? end repeated_field end
# File lib/google/protobuf/ffi/message.rb, line 125 def hash encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown temporary_arena = Google::Protobuf::FFI.create_arena mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) encoding = ::FFI::MemoryPointer.new(:pointer, 1) encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, encoding.to_ptr, size_ptr) if encoding_status != :Ok or encoding.null? raise ParseError.new "Error calculating hash" end encoding.read(:pointer).read_string(size_ptr.read(:size_t)).hash end
TODO - well known types keeps us on our toes by overloading methods.
How much of the public API needs to be defended?
# File lib/google/protobuf/ffi/message.rb, line 667 def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false) raise FrozenError.new "can't modify frozen #{self.class}" if frozen? if field_descriptor.nil? field_descriptor = self.class.descriptor.lookup(name) if field_descriptor.nil? raise ArgumentError.new "Unknown field: #{name}" end end unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap raise RuntimeError.new "allocation failed" end end
Accessor for field by name. Does not delegate to methods setup by self.setup_accessors! in order to avoid conflicts with bad field names e.g. ‘dup` or `class` which are perfectly valid for proto fields.
# File lib/google/protobuf/ffi/message.rb, line 660 def index_internal(name) field_descriptor = self.class.descriptor.lookup(name) get_field field_descriptor unless field_descriptor.nil? end
# File lib/google/protobuf/ffi/descriptor.rb, line 58 def inspect "Descriptor - (not the message class) #{name}" end
# File lib/google/protobuf/ffi/descriptor.rb, line 86 def lookup(name) Google::Protobuf::FFI.get_field_by_name(self, name, name.size) end
# File lib/google/protobuf/ffi/descriptor.rb, line 90 def lookup_oneof(name) Google::Protobuf::FFI.get_oneof_by_name(self, name, name.size) end
Returns a Map
instance for the given field. If the message is frozen the return value is also frozen. If not, a mutable instance is returned instead. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 369 def map_from_field_descriptor(field_descriptor) return frozen_map_from_field_descriptor field_descriptor if frozen? mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena get_map_field(mutable_message_value[:map], field_descriptor) end
Returns a Message instance for the given field. If the message is frozen nil is always returned. Otherwise, a mutable instance is returned instead. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 420 def message_from_field_descriptor(field_descriptor) return nil if frozen? return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena sub_message = mutable_message[:msg] sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor Descriptor.send(:get_message, sub_message, sub_message_def, @arena) end
# File lib/google/protobuf/ffi/message.rb, line 48 def method_missing(method_name, *args) method_missing_internal method_name, *args, mode: :method_missing end
# File lib/google/protobuf/ffi/message.rb, line 637 def method_missing_internal(method_name, *args, mode: nil) raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode #TODO not being allowed is not the same thing as not responding, but this is needed to pass tests if method_name.to_s.end_with? '=' if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym return false if mode == :respond_to_missing? raise RuntimeError.new "Oneof accessors are read-only." end end original_method_missing(method_name, *args) if mode == :method_missing end
# File lib/google/protobuf/ffi/descriptor.rb, line 94 def msgclass @msg_class ||= build_message_class end
# File lib/google/protobuf/ffi/descriptor.rb, line 66 def name @name ||= Google::Protobuf::FFI.get_message_fullname(self) end
# File lib/google/protobuf/ffi/descriptor.rb, line 98 def options @options ||= begin size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) temporary_arena = Google::Protobuf::FFI.create_arena buffer = Google::Protobuf::FFI.message_options(self, size_ptr, temporary_arena) opts = Google::Protobuf::MessageOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze) opts.clear_features() opts.freeze end end
Returns a RepeatedField
instance for the given field. If the message is frozen the return value is also frozen. If not, a mutable instance is returned instead. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 409 def repeated_field_from_field_descriptor(field_descriptor) return frozen_repeated_field_from_field_descriptor field_descriptor if frozen? mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena get_repeated_field(mutable_message_value[:array], field_descriptor) end
# File lib/google/protobuf/ffi/message.rb, line 52 def respond_to_missing?(method_name, include_private = false) method_missing_internal(method_name, mode: :respond_to_missing?) || super end
Returns a scalar value for the given field. If the message is frozen the return value is also frozen. @param field_descriptor [FieldDescriptor] Field
to retrieve.
# File lib/google/protobuf/ffi/message.rb, line 433 def scalar_from_field_descriptor(field_descriptor) c_type = field_descriptor.send(:c_type) message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor msg_or_enum_def = c_type == :enum ? Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) : nil return_value = convert_upb_to_ruby message_value, c_type, msg_or_enum_def frozen? ? return_value.freeze : return_value end
# File lib/google/protobuf/ffi/message.rb, line 138 def to_h to_h_internal @msg, self.class.descriptor end
# File lib/google/protobuf/ffi/descriptor.rb, line 43 def to_native self.class.to_native(self) end
# File lib/google/protobuf/ffi/descriptor.rb, line 54 def to_s inspect end
Private Instance Methods
# File lib/google/protobuf/ffi/descriptor.rb, line 147 def pool @descriptor_pool end
# File lib/google/protobuf/ffi/descriptor.rb, line 125 def wrapper? if defined? @wrapper @wrapper else @wrapper = case Google::Protobuf::FFI.get_well_known_type self when :DoubleValue, :FloatValue, :Int64Value, :UInt64Value, :Int32Value, :UInt32Value, :StringValue, :BytesValue, :BoolValue true else false end end end