1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Data, Fields, Type};
4
5#[proc_macro_derive(FromTushareData, attributes(tushare))]
36pub fn derive_from_tushare_data(input: TokenStream) -> TokenStream {
37 let input = parse_macro_input!(input as DeriveInput);
38
39 let name = &input.ident;
40 let fields = match &input.data {
41 Data::Struct(data) => match &data.fields {
42 Fields::Named(fields) => &fields.named,
43 _ => panic!("FromTushareData can only be derived for structs with named fields"),
44 },
45 _ => panic!("FromTushareData can only be derived for structs"),
46 };
47
48 let field_assignments = fields.iter().map(|field| {
49 let field_name = &field.ident;
50 let field_type = &field.ty;
51
52 let mut api_field_name = field_name.as_ref().unwrap().to_string();
54 let mut skip_field = false;
55 let mut date_format: Option<String> = None;
56
57 for attr in &field.attrs {
58 if attr.path().is_ident("tushare") {
59 if let Ok(meta_list) = attr.meta.require_list() {
60 let tokens_str = meta_list.tokens.to_string();
61
62 if let Some(field_start) = tokens_str.find("field") {
64 let after_field = &tokens_str[field_start + 5..]; if let Some(eq_pos) = after_field.find('=') {
66 let after_eq = &after_field[eq_pos + 1..].trim();
67 if let Some(start_quote) = after_eq.find('"') {
68 let after_start_quote = &after_eq[start_quote + 1..];
69 if let Some(end_quote) = after_start_quote.find('"') {
70 api_field_name = after_start_quote[..end_quote].to_string();
71 }
72 }
73 }
74 }
75
76 if tokens_str.contains("skip") {
78 skip_field = true;
79 }
80
81 if let Some(format_start) = tokens_str.find("date_format") {
83 let after_format = &tokens_str[format_start + 11..]; if let Some(eq_pos) = after_format.find('=') {
85 let after_eq = &after_format[eq_pos + 1..].trim();
86 if let Some(start_quote) = after_eq.find('"') {
87 let after_start_quote = &after_eq[start_quote + 1..];
88 if let Some(end_quote) = after_start_quote.find('"') {
89 date_format = Some(after_start_quote[..end_quote].to_string());
90 }
91 }
92 }
93 }
94 }
95 }
96 }
97
98 if skip_field {
99 quote! {
100 #field_name: Default::default(),
101 }
102 } else {
103 if is_option_type(field_type) {
105 let inner_type = extract_option_inner_type(field_type);
106
107 if let Some(format) = date_format {
108 quote! {
110 #field_name: {
111 let value = match tushare_api::utils::get_field_value(fields, values, #api_field_name) {
112 Ok(v) => v,
113 Err(_) => &serde_json::Value::Null,
114 };
115 tushare_api::traits::from_optional_tushare_value_with_date_format::<#inner_type>(value, #format)?
116 },
117 }
118 } else {
119 quote! {
121 #field_name: {
122 let value = match tushare_api::utils::get_field_value(fields, values, #api_field_name) {
123 Ok(v) => v,
124 Err(_) => &serde_json::Value::Null,
125 };
126 <#inner_type as tushare_api::traits::FromOptionalTushareValue>::from_optional_tushare_value(value)?
127 },
128 }
129 }
130 } else {
131 if let Some(format) = date_format {
132 quote! {
134 #field_name: {
135 let value = tushare_api::utils::get_field_value(fields, values, #api_field_name)?;
136 tushare_api::traits::from_tushare_value_with_date_format::<#field_type>(value, #format)?
137 },
138 }
139 } else {
140 quote! {
142 #field_name: {
143 let value = tushare_api::utils::get_field_value(fields, values, #api_field_name)?;
144 <#field_type as tushare_api::traits::FromTushareValue>::from_tushare_value(value)?
145 },
146 }
147 }
148 }
149 }
150 });
151
152 let expanded = quote! {
153 impl tushare_api::traits::FromTushareData for #name {
154 fn from_row(
155 fields: &[String],
156 values: &[serde_json::Value],
157 ) -> Result<Self, tushare_api::error::TushareError> {
158 Ok(Self {
159 #(#field_assignments)*
160 })
161 }
162 }
163 };
164
165 TokenStream::from(expanded)
166}
167
168
169
170fn is_option_type(ty: &Type) -> bool {
172 if let Type::Path(type_path) = ty {
173 if let Some(segment) = type_path.path.segments.last() {
174 return segment.ident == "Option";
175 }
176 }
177 false
178}
179
180fn extract_option_inner_type(ty: &Type) -> Type {
181 if let Type::Path(type_path) = ty {
182 if let Some(segment) = type_path.path.segments.last() {
183 if segment.ident == "Option" {
184 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
185 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
186 return inner_ty.clone();
187 }
188 }
189 }
190 }
191 }
192 syn::parse_str("String").unwrap()
194}
195
196