tushare_api/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use crate::api::{Api, serialize_api_name};
4
5/// Tushare API request structure
6/// 
7/// Supports flexible string type usage, allowing direct use of string literals and String variables
8#[derive(Debug, Serialize, Deserialize, Clone)]
9pub struct TushareRequest {
10    #[serde(serialize_with = "serialize_api_name")]
11    pub api_name: Api,
12    pub params: HashMap<String, String>,
13    pub fields: Vec<String>,
14}
15
16impl TushareRequest {
17    /// Create a new TushareRequest
18    pub fn new<K, V, F, P, Fs>(api_name: Api, params: P, fields: Fs) -> Self
19    where
20        K: Into<String>,
21        V: Into<String>,
22        F: Into<String>,
23        P: IntoIterator<Item = (K, V)>,
24        Fs: IntoIterator<Item = F>,
25    {
26        let params = params
27            .into_iter()
28            .map(|(k, v)| (k.into(), v.into()))
29            .collect();
30        let fields = fields.into_iter().map(|f| f.into()).collect();
31        
32        Self {
33            api_name,
34            params,
35            fields,
36        }
37    }
38    
39    /// Create parameters from string literals
40    pub fn with_str_params<const N: usize>(api_name: Api, params: [(&str, &str); N], fields: &[&str]) -> Self {
41        let params = params
42            .into_iter()
43            .map(|(k, v)| (k.to_string(), v.to_string()))
44            .collect();
45        let fields = fields.iter().map(|f| f.to_string()).collect();
46        
47        Self {
48            api_name,
49            params,
50            fields,
51        }
52    }
53    
54    /// Add parameter
55    pub fn add_param<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
56        self.params.insert(key.into(), value.into());
57        self
58    }
59    
60    /// Add field
61    pub fn add_field<F: Into<String>>(mut self, field: F) -> Self {
62        self.fields.push(field.into());
63        self
64    }
65}
66
67/// Type alias retained for backward compatibility
68pub type TushareRequestString = TushareRequest;
69
70/// Macro for creating parameter HashMap
71#[macro_export]
72macro_rules! params {
73    ($($key:expr => $value:expr),* $(,)?) => {
74        {
75            let mut map = std::collections::HashMap::new();
76            $(
77                map.insert($key.to_string(), $value.to_string());
78            )*
79            map
80        }
81    };
82}
83
84/// Macro for creating fields Vec
85#[macro_export]
86macro_rules! fields {
87    ($($field:expr),* $(,)?) => {
88        vec![$($field.to_string()),*]
89    };
90}
91
92/// More concise builder macro - directly create TushareRequest
93#[macro_export]
94macro_rules! request {
95    ($api:expr, { $($key:expr => $value:expr),* $(,)? }, [ $($field:expr),* $(,)? ]) => {
96        TushareRequest {
97            api_name: $api,
98            params: params!($($key => $value),*),
99            fields: fields![$($field),*],
100        }
101    };
102}
103
104/// Tushare API response structure
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct TushareResponse {
107    pub request_id: String,
108    pub code: i32,
109    pub msg: Option<String>,
110    pub data: Option<TushareData>,
111}
112
113/// Tushare API data structure
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct TushareData {
116    pub fields: Vec<String>,
117    pub items: Vec<Vec<serde_json::Value>>,
118    pub has_more: bool,
119    pub count: i64,
120}
121
122/// Generic paginated entity list container
123/// 
124/// This is the new recommended way to handle paginated API responses.
125/// It provides a clear, type-safe interface with built-in pagination metadata.
126/// 
127/// # Examples
128/// 
129/// ```rust
130/// use tushare_api::{TushareClient, Api, request, TushareEntityList, params, fields, TushareRequest};
131/// use tushare_api::DeriveFromTushareData;
132/// 
133/// #[derive(Debug, Clone, DeriveFromTushareData)]
134/// pub struct Stock {
135///     pub ts_code: String,
136///     pub name: String,
137/// }
138/// 
139/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
140/// let client = TushareClient::from_env()?;
141/// 
142/// let stocks: TushareEntityList<Stock> = client.call_api_as(request!(
143///     Api::StockBasic, {
144///         "list_status" => "L"
145///     }, [
146///         "ts_code", "name"
147///     ]
148/// )).await?;
149/// 
150/// println!("Current page: {} stocks", stocks.len());
151/// println!("Total available: {} stocks", stocks.count());
152/// println!("Has more pages: {}", stocks.has_more());
153/// 
154/// for stock in &stocks {
155///     println!("{}: {}", stock.ts_code, stock.name);
156/// }
157/// # Ok(())
158/// # }
159/// ```
160#[derive(Debug, Clone)]
161pub struct TushareEntityList<T> {
162    /// The actual data items in this page
163    pub items: Vec<T>,
164    /// Whether there are more pages available
165    pub has_more: bool,
166    /// Total number of records available across all pages
167    pub count: i64,
168}
169
170impl<T> TushareEntityList<T> {
171    /// Create a new TushareEntityList
172    pub fn new(items: Vec<T>, has_more: bool, count: i64) -> Self {
173        Self {
174            items,
175            has_more,
176            count,
177        }
178    }
179    
180    /// Get the number of items in the current page
181    pub fn len(&self) -> usize {
182        self.items.len()
183    }
184    
185    /// Check if the current page is empty
186    pub fn is_empty(&self) -> bool {
187        self.items.is_empty()
188    }
189    
190    /// Get items as a slice
191    pub fn items(&self) -> &[T] {
192        &self.items
193    }
194    
195    /// Get mutable items as a slice
196    pub fn items_mut(&mut self) -> &mut [T] {
197        &mut self.items
198    }
199    
200    /// Check if there are more pages available
201    pub fn has_more(&self) -> bool {
202        self.has_more
203    }
204    
205    /// Get the total number of records across all pages
206    pub fn count(&self) -> i64 {
207        self.count
208    }
209    
210    /// Get an iterator over the items
211    pub fn iter(&self) -> std::slice::Iter<T> {
212        self.items.iter()
213    }
214    
215    /// Get a mutable iterator over the items
216    pub fn iter_mut(&mut self) -> std::slice::IterMut<T> {
217        self.items.iter_mut()
218    }
219    
220    /// Convert into the inner `Vec<T>`
221    pub fn into_items(self) -> Vec<T> {
222        self.items
223    }
224}
225
226// Implement Deref to allow direct access to Vec<T> methods
227impl<T> std::ops::Deref for TushareEntityList<T> {
228    type Target = Vec<T>;
229    
230    fn deref(&self) -> &Self::Target {
231        &self.items
232    }
233}
234
235// Implement DerefMut for mutable access
236impl<T> std::ops::DerefMut for TushareEntityList<T> {
237    fn deref_mut(&mut self) -> &mut Self::Target {
238        &mut self.items
239    }
240}
241
242// Implement IntoIterator for owned values
243impl<T> IntoIterator for TushareEntityList<T> {
244    type Item = T;
245    type IntoIter = std::vec::IntoIter<T>;
246    
247    fn into_iter(self) -> Self::IntoIter {
248        self.items.into_iter()
249    }
250}
251
252// Implement IntoIterator for borrowed values
253impl<'a, T> IntoIterator for &'a TushareEntityList<T> {
254    type Item = &'a T;
255    type IntoIter = std::slice::Iter<'a, T>;
256    
257    fn into_iter(self) -> Self::IntoIter {
258        self.items.iter()
259    }
260}
261
262// Implement IntoIterator for mutable borrowed values
263impl<'a, T> IntoIterator for &'a mut TushareEntityList<T> {
264    type Item = &'a mut T;
265    type IntoIter = std::slice::IterMut<'a, T>;
266    
267    fn into_iter(self) -> Self::IntoIter {
268        self.items.iter_mut()
269    }
270}
271
272// Implement From<Vec<T>> for convenience
273impl<T> From<Vec<T>> for TushareEntityList<T> {
274    fn from(items: Vec<T>) -> Self {
275        Self {
276            items,
277            has_more: false,
278            count: 0,
279        }
280    }
281}