libfmp 0.4.0

Async, typed Rust client for the Financial Modeling Prep API
Documentation
use serde::{Serialize, de::DeserializeOwned};
use serde_json::{Value, json};

use libfmp::responses::dcf::DcfValuation;

const STANDARD: &[u8] = include_bytes!("fixtures/discounted_cash_flow.json");
const LEVERED: &[u8] = include_bytes!("fixtures/levered_discounted_cash_flow.json");

#[test]
fn both_outer_fixtures_round_trip_exactly() {
    assert_exact::<DcfValuation>(STANDARD);
    assert_exact::<DcfValuation>(LEVERED);

    let standard: Vec<DcfValuation> = serde_json::from_slice(STANDARD).unwrap();
    let levered: Vec<DcfValuation> = serde_json::from_slice(LEVERED).unwrap();

    assert_eq!(standard[0].symbol.as_str(), "AAPL");
    assert_eq!(standard[0].date.to_string(), "2026-07-30");
    assert_eq!(standard[0].dcf, 147.10881272667325);
    assert_eq!(standard[0].stock_price, 338.19);
    assert_eq!(levered[0].dcf, 140.6429495133426);
    assert_eq!(levered[0].stock_price, 338.19);
}

fn assert_exact<T>(fixture: &[u8])
where
    T: DeserializeOwned + Serialize,
{
    let source: Value = serde_json::from_slice(fixture).unwrap();
    let rows: Vec<T> = serde_json::from_slice(fixture).unwrap();
    assert_eq!(serde_json::to_value(rows).unwrap(), source);
}

#[test]
fn every_documented_field_is_required_non_null_and_unknown_fields_are_accepted() {
    let source: Value = serde_json::from_slice(STANDARD).unwrap();
    let row = source[0].clone();

    for field in ["symbol", "date", "dcf", "Stock Price"] {
        let mut missing = row.clone();
        missing.as_object_mut().unwrap().remove(field);
        assert!(serde_json::from_value::<DcfValuation>(missing).is_err());

        let mut null = row.clone();
        null[field] = Value::Null;
        assert!(serde_json::from_value::<DcfValuation>(null).is_err());
    }

    let mut future = row;
    future["futureField"] = json!({"nested": true});
    let decoded: DcfValuation = serde_json::from_value(future).unwrap();
    assert_eq!(decoded.symbol.as_str(), "AAPL");
}

#[test]
fn dcf_contract_preserves_bare_array_roots_and_exact_stock_price_key() {
    assert!(
        serde_json::from_str::<Vec<DcfValuation>>("[]")
            .unwrap()
            .is_empty()
    );
    assert!(serde_json::from_str::<Vec<DcfValuation>>("{}").is_err());

    let row: DcfValuation = serde_json::from_slice::<Value>(STANDARD)
        .and_then(|value| serde_json::from_value(value[0].clone()))
        .unwrap();
    let encoded = serde_json::to_value(row).unwrap();
    assert_eq!(encoded["Stock Price"], json!(338.19));
    assert!(encoded.get("stockPrice").is_none());
    assert!(encoded.get("stock_price").is_none());
}