tushare_api/
types.rs

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