Skip to content

fix(models): make FMP-omittable ExchangeVariant and screener fields optional - #388

Merged
benbenbang merged 2 commits into
mainfrom
feat/368-search-nullable
Sep 30, 2026
Merged

benbenbang merged 2 commits into
mainfrom
feat/368-search-nullable

Conversation

@benbenbang

Copy link
Copy Markdown
Member

FMP can send null or omit certain fields on ExchangeVariant and CompanyScreenerResult, which previously caused an entire page to fail decoding whenever any single value was null. Making these fields optional lets a null value decode gracefully instead of rejecting the whole response.

What changed:

  • Typed the affected CompanyScreenerResult fields (beta, price, lastAnnualDividend, country, isFund) and ExchangeVariant fields (price, range, changes, cik, isin, cusip, website, ceo, fullTimeEmployees, phone, address, city, state, zip, dcfDiff) as optional across all bindings, decoding null to the absent value and re-encoding it as null.
  • Applied this consistently in the Rust response models (Option), the Go SDK (pointer types), and the Python pyo3 models and generated .pyi stubs (Optional); an empty cusip now also decodes to absent.
  • Added and extended decode-path and parity tests across Rust, Go, and Python to prove the null rows decode, including a synthetic fixture with a null beta, a null companyName, and a string volume.
  • Updated the fixture README to document the optional beta change and the decode-path proof.

BREAKING CHANGE: ExchangeVariant and CompanyScreenerResult members that FMP can omit are now optional (Option / pointer / Optional); callers must handle the absent case.

…ional

Members that FMP can send as null or omit on ExchangeVariant and
CompanyScreenerResult previously failed decoding a whole page when any
single value was null. These members are now optional so a null value
decodes to None instead of rejecting the response.

**Rust response models:**
* CompanyScreenerResult now types beta, price, lastAnnualDividend,
  country, and isFund as Option, decoding null to None and re-encoding
  None as null.
* ExchangeVariant now types 15 members (price, range, changes, cik,
  isin, cusip, website, ceo, fullTimeEmployees, phone, address, city,
  state, zip, dcfDiff) as Option; an empty cusip also decodes to None.

**Go SDK:**
* CompanyScreenerResult and ExchangeVariant use pointer types for the
  same members, decoding null to nil and re-encoding nil as null.

**Python bindings:**
* The pyo3 models and generated .pyi stubs expose the same members as
  Optional and handle None.

Because these members were previously required, code that read them as
plain values must now handle the absent case.

BREAKING CHANGE: ExchangeVariant and CompanyScreenerResult members FMP
can omit are now optional (Option / pointer / Optional); handle None.
Document that issue #368 made the `beta` field optional, so the
`company_screener_null_beta_synthetic.json` fixture now proves the null
row decodes across Rust, Go, and Python. Note the decode-path proof also
swaps a null `companyName` into row 37 and a string `volume` to confirm
the offending value never reaches the error path.
@benbenbang
benbenbang enabled auto-merge (rebase) September 30, 2026 12:57
@github-actions github-actions Bot added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 30, 2026
@benbenbang
benbenbang merged commit 540956e into main Sep 30, 2026
9 checks passed
@benbenbang
benbenbang deleted the feat/368-search-nullable branch September 30, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant