1use crate::error::TushareError;
4use crate::types::TushareResponse;
5use crate::traits::FromTushareData;
6use serde_json::Value;
7
8pub 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
25pub 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
35pub 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
43pub 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), }
57}
58
59pub 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
66pub 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), }
80}
81
82pub 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
89pub 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), }
103}
104
105pub 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
112pub 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), }
126}
127
128#[cfg(test)]
129mod 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}