Bourse.Unified.FieldMaps (bourse v0.1.0)

Copy Markdown View Source

Derives unified struct field sets from authored normalization.field_maps.

The source of truth is the union of canonical unified keys authored per parse type across the supported venues. The hand-authored structs (Bourse.Ticker, Bourse.Order, ...) keep their curated docs, typespecs, and helper functions, but their field set is governed by the spec — Bourse.Unified.FieldMapsTest fails the build when a struct drifts from the derived canonical set.

Why governance, not full code generation

Most field_maps belong to the core parse* types; many derivative structs have no authored field map to derive from). A few Tier 2/3 types do carry one and are governed alongside the core set (e.g. Bourse.OpenInterest), so @struct_for is the authoritative governed list rather than a fixed count. And the core structs carry curated helpers (Bourse.Order status predicates, Bourse.Balance.get/2, Bourse.OHLCV.from_list/1) that no generator can emit. So the spec governs the field set (drift-guarded by a test), not the full module source.

Honesty Rule

A canonical key appears in the derived set even when no exchange statically resolved a coercion for it (field_map[key] == null) — it is part of Bourse's unified shape and surfaces as a nil-valued struct field. Slots carrying a non-nil _unresolved_reason (e.g. multi_payload_branching:<N>) still contribute whatever keys they expose.

Naming divergences

A small set of canonical keys map to a different struct field name by design (mirrored in Bourse.ResponseParser). These are listed in divergences/1 and applied when computing the canonical field set for a struct.

Field names are surfaced as snake_case strings (not atoms): the derived set is compared against struct keys at governance time, so deriving atoms from raw spec strings would grow the atom table for no consumer benefit.

iex> "ask" in Bourse.Unified.FieldMaps.canonical_fields("ticker")
true

iex> Bourse.Unified.FieldMaps.coercion_type("safeNumber")
:number

Summary

Functions

Derives the full canonical field set per parse type as %{parse_type => [str]}.

Derives the canonical field set for a parse type as a sorted list of snake_case strings.

Maps a coercion vocabulary token to its JSONSpec scalar type.

Returns the by-design canonical-key -> struct-field renames for a parse type.

Returns the by-design canonical-key -> struct-field renames for a unified struct module, or an empty map. Lets a verifier reconcile our struct field names (order_id) against Bourse's canonical keys (order) from the same source of truth as the runtime parser.

Returns the parse types (field_maps slots) that govern a unified struct.

Returns the unified struct module governed by a parse type, or nil.

Functions

canonical_field_sets()

@spec canonical_field_sets() :: %{required(String.t()) => [String.t()]}

Derives the full canonical field set per parse type as %{parse_type => [str]}.

canonical_fields(parse_type)

@spec canonical_fields(String.t()) :: [String.t()]

Derives the canonical field set for a parse type as a sorted list of snake_case strings.

The set is the union of field_map keys across all in-scope exchange specs, converted to snake_case, with the parse type's divergences/1 renames applied. OHLCV carries no field map (array shape) and yields [].

iex> fields = Bourse.Unified.FieldMaps.canonical_fields("trade")
iex> "order_id" in fields and "order" not in fields
true

coercion_type(coercion)

@spec coercion_type(String.t() | nil) ::
  :string | :integer | :number | :boolean | :unknown

Maps a coercion vocabulary token to its JSONSpec scalar type.

Returns :unknown for a nil coercion (an unresolved canonical key) or an unrecognised token.

divergences(parse_type)

@spec divergences(String.t()) :: %{required(String.t()) => String.t()}

Returns the by-design canonical-key -> struct-field renames for a parse type.

divergences_for_struct(module)

@spec divergences_for_struct(module() | nil) :: %{required(String.t()) => String.t()}

Returns the by-design canonical-key -> struct-field renames for a unified struct module, or an empty map. Lets a verifier reconcile our struct field names (order_id) against Bourse's canonical keys (order) from the same source of truth as the runtime parser.

parse_types()

@spec parse_types() :: [String.t()]

Returns the parse types (field_maps slots) that govern a unified struct.

struct_for(parse_type)

@spec struct_for(String.t()) :: module() | nil

Returns the unified struct module governed by a parse type, or nil.