Replicant.Casting.Types (Replicant v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

Casts a PostgreSQL string value to its appropriate Elixir type.

Functions

cast_record(record, type)

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.