stock-rust 0.1.0

Rust version of stock-api
Documentation
use anyhow::{anyhow, Result};
use async_trait::async_trait;

use crate::types::{Stock, StockApi};

pub const COMMON_SZ: &str = "SZ";
pub const COMMON_SH: &str = "SH";
pub const COMMON_HK: &str = "HK";
pub const COMMON_US: &str = "US";

#[derive(Clone, Copy)]
pub struct BaseApi;

#[async_trait]
impl StockApi for BaseApi {
    async fn get_stock(&self, _code: &str) -> Result<Stock> {
        Err(anyhow!("未实现获取股票数据"))
    }

    async fn get_stocks(&self, _codes: &[String]) -> Result<Vec<Stock>> {
        Err(anyhow!("未实现获取股票数据组"))
    }

    async fn search_stocks(&self, _key: &str) -> Result<Vec<Stock>> {
        Err(anyhow!("未实现搜索股票代码"))
    }
}

pub fn dedup_codes(codes: &[String]) -> Vec<String> {
    let mut out: Vec<String> = Vec::new();
    for code in codes {
        if !code.is_empty() && !out.contains(code) {
            out.push(code.clone());
        }
    }
    out
}

pub fn code_market(code: &str) -> &str {
    if code.len() >= 2 {
        &code[..2]
    } else {
        ""
    }
}

pub fn parse_num(s: Option<&str>) -> f64 {
    s.unwrap_or("0").parse::<f64>().unwrap_or(0.0)
}

pub fn as_f64(v: Option<&serde_json::Value>) -> f64 {
    match v {
        Some(serde_json::Value::Number(n)) => n.as_f64().unwrap_or(0.0),
        Some(serde_json::Value::String(s)) => s.parse::<f64>().unwrap_or(0.0),
        _ => 0.0,
    }
}

pub fn as_string(v: Option<&serde_json::Value>) -> String {
    match v {
        Some(serde_json::Value::String(s)) => s.clone(),
        Some(other) => other.to_string(),
        None => "---".to_string(),
    }
}

pub fn percent(now: f64, yesterday: f64) -> f64 {
    if now == 0.0 || yesterday == 0.0 {
        0.0
    } else {
        now / yesterday - 1.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dedup_codes() {
        let codes = vec![
            "SH510500".to_string(),
            "".to_string(),
            "SH510500".to_string(),
            "SZ000001".to_string(),
        ];
        assert_eq!(dedup_codes(&codes), vec!["SH510500", "SZ000001"]);
    }

    #[test]
    fn test_percent() {
        let p = percent(7.224, 7.149);
        assert!((p - (7.224 / 7.149 - 1.0)).abs() < 1e-12);
        assert_eq!(percent(0.0, 7.149), 0.0);
    }
}