zfin/src/srf_opts.zig
Emil Lerch 40869cb3a4
lint srf files in audit / doctor commands
This also provides ensurance through zig build test that all
fields are documented in markdown so they do not get out of sync
2026-09-24 06:39:23 -07:00

59 lines
3.1 KiB
Zig

//! SRF coercion policy, in one place.
//!
//! SRF encodes a field's type in its separator: `key::v` is a string,
//! `key:num:v` a number, `key:bool:v` a boolean. Coercion into a typed
//! struct therefore depends on the writer having picked the right one.
//!
//! That assumption holds for files zfin writes and breaks for files
//! people write, so the two get different options - and which one a
//! parser wants is a property of where the file came from, not of the
//! struct being parsed. Naming the two policies keeps that decision
//! visible at the call site instead of buried in whichever comment
//! happened to explain it first.
const srf = @import("srf");
/// For files a HUMAN edits: `portfolio.srf`, `accounts.srf`,
/// `metadata.srf`, `watchlist.srf`, `transaction_log.srf`,
/// `projections.srf`, `imported_values.srf`, `acknowledgments.srf`, and
/// the keybind config.
///
/// **`grep -rn srf_opts.user_edited src/` is the authoritative index of
/// hand-edited SRF files, and `src/srf_lint.zig`'s `schemas` registry
/// must cover the same set.** A new parse site here needs a matching
/// `srf_schema` on its model and an entry in that registry, or the
/// file's field names go unchecked - SRF silently discards a key that
/// names no field of the target struct, so a typo in an unregistered
/// file is invisible. (The one deliberate exception is
/// `imported_values.srf`, which is machine-generated; see the registry's
/// doc comment.) Zig cannot enforce this - there is no way to ask which
/// files reference a constant - so it is a convention, hence this note.
///
/// Accepts a string where a number was declared, because a hand-typed
/// `close_price::200.00` instead of `close_price:num:200.00` is a
/// slip, not a different intent. SRF's own doc says as much: strict
/// coercion is "intended for performant access for cache use cases...
/// if you want to use this for human-edited files, turn this on".
///
/// It is also a safety measure, which is the part worth not
/// forgetting. Under strict coercion a string reaching a numeric field
/// falls through to an unchecked `val.?.number` inside SRF - a panic
/// in Debug/ReleaseSafe and undefined behaviour in ReleaseFast, which
/// is how zfin is built. One `close_price::200.00` once took down
/// every `zfin portfolio` run; the fix was to turn this on for
/// `portfolio.srf` alone, which left every other hand-edited file
/// exposed to the same typo.
pub const user_edited: srf.CoercionOptions = .{ .strings_to_numbers = true };
/// For files ZFIN writes: the candle, quote, and options caches,
/// `cusip_tickers.srf` under `cache_dir`, server responses, and the
/// `history/<date>-portfolio.srf` snapshots produced by
/// `zfin snapshot`.
///
/// Keeps SRF's strict default. The writer is a machine that always
/// emits `:num:` for numbers, so accepting a string instead would only
/// mask a serializer bug rather than tolerate a human slip - these
/// files are not edited after the fact. Strictness is also free
/// performance on the hot paths, where a cached candle file is
/// millions of records.
pub const machine_written: srf.CoercionOptions = .{};