class Google::Protobuf::Descriptor

Message Descriptor - Descriptor for short.

Attributes

descriptor[RW]
oneof_field_names[RW]
descriptor_pool[RW]

Public Class Methods

add_oneof_accessors_for!(oneof_descriptor) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 397
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
decode(data, options) → message click to toggle source

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 169
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
decode_json(data, options = {}) click to toggle source

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 233
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
  unless Google::Protobuf::FFI.json_decode_message(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
    raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
  end
  message
end
deep_copy(msg, arena = nil) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 486
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
encode(msg, options) → bytes click to toggle source

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 202
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
encode_internal(msg, encoding_options = 0) { |read(:pointer), read(:size_t), mini_table_ptr| ... } click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 497
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
encode_json(message, options = {}) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 259
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
from_native(msg_def, _ = nil) click to toggle source

@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
inspect_field(field_descriptor, c_type, message_value) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 430
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
inspect_internal(msg) click to toggle source

@param msg [::FFI::Pointer] Pointer to the Message

# File lib/google/protobuf/ffi/message.rb, line 440
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
new(*arguments, &block) click to toggle source

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
new(msg_def, descriptor_pool) click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 111
def initialize(msg_def, descriptor_pool)
  @msg_def = msg_def
  @msg_class = nil
  @descriptor_pool = descriptor_pool
end
setup_accessors!() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 312
def self.setup_accessors!
  @descriptor.each do |field_descriptor|
    field_name = field_descriptor.name
    unless instance_methods(true).include?(field_name.to_sym)
      #TODO - at a high level, dispatching to either
      # index_internal or get_field would be logically correct, but slightly slower.
      if field_descriptor.map?
        define_method(field_name) do
          mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
          get_map_field(mutable_message_value[:map], field_descriptor)
        end
      elsif field_descriptor.repeated?
        define_method(field_name) do
          mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
          get_repeated_field(mutable_message_value[:array], field_descriptor)
        end
      elsif field_descriptor.sub_message?
        define_method(field_name) do
          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
      else
        c_type = field_descriptor.send(:c_type)
        if c_type == :enum
          define_method(field_name) do
            message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
            convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor)
          end
        else
          define_method(field_name) do
            message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
            convert_upb_to_ruby message_value, c_type
          end
        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
        define_method("#{field_name}_const") do
          if field_descriptor.repeated?
            return_value = []
            get_field(field_descriptor).send(:each_msg_val) do |msg_val|
              return_value << msg_val[:int32_val]
            end
            return_value
          else
            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
setup_oneof_accessors!() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 391
def self.setup_oneof_accessors!
  @oneof_field_names = []
  @descriptor.each_oneof do |oneof_descriptor|
    self.add_oneof_accessors_for! oneof_descriptor
  end
end
to_native(value, _ = nil) click to toggle source

@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

get_message(msg, descriptor, arena) click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 136
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)
    message.freeze if frozen?
  end
  message
end
private_constructor(msg_def, descriptor_pool) click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 117
def self.private_constructor(msg_def, descriptor_pool)
  instance = allocate
  instance.send(:initialize, msg_def, descriptor_pool)
  instance
end

Public Instance Methods

[](index) → value click to toggle source
Accesses a field's value by field name. The provided field name
should be a string.
# File lib/google/protobuf/ffi/message.rb, line 142
def [](name)
  raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
  index_internal name
end
[]=(index, value) click to toggle source
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
# File lib/google/protobuf/ffi/message.rb, line 154
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
build_message_class() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 33
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

    def freeze
      return self if frozen?
      super
      @arena.pin self
      self.class.descriptor.each do |field_descriptor|
        next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
        if field_descriptor.map? or field_descriptor.repeated? or field_descriptor.sub_message?
          get_field(field_descriptor).freeze
        end
      end
      self
    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
      unless Google::Protobuf::FFI.json_decode_message(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
        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

    def self.setup_accessors!
      @descriptor.each do |field_descriptor|
        field_name = field_descriptor.name
        unless instance_methods(true).include?(field_name.to_sym)
          #TODO - at a high level, dispatching to either
          # index_internal or get_field would be logically correct, but slightly slower.
          if field_descriptor.map?
            define_method(field_name) do
              mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
              get_map_field(mutable_message_value[:map], field_descriptor)
            end
          elsif field_descriptor.repeated?
            define_method(field_name) do
              mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
              get_repeated_field(mutable_message_value[:array], field_descriptor)
            end
          elsif field_descriptor.sub_message?
            define_method(field_name) do
              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
          else
            c_type = field_descriptor.send(:c_type)
            if c_type == :enum
              define_method(field_name) do
                message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
                convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor)
              end
            else
              define_method(field_name) do
                message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
                convert_upb_to_ruby message_value, c_type
              end
            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
            define_method("#{field_name}_const") do
              if field_descriptor.repeated?
                return_value = []
                get_field(field_descriptor).send(:each_msg_val) do |msg_val|
                  return_value << msg_val[:int32_val]
                end
                return_value
              else
                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

    def self.setup_oneof_accessors!
      @oneof_field_names = []
      @descriptor.each_oneof do |oneof_descriptor|
        self.add_oneof_accessors_for! oneof_descriptor
      end
    end
    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

    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(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, value: value), name: key.to_s)
          elsif field_descriptor.repeated?
            index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, @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 the argument definition.
    #
    # @param field [FieldDescriptor] Descriptor of the field to get
    def get_field(field, unwrap: false)
      if field.map?
        mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
        get_map_field(mutable_message_value[:map], field)
      elsif field.repeated?
        mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
        get_repeated_field(mutable_message_value[:array], field)
      elsif field.sub_message?
        return nil unless Google::Protobuf::FFI.get_message_has @msg, field
        sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field)
        if unwrap
          if field.has?(self)
            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
          mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
          sub_message = mutable_message[:msg]
          Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
        end
      else
        c_type = field.send(:c_type)
        message_value = Google::Protobuf::FFI.get_message_value @msg, field
        if c_type == :enum
          convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field)
        else
          convert_upb_to_ruby message_value, c_type
        end
      end
    end

    ##
    # @param array [::FFI::Pointer] Pointer to the 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, array: array)
        repeated_field.freeze if frozen?
      end
      repeated_field
    end

    ##
    # @param map [::FFI::Pointer] Pointer to the 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, map: map)
        map_field.freeze if frozen?
      end
      map_field
    end
  end
end
clear_internal(field_def) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 522
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
dup() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 74
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
each() { |get_field_by_index| ... } click to toggle source
# 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
each_oneof() { |get_oneof_by_index| ... } click to toggle source
# 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
eql?(other) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 83
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_descriptor() click to toggle source
# 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
freeze() click to toggle source
Calls superclass method
# File lib/google/protobuf/ffi/message.rb, line 61
def freeze
  return self if frozen?
  super
  @arena.pin self
  self.class.descriptor.each do |field_descriptor|
    next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
    if field_descriptor.map? or field_descriptor.repeated? or field_descriptor.sub_message?
      get_field(field_descriptor).freeze
    end
  end
  self
end
get_field(field, unwrap: false) click to toggle source

Gets a field of this message identified by the argument definition.

@param field [FieldDescriptor] Descriptor of the field to get

# File lib/google/protobuf/ffi/message.rb, line 593
def get_field(field, unwrap: false)
  if field.map?
    mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
    get_map_field(mutable_message_value[:map], field)
  elsif field.repeated?
    mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
    get_repeated_field(mutable_message_value[:array], field)
  elsif field.sub_message?
    return nil unless Google::Protobuf::FFI.get_message_has @msg, field
    sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field)
    if unwrap
      if field.has?(self)
        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
      mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
      sub_message = mutable_message[:msg]
      Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
    end
  else
    c_type = field.send(:c_type)
    message_value = Google::Protobuf::FFI.get_message_value @msg, field
    if c_type == :enum
      convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field)
    else
      convert_upb_to_ruby message_value, c_type
    end
  end
end
get_map_field(map, field) click to toggle source

@param map [::FFI::Pointer] Pointer to the Map @param field [Google::Protobuf::FieldDescriptor] Type of the map field

# File lib/google/protobuf/ffi/message.rb, line 646
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, map: map)
    map_field.freeze if frozen?
  end
  map_field
end
get_repeated_field(array, field) click to toggle source

@param array [::FFI::Pointer] Pointer to the Array @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field

# File lib/google/protobuf/ffi/message.rb, line 633
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, array: array)
    repeated_field.freeze if frozen?
  end
  repeated_field
end
hash() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 105
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
index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false) click to toggle source

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 534
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
index_internal(name) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 527
def index_internal(name)
  field_descriptor = self.class.descriptor.lookup(name)
  get_field field_descriptor unless field_descriptor.nil?
end
inspect() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 58
def inspect
  "Descriptor - (not the message class) #{name}"
end
lookup(name) click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 86
def lookup(name)
  Google::Protobuf::FFI.get_field_by_name(self, name, name.size)
end
lookup_oneof(name) click to toggle source
# 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
method_missing(method_name, *args) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 45
def method_missing(method_name, *args)
  method_missing_internal method_name, *args, mode: :method_missing
end
method_missing_internal(method_name, *args, mode: nil) click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 508
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
msgclass() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 94
def msgclass
  @msg_class ||= build_message_class
end
name() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 66
def name
  @name ||= Google::Protobuf::FFI.get_message_fullname(self)
end
options() click to toggle source
# 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)
    Google::Protobuf::MessageOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).freeze
  end
end
respond_to_missing?(method_name, include_private = false) click to toggle source
Calls superclass method
# File lib/google/protobuf/ffi/message.rb, line 49
def respond_to_missing?(method_name, include_private = false)
  method_missing_internal(method_name, mode: :respond_to_missing?) || super
end
to_h() click to toggle source
# File lib/google/protobuf/ffi/message.rb, line 118
def to_h
  to_h_internal @msg, self.class.descriptor
end
to_native() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 43
def to_native
  self.class.to_native(self)
end
to_s() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 54
def to_s
  inspect
end

Private Instance Methods

pool() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 146
def pool
  @descriptor_pool
end
wrapper?() click to toggle source
# File lib/google/protobuf/ffi/descriptor.rb, line 123
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