# `Capstan.Casting.Types`
[🔗](https://github.com/baselabs/capstan/blob/v1.3.1/lib/capstan/casting/types.ex#L1)

Casts one column's raw row-image bytes to an Elixir term, and owns the per-column
metadata-width split of a `TABLE_MAP`'s raw `column_metadata` blob.

This is the silent-wrong-value surface of C1, so the whole module is built around
*never emitting a plausible-but-wrong value*. Three failure modes get first-class
defence:

  * **Signedness.** Integer width comes from the wire type byte, but *signedness
    does not* — it rides `TABLE_MAP` optional-metadata TLV type 1. `cast/4` takes the
    resolved signedness bit as an explicit argument; a `BIGINT UNSIGNED` of
    `18446744073709551615` must never decode to `-1`.
  * **`SET` masquerading as `ENUM`.** Both are wire type `254` (`MYSQL_TYPE_STRING`)
    and are distinguishable only by the STRING metadata pair (byte 0 real-type:
    `247` = `ENUM`, `248` = `SET`). `SET`'s row image is packed as a bitfield, not an
    index, so an enum-style decode would be silently wrong. C1 defers `SET`
    (roadmap row C4): `parse_col_meta/2` unpacks the pair to *detect* it and `cast/4`
    **fails closed** with `:unsupported_column_type`.
  * **Meta-driven widths.** Fractional-second width, decimal precision/scale, blob
    length-prefix width and string length all come from the metadata, not the type
    byte. A fixed-width assumption both mis-values the column *and* desynchronises
    every column after it. `parse_col_meta/2` enumerates every supported type's meta
    width **explicitly** and fails closed on the rest, rather than guessing with a
    1-byte fallback (the bug in `probe/mysql_binlog_probe.exs:301-311`).

## Fail-closed contract

Both entry points return `{:error, {:unsupported_column_type, detail}}` rather than a
value whenever the type, its metadata, or a decoded temporal component is one C1 does
not faithfully support. The owner (`Capstan.Assembler`) turns that into a stream halt, exactly as
it does for the registry's `{:error, :unmapped_table_id}` — a column C1 cannot decode
is never silently dropped or passed through raw. `detail` carries only schema-level
facts (wire type byte, column position); never a column value (Rule 1).

# `col_meta`

```elixir
@type col_meta() ::
  {:int, 1 | 2 | 3 | 4 | 8}
  | {:decimal, non_neg_integer(), non_neg_integer()}
  | {:varstring, non_neg_integer()}
  | {:char, non_neg_integer()}
  | {:enum, 1 | 2}
  | {:set, 1 | 2}
  | {:blob, 1..4}
  | {:datetime, 0..6}
  | {:timestamp, 0..6}
  | {:time, 0..6}
  | :date
  | {:json, 1..4}
  | {:geometry, 1..4}
```

A single column's parsed metadata — self-describing enough for `cast/4` to decode the
value, produced by `parse_col_meta/2`.

# `unsupported`

```elixir
@type unsupported() :: {:unsupported_column_type, map()}
```

The fail-closed signal: a type/meta/value C1 refuses rather than mis-decode.

# `cast`

```elixir
@spec cast(col_meta(), boolean(), [String.t()], binary()) ::
  {:ok, term(), binary()} | {:error, unsupported()}
```

Casts one column's leading bytes of a row image to an Elixir term.

Arguments:

  * `col_meta` — the column's `t:col_meta/0` from `parse_col_meta/2`.
  * `signed?` — the resolved signedness bit (`true` = signed). Consumed only by
    integer casts; supplied by the caller from TLV type 1.
  * `str_values` — the allowed member strings for an `ENUM` column, `[]` otherwise.
  * `bytes` — the remaining row-image bytes, positioned at this column's value.

Returns `{:ok, value, rest}` with the bytes this column consumed removed, or
`{:error, {:unsupported_column_type, detail}}` (`SET`, JSON opaque, or an
out-of-range temporal/enum) — never a wrong value and never a raw passthrough.

# `parse_col_meta`

```elixir
@spec parse_col_meta([byte()], binary()) ::
  {:ok, [col_meta()]} | {:error, unsupported()}
```

Splits a `TABLE_MAP`'s raw `column_metadata` blob into one `t:col_meta/0` per column.

`column_types` is the wire type byte per column (in order); `column_metadata` is the
raw, unsplit blob the decoder stored verbatim. Each type consumes a *type-specific*
number of metadata bytes — enumerated explicitly here. The first column whose type C1
does not support **fails closed**: an unknown meta width cannot be skipped without
desynchronising every later column, so the whole table is refused.

Returns `{:ok, [col_meta]}` aligned with `column_types`, or
`{:error, {:unsupported_column_type, detail}}`.

---

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