tushare_api/
utils.rs

1//! Utility functions for working with Tushare API responses
2
3use crate::error::TushareError;
4use crate::types::TushareResponse;
5use crate::traits::FromTushareData;
6use serde_json::Value;
7
8/// Convert TushareResponse to `Vec<T>` where T implements FromTushareData
9pub fn response_to_vec<T: FromTushareData>(response: TushareResponse) -> Result<Vec<T>, TushareError> {
10    let mut results = Vec::new();
11    if response.data.is_none() {
12        return Ok(results);
13    }
14    let Some(data) = response.data else {
15        return Ok(results);
16    };
17    for item in  data.items {
18        let converted = T::from_row(&data.fields, &item)?;
19        results.push(converted);
20    }
21    
22    Ok(results)
23}
24
25/// Helper function to get field value by name
26pub fn get_field_value<'a>(fields: &[String], values: &'a [Value], field_name: &str) -> Result<&'a Value, TushareError> {
27    let index = fields.iter()
28        .position(|f| f == field_name)
29        .ok_or_else(|| TushareError::ParseError(format!("Missing field: {}", field_name)))?;
30        
31    values.get(index)
32        .ok_or_else(|| TushareError::ParseError(format!("Value not found for field: {}", field_name)))
33}
34
35/// Helper function to get string field value
36pub fn get_string_field(fields: &[String], values: &[Value], field_name: &str) -> Result<String, TushareError> {
37    let value = get_field_value(fields, values, field_name)?;
38    value.as_str()
39        .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a string", field_name)))
40        .map(|s| s.to_string())
41}
42
43/// Helper function to get optional string field value
44pub fn get_optional_string_field(fields: &[String], values: &[Value], field_name: &str) -> Result<Option<String>, TushareError> {
45    match get_field_value(fields, values, field_name) {
46        Ok(value) => {
47            if value.is_null() {
48                Ok(None)
49            } else {
50                value.as_str()
51                    .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a string", field_name)))
52                    .map(|s| Some(s.to_string()))
53            }
54        }
55        Err(_) => Ok(None), // Field not present
56    }
57}
58
59/// Helper function to get float field value
60pub fn get_float_field(fields: &[String], values: &[Value], field_name: &str) -> Result<f64, TushareError> {
61    let value = get_field_value(fields, values, field_name)?;
62    value.as_f64()
63        .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a number", field_name)))
64}
65
66/// Helper function to get optional float field value
67pub fn get_optional_float_field(fields: &[String], values: &[Value], field_name: &str) -> Result<Option<f64>, TushareError> {
68    match get_field_value(fields, values, field_name) {
69        Ok(value) => {
70            if value.is_null() {
71                Ok(None)
72            } else {
73                value.as_f64()
74                    .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a number", field_name)))
75                    .map(Some)
76            }
77        }
78        Err(_) => Ok(None), // Field not present
79    }
80}
81
82/// Helper function to get integer field value
83pub fn get_int_field(fields: &[String], values: &[Value], field_name: &str) -> Result<i64, TushareError> {
84    let value = get_field_value(fields, values, field_name)?;
85    value.as_i64()
86        .ok_or_else(|| TushareError::ParseError(format!("Field {} is not an integer", field_name)))
87}
88
89/// Helper function to get optional integer field value
90pub fn get_optional_int_field(fields: &[String], values: &[Value], field_name: &str) -> Result<Option<i64>, TushareError> {
91    match get_field_value(fields, values, field_name) {
92        Ok(value) => {
93            if value.is_null() {
94                Ok(None)
95            } else {
96                value.as_i64()
97                    .ok_or_else(|| TushareError::ParseError(format!("Field {} is not an integer", field_name)))
98                    .map(Some)
99            }
100        }
101        Err(_) => Ok(None), // Field not present
102    }
103}
104
105/// Helper function to get boolean field value
106pub fn get_bool_field(fields: &[String], values: &[Value], field_name: &str) -> Result<bool, TushareError> {
107    let value = get_field_value(fields, values, field_name)?;
108    value.as_bool()
109        .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a boolean", field_name)))
110}
111
112/// Helper function to get optional boolean field value
113pub fn get_optional_bool_field(fields: &[String], values: &[Value], field_name: &str) -> Result<Option<bool>, TushareError> {
114    match get_field_value(fields, values, field_name) {
115        Ok(value) => {
116            if value.is_null() {
117                Ok(None)
118            } else {
119                value.as_bool()
120                    .ok_or_else(|| TushareError::ParseError(format!("Field {} is not a boolean", field_name)))
121                    .map(Some)
122            }
123        }
124        Err(_) => Ok(None), // Field not present
125    }
126}
127//
128// #[cfg(test)]
129// mod tests {
130//     use super::*;
131//     use crate::{TushareData, TushareResponse};
132//     use serde_json::json;
133//
134//     #[derive(Debug, PartialEq)]
135//     struct TestStock {
136//         ts_code: String,
137//         symbol: String,
138//         name: String,
139//         price: Option<f64>,
140//     }
141//
142//     impl FromTushareData for TestStock {
143//         fn from_row(fields: &[String], values: &[Value]) -> Result<Self, TushareError> {
144//             Ok(TestStock {
145//                 ts_code: get_string_field(fields, values, "ts_code")?,
146//                 symbol: get_string_field(fields, values, "symbol")?,
147//                 name: get_string_field(fields, values, "name")?,
148//                 price: get_optional_float_field(fields, values, "price")?,
149//             })
150//         }
151//     }
152//
153//   //  #[test]
154//     fn test_response_to_vec() {
155//         let response = TushareResponse {
156//             request_id: "test".to_string(),
157//             code: 0,
158//             msg: None,
159//             data: TushareData {
160//                 fields: vec![
161//                     "ts_code".to_string(),
162//                     "symbol".to_string(),
163//                     "name".to_string(),
164//                     "price".to_string(),
165//                 ],
166//                 items: vec![
167//                     vec![
168//                         json!("000001.SZ"),
169//                         json!("000001"),
170//                         json!("平安银行"),
171//                         json!(10.5),
172//                     ],
173//                     vec![
174//                         json!("000002.SZ"),
175//                         json!("000002"),
176//                         json!("万科A"),
177//                         json!(null),
178//                     ],
179//                 ],
180//                 has_more: false,
181//                 count: 2,
182//             },
183//         };
184//
185//         let stocks: Vec<TestStock> = response_to_vec(response).unwrap();
186//
187//         assert_eq!(stocks.len(), 2);
188//         assert_eq!(stocks[0].ts_code, "000001.SZ");
189//         assert_eq!(stocks[0].symbol, "000001");
190//         assert_eq!(stocks[0].name, "平安银行");
191//         assert_eq!(stocks[0].price, Some(10.5));
192//
193//         assert_eq!(stocks[1].ts_code, "000002.SZ");
194//         assert_eq!(stocks[1].symbol, "000002");
195//         assert_eq!(stocks[1].name, "万科A");
196//         assert_eq!(stocks[1].price, None);
197//     }
198// }