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// }