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

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
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 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
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 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
deep_copy(msg, arena = nil) click to toggle source
# 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
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 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
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 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
encode_json(message, options = {}) click to toggle source
# 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
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 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
inspect_internal(msg) click to toggle source

@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
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 113
def initialize(msg_def, descriptor_pool)
  @msg_def = msg_def
  @msg_class = nil
  @descriptor_pool = descriptor_pool
end
setup_accessors!() click to toggle source

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
setup_oneof_accessors!() click to toggle source

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
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 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
private_constructor(msg_def, descriptor_pool) click to toggle source
# 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

[](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 162
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 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
build_message_class() click to toggle source
# 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
clear_internal(field_def) click to toggle source
# 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
dup() click to toggle source
# 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
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
empty_frozen_map(field_descriptor) click to toggle source

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
empty_frozen_repeated_field(field_descriptor) click to toggle source

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
eql?(other) click to toggle source
# 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_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

Freezes the map object. We have to intercept this so we can freeze the underlying representation, not just the Ruby wrapper. Returns self.

Calls superclass method
# 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
frozen?() click to toggle source

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.

Calls superclass method
# 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
frozen_map_from_field_descriptor(field_descriptor) click to toggle source

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
frozen_repeated_field_from_field_descriptor(field_descriptor) click to toggle source

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
get_field(field, unwrap: false) click to toggle source

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
get_map_field(map, field) click to toggle source

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
get_repeated_field(array, field) click to toggle source

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
hash() click to toggle source
# 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
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 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
index_internal(name) click to toggle source

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
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
map_from_field_descriptor(field_descriptor) click to toggle source

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
message_from_field_descriptor(field_descriptor) click to toggle source

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
method_missing(method_name, *args) click to toggle source
# 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
method_missing_internal(method_name, *args, mode: nil) click to toggle source
# 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
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)
    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
repeated_field_from_field_descriptor(field_descriptor) click to toggle source

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
respond_to_missing?(method_name, include_private = false) click to toggle source
Calls superclass method
# 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
scalar_from_field_descriptor(field_descriptor) click to toggle source

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