# `F3.FlatBuffer`

Bounded reader for the FlatBuffers tables this library needs.

Offsets are checked against the buffer before every read. A table that
points at itself stops at a depth or visit cap and returns `:depth`
rather than looping. This is not a general FlatBuffers toolkit. Field
numbers are the positions in the pinned `File.fbs` and in Arrow IPC 53.

# `error`

```elixir
@type error() :: :corrupt | :depth
```

`:corrupt` is a bad offset. `:depth` is the visit or nesting cap.

# `t`

```elixir
@type t() :: %F3.FlatBuffer{
  bin: binary(),
  depth: non_neg_integer(),
  object_size: pos_integer(),
  pos: non_neg_integer(),
  visits: non_neg_integer(),
  vtable: non_neg_integer(),
  vtable_size: pos_integer()
}
```

One open table.

`bin` is the whole buffer. `pos` is the table's start. `vtable` is the
vtable start. `vtable_size` and `object_size` come from that vtable.
`depth` counts nested tables. `visits` counts tables opened from this root.

# `bytes`

```elixir
@spec bytes(t(), non_neg_integer()) :: {:ok, binary() | nil} | {:error, error()}
```

Raw byte vector, including an Arrow IPC message. Absent is `nil`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.bytes(table, 1)
    {:ok, nil}

# `optional_scalar`

```elixir
@spec optional_scalar(
  t(),
  non_neg_integer(),
  :u8 | :u16 | :u32 | :u64 | :i16 | :i32 | :bool
) ::
  {:ok, term() | nil} | {:error, error()}
```

Same as `scalar/4`, but an absent slot is `nil` instead of a default.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.optional_scalar(table, 1, :u8)
    {:ok, nil}

# `root`

```elixir
@spec root(binary()) :: {:ok, t()} | {:error, error()}
```

Opens the root table. The first four bytes are a little-endian offset
from the start of `bin`. An empty or non-binary buffer is `:corrupt`.
This does not raise for those inputs.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> table.pos
    10

    iex> F3.FlatBuffer.root(<<>>)
    {:error, :corrupt}

# `scalar`

```elixir
@spec scalar(
  t(),
  non_neg_integer(),
  :u8 | :u16 | :u32 | :u64 | :i16 | :i32 | :bool,
  term()
) ::
  {:ok, term()} | {:error, error()}
```

Signed or unsigned little-endian scalar. An absent slot returns `default`.

`index` is the vtable field, starting at 0. `kind` is `:u8`, `:u16`,
`:u32`, `:u64`, `:i16`, `:i32`, or `:bool`. A bad offset is `:corrupt`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.scalar(table, 0, :u8, 0)
    {:ok, 7}
    iex> F3.FlatBuffer.scalar(table, 1, :u8, 0)
    {:ok, 0}

# `string`

```elixir
@spec string(t(), non_neg_integer()) :: {:ok, String.t() | nil} | {:error, error()}
```

UTF-8 string. Absent is `nil`. Bytes that are not UTF-8 are `:corrupt`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.string(table, 1)
    {:ok, nil}

# `string_vector`

```elixir
@spec string_vector(t(), non_neg_integer()) :: {:ok, [String.t()]} | {:error, error()}
```

Vector of UTF-8 strings. Absent is an empty list.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.string_vector(table, 1)
    {:ok, []}

# `table`

```elixir
@spec table(t(), non_neg_integer()) :: {:ok, t() | nil} | {:error, error()}
```

Follows a table slot. Absent is `nil`. A slot that does not point at a
table is `:corrupt`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.table(table, 1)
    {:ok, nil}

# `tables`

```elixir
@spec tables(t(), non_neg_integer()) :: {:ok, [t()]} | {:error, error()}
```

Vector of tables. Absent is an empty list.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.tables(table, 1)
    {:ok, []}

# `uints`

```elixir
@spec uints(t(), non_neg_integer(), 4 | 8) ::
  {:ok, [non_neg_integer()]} | {:error, error()}
```

Vector of unsigned little-endian integers. Absent is an empty list.
`width` is 4 or 8. Any other width raises `FunctionClauseError`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 7, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.uints(table, 1, 4)
    {:ok, []}

# `union`

```elixir
@spec union(t(), non_neg_integer()) ::
  {:ok, :none | {non_neg_integer(), t()}} | {:error, error()}
```

Union stored as a type byte at `type_index` and a table at the next slot.

Type `0` is none. Any other type follows the table. The caller interprets
the type byte. A non-zero type with no table is `:corrupt`.

## Examples

    iex> bin = <<10::little-32, 6::little-16, 8::little-16, 4::little-16, 6::little-32, 0, 0, 0, 0>>
    iex> {:ok, table} = F3.FlatBuffer.root(bin)
    iex> F3.FlatBuffer.union(table, 0)
    {:ok, :none}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
