# `Replicant.Casting.Types`
[🔗](https://github.com/baselabs/replicant/blob/v0.3.1/lib/replicant/casting/types.ex#L1)

Cast from Postgres to Elixir types

Implementation inspired by Cainophile, Supabase Realtime and Sequin.
Vendored from walex 4.8.0 (`WalEx.Casting.Types`).

## Raise-sites (decode-boundary contract)

`cast_record/2` is *lenient by default*: every clause that parses a scalar
wraps the parse in a `case`/`Integer.parse`/`Float.parse` and falls back to
the original string on failure, and the catch-all clause returns the value
unchanged for unknown types. Parse-*failures* therefore never raise.

Three clauses can still raise on *truly-malformed* input, because they call
a bang/raising function with no local rescue:

  * `Decimal.new/1` (the `"numeric"`, `"decimal"`, `"money"` scalar clauses
    and the `"_numeric"`/`"_decimal"`/`"_money"` array clauses) raises
    `Decimal.Error` on a malformed numeric string.
  * `Base.decode16!/2` (the `"bytea"` scalar clause and the `"_bytea"` array
    clause) raises `ArgumentError` on non-hex bytea payloads.
  * `DateTime.from_naive!/2` (the `"timestamp"` scalar clause and the
    `"_timestamp"` array clause) raises on an invalid naive datetime; in
    practice the preceding `NaiveDateTime.from_iso8601/1` guards it, but the
    bang call is retained verbatim from upstream.

`Jason.decode/1` (the `"jsonb"`/`"json"` scalar and `"_jsonb"`/`"_json"`
array clauses) does NOT raise — it returns `{:error, _}`, which the
surrounding `case` collapses to the lenient fallback.

Because malformed numeric/bytea inputs raise *through* `cast_record/2`, the
Assembler (Task 13) MUST invoke `cast_record/2` inside a decode boundary —
either `Replicant.Decoder.decode/1` (Task 8) or its own `try/rescue` — so a
single malformed cell scrubs to a boundary error rather than crashing the
pipeline. Do NOT widen `cast_record/2` to swallow these: the lenient
fallback already covers ordinary parse-failures; only genuinely-malformed
input reaches the raising path, and the boundary is the correct place to
scrub it.

# `cast_record`

Casts a PostgreSQL string value to its appropriate Elixir type.

## Examples

    iex> cast_record("t", "bool")
    true

    iex> cast_record("123", "int4")
    123

    iex> cast_record("123.45", "numeric")
    #Decimal<123.45>

    iex> cast_record("{1,2,3}", "_int4")
    [1, 2, 3]

    iex> cast_record("2024-01-15T10:30:00Z", "timestamptz")
    #DateTime<2024-01-15 10:30:00Z>

Special values like NaN and Infinity are handled:

    iex> cast_record("NaN", "float8")
    :nan

Returns the original value if casting fails.

---

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