Skip to main content

tvdata_rs/scanner/
metainfo.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct ScannerMetainfo {
6    #[serde(default)]
7    pub financial_currency: Option<String>,
8    #[serde(default)]
9    pub fields: Vec<ScannerFieldMetainfo>,
10}
11
12impl ScannerMetainfo {
13    pub fn field(&self, name: &str) -> Option<&ScannerFieldMetainfo> {
14        self.fields.iter().find(|field| field.name == name)
15    }
16
17    pub fn supports_field(&self, name: &str) -> bool {
18        self.field(name).is_some()
19    }
20
21    pub fn fields_by_type<'a>(
22        &'a self,
23        field_type: &'a str,
24    ) -> impl Iterator<Item = &'a ScannerFieldMetainfo> + 'a {
25        self.fields
26            .iter()
27            .filter(move |field| field.field_type.as_str() == field_type)
28    }
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct ScannerFieldMetainfo {
33    #[serde(rename = "n")]
34    pub name: String,
35    #[serde(rename = "t")]
36    pub field_type: ScannerFieldType,
37    #[serde(rename = "r", default)]
38    pub range: Option<Value>,
39}
40
41impl ScannerFieldMetainfo {
42    pub fn enum_values(&self) -> Option<&[Value]> {
43        self.range.as_ref()?.as_array().map(Vec::as_slice)
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(transparent)]
49pub struct ScannerFieldType(String);
50
51impl ScannerFieldType {
52    pub fn as_str(&self) -> &str {
53        &self.0
54    }
55
56    pub fn is_numeric_like(&self) -> bool {
57        matches!(
58            self.as_str(),
59            "number" | "percent" | "price" | "fundamental_price" | "num_slice"
60        )
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use serde_json::json;
67
68    use super::*;
69
70    #[test]
71    fn metainfo_looks_up_fields_and_types() {
72        let metainfo = ScannerMetainfo {
73            financial_currency: Some("USD".to_owned()),
74            fields: vec![
75                ScannerFieldMetainfo {
76                    name: "close".to_owned(),
77                    field_type: ScannerFieldType("price".to_owned()),
78                    range: None,
79                },
80                ScannerFieldMetainfo {
81                    name: "country".to_owned(),
82                    field_type: ScannerFieldType("text".to_owned()),
83                    range: Some(json!(["United States", "Canada"])),
84                },
85            ],
86        };
87
88        assert!(metainfo.supports_field("close"));
89        assert_eq!(
90            metainfo
91                .field("country")
92                .and_then(ScannerFieldMetainfo::enum_values),
93            Some(&[json!("United States"), json!("Canada")][..]),
94        );
95        assert_eq!(metainfo.fields_by_type("price").count(), 1);
96        assert!(
97            metainfo
98                .field("close")
99                .is_some_and(|field| field.field_type.is_numeric_like())
100        );
101    }
102}