# `F3.Error`

Error returned by the public F3 API.

`message` is safe to show to a caller. It does not include file bytes,
secrets, or an underlying payload. `context` carries small identifiers
such as a path, offset, or operation name.

# `category`

```elixir
@type category() ::
  :not_implemented
  | :not_found
  | :io
  | :closed
  | :invalid_argument
  | :invalid_magic
  | :unsupported_version
  | :truncated
  | :corrupt_metadata
  | :unsupported_encoding
  | :invalid_projection
  | :checksum
  | :resource_limit
  | :invalid_type
  | :native_unavailable
  | :corrupt
```

Stable class of a failure.

`:not_implemented` is a feature this build does not perform.
`:not_found` is a missing path or HTTP object. `:io` is a source failure.
`:closed` is a handle that was already released. `:invalid_argument` is a
caller value this function does not accept. `:invalid_magic` is a file that
does not end in `F3`. `:unsupported_version` is a wire pair other than 0.1.
`:truncated` is a short file or a short HTTP body. `:corrupt_metadata` is a
footer that does not describe the file. `:unsupported_encoding` is an
encoding this build will not decode. `:invalid_projection` is an unknown
column name. `:checksum` is a stored chunk checksum that did not match.
`:resource_limit` is a size above the caller's cap, including a timeout.
`:invalid_type` is a value that does not fit the field. `:native_unavailable`
means the NIF was not compiled. `:corrupt` is an encoding unit that could
not be decoded.

# `t`

```elixir
@type t() :: %F3.Error{
  category: category(),
  cause: term(),
  context: map(),
  message: String.t(),
  reason: atom()
}
```

One failure.

`reason` is the operation or the underlying atom, such as `:enoent`.
`category` is the stable class. `message` is safe to show. `cause` is
reserved and is `nil` for errors this library builds. `context` holds
small identifiers. Constructors set `context.operation`. File bytes and
secrets are not fields.

# `checksum`

```elixir
@spec checksum(atom(), String.t()) :: t()
```

A chunk checksum was present and did not match the bytes.

## Examples

    iex> F3.Error.checksum(:read, "chunk checksum mismatch").category
    :checksum

# `closed`

```elixir
@spec closed(atom()) :: t()
```

The handle has been closed.

## Examples

    iex> error = F3.Error.closed(:read)
    iex> {error.category, error.context.operation}
    {:closed, :read}

# `corrupt`

```elixir
@spec corrupt(atom(), String.t()) :: t()
```

An encoding unit could not be decoded. The message does not include file bytes.

## Examples

    iex> F3.Error.corrupt(:decode, "native column term is the wrong shape").category
    :corrupt

# `corrupt_metadata`

```elixir
@spec corrupt_metadata(atom(), String.t(), map()) :: t()
```

A size, offset, or FlatBuffers field does not describe this file.

## Examples

    iex> F3.Error.corrupt_metadata(:open, "footer is truncated").category
    :corrupt_metadata

# `invalid_argument`

```elixir
@spec invalid_argument(atom(), String.t()) :: t()
```

The caller passed a value the function does not accept.

`operation` is the public call. `message` is the sentence returned to the
caller. This does not raise.

## Examples

    iex> error = F3.Error.invalid_argument(:open, "options must be a keyword list")
    iex> error.category
    :invalid_argument

# `invalid_magic`

```elixir
@spec invalid_magic(atom()) :: t()
```

The last two bytes are not the F3 magic.

## Examples

    iex> F3.Error.invalid_magic(:open).message
    "file does not end with the F3 magic"

# `invalid_projection`

```elixir
@spec invalid_projection(atom(), String.t()) :: t()
```

A projected name is not a top-level field.

## Examples

    iex> error = F3.Error.invalid_projection(:read, "missing")
    iex> error.message
    "unknown column missing"

# `invalid_type`

```elixir
@spec invalid_type(atom(), String.t()) :: t()
```

A value does not fit the field type.

## Examples

    iex> F3.Error.invalid_type(:writer_write, "id is not an integer").category
    :invalid_type

# `io`

```elixir
@spec io(atom(), atom(), map()) :: t()
```

A source operation failed. `reason` is an Erlang file error atom.
`context` is merged with `%{operation: operation}`. The default context
is an empty map.

## Examples

    iex> error = F3.Error.io(:pread, :eacces)
    iex> error.message
    "pread failed: eacces"

    iex> error = F3.Error.io(:pread, :redirect, %{status: 301})
    iex> error.context.status
    301

# `native_unavailable`

```elixir
@spec native_unavailable(atom()) :: t()
```

Row decoding needs the native library and this build does not have it.

## Examples

    iex> F3.Error.native_unavailable(:read).message
    "native decoder is not loaded"

# `not_found`

```elixir
@spec not_found(Path.t()) :: t()
```

A local path does not exist. `reason` is `:enoent`.

## Examples

    iex> error = F3.Error.not_found("missing-file.f3")
    iex> error.message
    "file not found: missing-file.f3"

# `not_implemented`

```elixir
@spec not_implemented(atom()) :: t()
```

Builds an error for an operation this experimental version does not perform.

## Examples

    iex> error = F3.Error.not_implemented(:read)
    iex> {error.category, error.message}
    {:not_implemented, "read is not implemented in this experimental build"}

# `resource_limit`

```elixir
@spec resource_limit(atom(), String.t(), map()) :: t()
```

A size field is above the caller's allocation cap. A timed-out request
uses this category as well.

## Examples

    iex> error = F3.Error.resource_limit(:open, "request timed out", %{timeout_ms: 10})
    iex> error.context.timeout_ms
    10

# `truncated`

```elixir
@spec truncated(atom(), String.t(), map()) :: t()
```

The file ends before a required field. `context` defaults to an empty map.

## Examples

    iex> error = F3.Error.truncated(:open, "file is shorter than the 32 byte postscript")
    iex> error.category
    :truncated

# `unsupported_encoding`

```elixir
@spec unsupported_encoding(atom(), String.t(), map()) :: t()
```

The file names an encoding this build will not decode.

## Examples

    iex> F3.Error.unsupported_encoding(:read, "CUSTOM_WASM is not executed").category
    :unsupported_encoding

# `unsupported_version`

```elixir
@spec unsupported_version(atom(), non_neg_integer(), non_neg_integer()) :: t()
```

The postscript version is outside the pinned pair.

## Examples

    iex> error = F3.Error.unsupported_version(:open, 1, 0)
    iex> error.message
    "unsupported F3 version 1.0"

---

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