uniffi_bindgen 0.32.0

a multi-language bindings generator for rust (codegen and cli tooling)
Documentation
class RustCallStatus < FFI::Struct
  layout :code,    :int8,
         :error_buf, RustBuffer

  def code
    self[:code]
  end

  def error_buf
    self[:error_buf]
  end

  def to_s
    "RustCallStatus(code=#{self[:code]})"
  end
end

# These match the values from the uniffi::rustcalls module
CALL_SUCCESS = 0
CALL_ERROR = 1
CALL_PANIC = 2
{%- for e in ci.enum_definitions() %}
{% if ci.is_name_used_as_error(e.name()) %}
{% if e.is_flat() %}
class {{ e.name()|class_name_rb }}
    {%- for variant in e.variants() %}
    {{ variant.name()|class_name_rb }} = Class.new StandardError
    {%- endfor %}
{% else %}
module {{ e.name()|class_name_rb }}
  {%- for variant in e.variants() %}
  class {{ variant.name()|class_name_rb }} < StandardError
    {%- let named_fields = variant.has_fields() && !variant.fields()[0].name().is_empty() %}
    {%- if named_fields %}
    def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}:{% if !loop.last %}, {% endif %}{% endfor %})
        {% for field in variant.fields() %}
        @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
        {% endfor %}
        super()
    end
    {% else %}
    def initialize({% for field in variant.fields() %}v{{ loop.index }}{% if !loop.last %}, {% endif %}{% endfor %})
        {% if variant.has_fields() %}
        @values = [{% for field in variant.fields() %}v{{ loop.index }}{% if !loop.last %}, {% endif %}{% endfor %}]
        {% endif %}
        super()
    end
    {% endif %}
    {%- if variant.has_fields() %}
    {%- if named_fields %}

    attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}
    {%- else %}

    attr_reader :values

    def [](index)
        @values[index]
    end
    {%- endif %}
    {% endif %}

    def to_s
      {%- if named_fields %}
        "#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})"

      {%- else %}
      {%- if variant.has_fields() %}
        "#{self.class.name}(#{@values.inspect})"
      {%- else %}
        "#{self.class.name}()"
      {%- endif %}
      {%- endif %}

    end
  end
  {%- endfor %}
{% endif %}
end
{% endif %}
{%- endfor %}

# Map error modules to the RustBuffer method name that reads them
ERROR_MODULE_TO_READER_METHOD = {
{% for e in ci.enum_definitions() %}
{%- if ci.is_name_used_as_error(e.name()) -%}
  {{ e.name()|class_name_rb }} => :read_{{ self::canonical_name(e.as_type().borrow()) }},
{% endif %}
{%- endfor -%}
{% for obj in ci.object_definitions() %}
{%- if ci.is_name_used_as_error(obj.name()) -%}
  '{{ obj.name()|class_name_rb }}' => :read_{{ self::canonical_name(obj.as_type().borrow()) }},
{% endif %}
{%- endfor -%}
}

private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
                 :RustCallStatus

def self.consume_buffer_into_error(error_module, rust_buffer)
  rust_buffer.consumeWithStream do |stream|
    reader_method = ERROR_MODULE_TO_READER_METHOD[error_module] ||
                    ERROR_MODULE_TO_READER_METHOD[error_module.name&.split('::')&.last]
    return stream.send(reader_method)
  end
end

class InternalError < StandardError
end

def self.rust_call(fn_name, *args)
  # Call a rust function
  rust_call_with_error(nil, fn_name, *args)
end

def self.rust_call_with_error(error_module, fn_name, *args)
  # Call a rust function and handle errors
  #
  # Use this when the rust function returns a Result<>.  error_module must be the error_module that corresponds to that Result.


  # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
  # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
  # data=NULL))
  status = RustCallStatus.new
  args << status

  result = UniFFILib.public_send(fn_name, *args)

  case status.code
  when CALL_SUCCESS
    result
  when CALL_ERROR
    if error_module.nil?
      status.error_buf.free
      raise InternalError, "CALL_ERROR with no error_module set"
    else
      raise consume_buffer_into_error(error_module, status.error_buf)
    end
  when CALL_PANIC
    # When the rust code sees a panic, it tries to construct a RustBuffer
    # with the message.  But if that code panics, then it just sends back
    # an empty buffer.
    if status.error_buf.len > 0
      raise InternalError, {{ "status.error_buf"|lift_rb(&Type::String, config) }}
    else
      raise InternalError, "Rust panic"
    end
  else
    raise InternalError, "Unknown call status: #{status.code}"
  end
end

private_class_method :consume_buffer_into_error