yfinance-rs 0.9.1

Ergonomic Rust client for Yahoo Finance, supporting historical prices, real-time streaming, options, fundamentals, and more.
Documentation
use std::{fmt, marker::PhantomData};

use paft::Decimal;
use serde::{
    Deserialize, Deserializer,
    de::{IgnoredAny, MapAccess, SeqAccess, Visitor},
};
use serde_field_result::{Field, FieldDecode, invalid_seq};

use super::{
    number::{JsonDecimal, JsonU64, de_decimal_from_json, de_u64_from_json},
    value::WireValue,
};

#[derive(Deserialize, Clone, Copy, Debug)]
pub struct RawNum<T> {
    pub(crate) raw: Option<T>,
}

#[derive(Deserialize, Clone, Copy, Debug)]
pub struct RawDecimal {
    #[serde(default, deserialize_with = "de_decimal_from_json")]
    pub(crate) raw: Option<Decimal>,
}

#[derive(Deserialize, Clone, Copy, Debug)]
pub struct RawDate {
    pub(crate) raw: Option<i64>,
}

pub fn from_raw_date(r: Option<RawDate>) -> Option<i64> {
    r.and_then(|d| d.raw)
}

#[derive(Deserialize, Clone, Copy, Debug)]
pub struct RawNumU64 {
    #[serde(default, deserialize_with = "de_u64_from_json")]
    pub(crate) raw: Option<u64>,
}

impl<'de, T> FieldDecode<'de> for RawNum<T>
where
    T: FieldDecode<'de>,
{
    fn decode_field<D>(deserializer: D) -> Result<Field<Self>, D::Error>
    where
        D: Deserializer<'de>,
    {
        decode_raw_map::<_, T>(deserializer).map(|decoded| decoded.map(|raw| Self { raw }))
    }
}

impl<'de> FieldDecode<'de> for RawDecimal {
    fn decode_field<D>(deserializer: D) -> Result<Field<Self>, D::Error>
    where
        D: Deserializer<'de>,
    {
        decode_raw_map::<_, JsonDecimal>(deserializer).map(|decoded| {
            decoded.map(|raw| Self {
                raw: raw.map(JsonDecimal::into_decimal),
            })
        })
    }
}

impl<'de> FieldDecode<'de> for RawDate {
    fn decode_field<D>(deserializer: D) -> Result<Field<Self>, D::Error>
    where
        D: Deserializer<'de>,
    {
        decode_raw_map::<_, i64>(deserializer).map(|decoded| decoded.map(|raw| Self { raw }))
    }
}

impl<'de> FieldDecode<'de> for RawNumU64 {
    fn decode_field<D>(deserializer: D) -> Result<Field<Self>, D::Error>
    where
        D: Deserializer<'de>,
    {
        decode_raw_map::<_, JsonU64>(deserializer).map(|decoded| {
            decoded.map(|raw| Self {
                raw: raw.map(JsonU64::into_u64),
            })
        })
    }
}

fn decode_raw_map<'de, D, T>(deserializer: D) -> Result<Field<Option<T>>, D::Error>
where
    D: Deserializer<'de>,
    T: FieldDecode<'de>,
{
    struct RawMapVisitor<T>(PhantomData<T>);

    impl<'de, T> Visitor<'de> for RawMapVisitor<T>
    where
        T: FieldDecode<'de>,
    {
        type Value = Field<Option<T>>;

        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            formatter.write_str(RAW_OBJECT)
        }

        fn visit_unit<E>(self) -> Result<Self::Value, E> {
            Ok(Field::Missing)
        }

        fn visit_none<E>(self) -> Result<Self::Value, E> {
            Ok(Field::Missing)
        }

        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
        where
            A: MapAccess<'de>,
        {
            let mut raw = None;
            let mut invalid = None;

            while let Some(field) = map.next_key::<RawField>()? {
                match field {
                    RawField::Raw => match map.next_value::<WireValue<T>>()? {
                        WireValue::Missing => raw = None,
                        WireValue::Valid(value) => raw = Some(value),
                        WireValue::Invalid(error) => {
                            if invalid.is_none() {
                                invalid = Some(error);
                            }
                        }
                    },
                    RawField::Other => {
                        let _: IgnoredAny = map.next_value()?;
                    }
                }
            }

            Ok(invalid.map_or_else(|| Field::Valid(raw), Field::Invalid))
        }

        fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("boolean `{value}`")))
        }

        fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("integer `{value}`")))
        }

        fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("integer `{value}`")))
        }

        fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("floating point `{value}`")))
        }

        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("string `{value}`")))
        }

        fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
            Ok(invalid_raw(format_args!("string `{value}`")))
        }

        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
        where
            A: SeqAccess<'de>,
        {
            invalid_seq(&mut seq, unexpected(RAW_OBJECT, "array"))
        }
    }

    deserializer.deserialize_any(RawMapVisitor::<T>(PhantomData))
}

const RAW_OBJECT: &str = "Yahoo raw value object";

fn invalid_raw<T>(actual: impl fmt::Display) -> Field<T> {
    Field::invalid(unexpected(RAW_OBJECT, actual))
}

fn unexpected(expected: &'static str, actual: impl fmt::Display) -> String {
    format!("expected {expected}, got {actual}")
}

#[derive(Deserialize)]
#[serde(field_identifier)]
enum RawField {
    #[serde(rename = "raw")]
    Raw,
    #[serde(other)]
    Other,
}