tushare_api/
traits.rs

1//! Traits for Tushare API data conversion
2//!
3//! This module contains traits that define how to convert Tushare API response data
4//! into Rust structs. The main trait is `FromTushareData` which can be implemented
5//! manually or automatically using the derive macro from `tushare-derive`.
6
7use crate::error::TushareError;
8use crate::types::{TushareResponse, TushareEntityList};
9use serde_json::Value;
10
11/// Trait for converting individual JSON values to custom types
12/// 
13/// This trait allows users to define how to convert a single JSON value
14/// from Tushare API responses into their custom types. It's designed to
15/// work with the procedural macro system for automatic field conversion.
16/// 
17/// # Example
18/// 
19/// ```rust
20/// use tushare_api::{FromTushareValue, TushareError};
21/// use serde_json::Value;
22/// 
23/// // Custom type example
24/// #[derive(Debug, Clone, PartialEq)]
25/// struct CustomDecimal(f64);
26/// 
27/// impl std::str::FromStr for CustomDecimal {
28///     type Err = std::num::ParseFloatError;
29///     fn from_str(s: &str) -> Result<Self, Self::Err> {
30///         s.parse::<f64>().map(CustomDecimal)
31///     }
32/// }
33/// 
34/// impl FromTushareValue for CustomDecimal {
35///     fn from_tushare_value(value: &Value) -> Result<Self, TushareError> {
36///         match value {
37///             Value::String(s) => s.parse().map_err(|e| {
38///                 TushareError::ParseError(format!("Failed to parse decimal: {}", e))
39///             }),
40///             Value::Number(n) => {
41///                 if let Some(f) = n.as_f64() {
42///                     Ok(CustomDecimal(f))
43///                 } else {
44///                     Err(TushareError::ParseError("Invalid number format".to_string()))
45///                 }
46///             }
47///             _ => Err(TushareError::ParseError("Value is not a valid decimal".to_string()))
48///         }
49///     }
50/// }
51/// ```
52pub trait FromTushareValue: Sized {
53    /// Convert a JSON value to this type
54    /// 
55    /// # Arguments
56    /// 
57    /// * `value` - The JSON value to convert
58    fn from_tushare_value(value: &Value) -> Result<Self, TushareError>;
59}
60
61// Note: Basic Rust type implementations have been moved to src/basic_types.rs
62// This includes implementations for: String, f64, f32, i64, i32, i16, i8, u64, u32, u16, u8, usize, isize, bool
63
64/// Trait for converting optional JSON values to custom types
65/// 
66/// This trait handles the conversion of potentially null or missing JSON values
67/// to optional custom types. It's designed to work with the procedural macro
68/// system for automatic optional field conversion.
69/// 
70/// # Example
71/// 
72/// ```rust
73/// use tushare_api::{FromTushareValue, FromOptionalTushareValue, TushareError};
74/// use serde_json::Value;
75/// 
76/// // Custom type example (same as above)
77/// #[derive(Debug, Clone, PartialEq)]
78/// struct CustomDecimal(f64);
79/// 
80/// impl std::str::FromStr for CustomDecimal {
81///     type Err = std::num::ParseFloatError;
82///     fn from_str(s: &str) -> Result<Self, Self::Err> {
83///         s.parse::<f64>().map(CustomDecimal)
84///     }
85/// }
86/// 
87/// impl FromTushareValue for CustomDecimal {
88///     fn from_tushare_value(value: &Value) -> Result<Self, TushareError> {
89///         match value {
90///             Value::String(s) => s.parse().map_err(|e| {
91///                 TushareError::ParseError(format!("Failed to parse decimal: {}", e))
92///             }),
93///             Value::Number(n) => {
94///                 if let Some(f) = n.as_f64() {
95///                     Ok(CustomDecimal(f))
96///                 } else {
97///                     Err(TushareError::ParseError("Invalid number format".to_string()))
98///                 }
99///             }
100///             _ => Err(TushareError::ParseError("Value is not a valid decimal".to_string()))
101///         }
102///     }
103/// }
104/// 
105/// impl FromOptionalTushareValue for CustomDecimal {
106///     fn from_optional_tushare_value(value: &Value) -> Result<Option<Self>, TushareError> {
107///         if value.is_null() {
108///             Ok(None)
109///         } else {
110///             match value {
111///                 Value::String(s) if s.is_empty() => Ok(None),
112///                 _ => CustomDecimal::from_tushare_value(value).map(Some)
113///             }
114///         }
115///     }
116/// }
117/// ```
118pub trait FromOptionalTushareValue: Sized {
119    /// Convert an optional JSON value to this type
120    /// 
121    /// # Arguments
122    /// 
123    /// * `value` - The JSON value to convert (may be null)
124    fn from_optional_tushare_value(value: &Value) -> Result<Option<Self>, TushareError>;
125}
126
127// Note: Basic Rust type implementations for FromOptionalTushareValue have been moved to src/basic_types.rs
128
129/// Trait for converting Tushare API response data into Rust structs
130/// 
131/// This trait defines how to convert a single row of data from a Tushare API response
132/// into a Rust struct. It can be implemented manually or automatically using the
133/// `#[derive(FromTushareData)]` macro from the `tushare-derive` crate.
134/// 
135/// # Example
136/// 
137/// ```rust
138/// use tushare_api::traits::FromTushareData;
139/// use tushare_api::error::TushareError;
140/// use serde_json::Value;
141/// 
142/// struct Stock {
143///     ts_code: String,
144///     name: String,
145/// }
146/// 
147/// impl FromTushareData for Stock {
148///     fn from_row(fields: &[String], values: &[Value]) -> Result<Self, TushareError> {
149///         // Manual implementation
150///         let ts_code_idx = fields.iter().position(|f| f == "ts_code")
151///             .ok_or_else(|| TushareError::ParseError("Missing ts_code field".to_string()))?;
152///         let name_idx = fields.iter().position(|f| f == "name")
153///             .ok_or_else(|| TushareError::ParseError("Missing name field".to_string()))?;
154///             
155///         Ok(Stock {
156///             ts_code: values[ts_code_idx].as_str()
157///                 .ok_or_else(|| TushareError::ParseError("Invalid ts_code".to_string()))?
158///                 .to_string(),
159///             name: values[name_idx].as_str()
160///                 .ok_or_else(|| TushareError::ParseError("Invalid name".to_string()))?
161///                 .to_string(),
162///         })
163///     }
164/// }
165/// ```
166/// 
167/// # Using the derive macro
168/// 
169/// For most use cases, you can use the derive macro instead of manual implementation:
170/// 
171/// ```rust
172/// use tushare_api::DeriveFromTushareData;
173/// 
174/// #[derive(Debug, Clone, DeriveFromTushareData)]
175/// pub struct Stock {
176///     ts_code: String,
177///     name: String,
178///     area: Option<String>,
179/// }
180/// ```
181pub trait FromTushareData: Sized {
182    /// Convert a single row of data to this type
183    /// 
184    /// # Arguments
185    /// 
186    /// * `fields` - Field names from the response
187    /// * `values` - Values for this row
188    fn from_row(fields: &[String], values: &[Value]) -> Result<Self, TushareError>;
189}
190
191/// Implementation of `TryFrom<TushareResponse>` for `TushareEntityList<T>`
192/// 
193/// This allows automatic conversion from API responses to typed entity lists.
194/// It extracts pagination metadata and converts each data row to the target type T.
195impl<T> TryFrom<TushareResponse> for TushareEntityList<T>
196where
197    T: FromTushareData,
198{
199    type Error = TushareError;
200    
201    fn try_from(response: TushareResponse) -> Result<Self, Self::Error> {
202        let Some(data) = response.data else {
203          return Err(TushareError::ParseError("Missing data in response".to_string()));
204        };
205        let mut items = Vec::new();
206        // Convert each row to the target type
207        for row in &data.items {
208            let item = T::from_row(&data.fields, row)?;
209            items.push(item);
210        }
211        
212        Ok(TushareEntityList::new(
213            items,
214            data.has_more,
215            data.count,
216        ))
217    }
218}
219
220/// Helper function for parsing values with custom date format (non-optional types)
221/// 
222/// This function is used by the procedural macro when a `date_format` attribute is specified.
223/// It attempts to parse the value using the custom format for supported chrono types.
224/// 
225/// # Arguments
226/// 
227/// * `value` - The JSON value to parse
228/// * `format` - The custom date format string (e.g., "%d/%m/%Y")
229/// 
230/// # Returns
231/// 
232/// Returns the parsed value of type T or an error if parsing fails.
233pub fn from_tushare_value_with_date_format<T>(
234    value: &serde_json::Value,
235    format: &str,
236) -> Result<T, crate::error::TushareError>
237where
238    T: FromTushareValueWithFormat,
239{
240    T::from_tushare_value_with_format(value, format)
241}
242
243/// Helper function for parsing optional values with custom date format
244/// 
245/// This function is used by the procedural macro when a `date_format` attribute is specified
246/// for optional fields. It handles null/empty values gracefully.
247/// 
248/// # Arguments
249/// 
250/// * `value` - The JSON value to parse (may be null)
251/// * `format` - The custom date format string (e.g., "%d/%m/%Y")
252/// 
253/// # Returns
254/// 
255/// Returns Some(parsed_value) for valid values, None for null/empty, or an error for invalid formats.
256pub fn from_optional_tushare_value_with_date_format<T>(
257    value: &serde_json::Value,
258    format: &str,
259) -> Result<Option<T>, crate::error::TushareError>
260where
261    T: FromTushareValueWithFormat,
262{
263    match value {
264        serde_json::Value::Null => Ok(None),
265        serde_json::Value::String(s) if s.is_empty() => Ok(None),
266        _ => Ok(Some(T::from_tushare_value_with_format(value, format)?)),
267    }
268}
269
270/// Trait for types that support custom date format parsing
271/// 
272/// This trait is implemented for chrono date/time types to enable
273/// custom format parsing through the `#[tushare(date_format = "...")]` attribute.
274pub trait FromTushareValueWithFormat: Sized {
275    /// Parse a value using a custom date format
276    /// 
277    /// # Arguments
278    /// 
279    /// * `value` - The JSON value to parse
280    /// * `format` - The custom date format string
281    /// 
282    /// # Returns
283    /// 
284    /// Returns the parsed value or an error if parsing fails.
285    fn from_tushare_value_with_format(
286        value: &serde_json::Value,
287        format: &str,
288    ) -> Result<Self, crate::error::TushareError>;
289}