DpExchange.Core.Types.Validate (DpExchangeCore v0.3.14)

Copy Markdown View Source

The validating constructor every Types.* struct builds new/1 on.

@enforce_keys guards presence, not nil

@enforce_keys [:open, :high, :low, :close, ...] makes struct!(Candle, attrs) raise when a key is absent from attrs. It does nothing when the key is present and nil: struct!(Candle, open: nil, high: ..., ...) builds without complaint, even though Candle's own typespec declares open: Decimal.t() — never Decimal.t() | nil.

That gap is not academic. A nil in a field the typespec calls non-nilable is exactly what a JSON decode bug produces — Map.get(json, "open") on a key the venue renamed returns nil, silently, and every value downstream stays plausible until the struct reaches Decimal.compare/2 or similar, several calls away from where the bad data actually entered. The error message at that point names a Decimal function, not the venue response that caused it.

What new!/3 adds

struct!/2, plus a check that every field named in required_non_nil is present AND non-nil, raising ArgumentError naming the offending field when it is not. A caller gets a message pointing at the exact field and the venue's own module, at the moment the struct was about to be built — not a FunctionClauseError inside Decimal three frames later with no indication which field was the problem.

What this deliberately does not do

No coercion, no defaulting, no guessing. A nil where the type forbids one is an error, full stop — the same rule this whole family applies everywhere else. new!/3 fails closed; it does not repair the input.

Struct literals still work

Nothing here removes defstruct or @enforce_keys from any Types.* module, and %Candle{...} remains a valid, unchecked way to build one — internal code and tests that already construct known-good values by hand are unaffected. new/1 (built on this module, one per type) is the path a venue package's decoder should prefer, because it is the one that turns a decode bug into an error at the boundary instead of a crash three calls downstream.

Summary

Functions

Builds module's struct from attrs, raising ArgumentError naming the field if any of required_non_nil is absent or explicitly nil.

Functions

new!(module, required_non_nil, attrs)

@spec new!(module(), [atom()], keyword() | map()) :: struct()

Builds module's struct from attrs, raising ArgumentError naming the field if any of required_non_nil is absent or explicitly nil.

required_non_nil is ordinarily the type's own @enforce_keys — the fields whose typespec admits no nil. A type that deliberately allows nil on some enforced keys (see DpExchange.Core.Types.Order, which enforces presence of seven keys but documents that six of them may honestly be nil) passes a narrower list naming only the fields that must never be nil.