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
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.
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
@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.
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.
@spec parse_types() :: [String.t()]
Returns the parse types (field_maps slots) that govern a unified struct.
Returns the unified struct module governed by a parse type, or nil.