1use crate::Endpoint;
2use darling::{
3 ast::{Data, Fields},
4 FromDeriveInput, FromField, FromMeta, FromVariant,
5};
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::{GenericArgument, Ident, PathArguments, Type};
9
10#[derive(Debug, FromField, Clone)]
11#[darling(attributes(api))]
12pub struct ApiField {
13 pub ident: Option<Ident>,
14 pub ty: Type,
15 #[darling(default)]
17 pub table_skip: bool,
18 pub table_format: Option<FieldFormat>,
20}
21
22#[derive(Debug, FromMeta, Clone)]
23pub enum FieldFormat {
24 Date {
26 format: String,
37 },
38}
39
40#[derive(Debug, FromVariant)]
41#[darling(attributes(api))]
42#[allow(dead_code)]
43pub struct ApiVariant {
44 ident: Ident,
45 fields: darling::ast::Fields<Type>,
47 }
49
50#[derive(Debug, FromDeriveInput)]
51#[darling(attributes(api, suggest), forward_attrs(derive))]
52pub struct Api {
53 pub ident: Ident,
54 pub data: Data<ApiVariant, ApiField>,
55 pub attrs: Vec<syn::Attribute>,
56
57 #[darling(default)]
58 #[darling(multiple)]
59 pub endpoint: Vec<Endpoint>,
60
61 #[darling(rename = "stream")]
63 #[darling(default)]
64 pub result_is_stream: bool,
65}
66
67#[rustfmt::skip::macros(quote)]
68pub fn table_impl<T: Into<ApiField> + Clone>(
69 struct_ident: &Ident,
70 data: &Data<ApiVariant, T>,
71 is_pretty: bool,
72) -> TokenStream {
73 let (headers, table_convertions) = match data {
74 Data::Enum(_) => {
75 todo!("Générer les entetes et les données pour chaque variant");
76 }
78 Data::Struct(Fields { fields, .. }) => {
79 let headers: Vec<proc_macro2::TokenStream> = fields
80 .iter()
81 .filter(|field| {
82 let field: ApiField = (*field).clone().into();
83 !field.table_skip
84 })
85 .map(|field| {
86 let field: ApiField = (*field).clone().into();
87 let f = field
88 .ident
89 .as_ref()
90 .expect("Field without ident")
91 .to_string();
92 quote! {#f.to_string()}
93 })
94 .collect();
95
96 let table_convertions: proc_macro2::TokenStream = fields
97 .iter()
98 .filter(|field| {
99 let field: ApiField = (*field).clone().into();
100 !field.table_skip
101 })
102 .map(|field| {
103 let field: ApiField = (*field).clone().into();
104 let fname = field.ident.as_ref().expect("Field without ident");
105 let unformated_value = if is_option_vec(&field.ty) {
107 quote!{self.#fname .as_ref().unwrap() .join(", ").replace('\n', "\\n")}
109 } else if is_option(&field.ty) {
110 quote! {self.#fname .clone().unwrap_or_default().to_string().replace('\n', "\\n")}
111 } else if is_vec(&field.ty) {
112 quote!{self.#fname .join(", ").replace('\n', "\\n")}
113 } else {
114 quote! {self.#fname .to_string().replace('\n', "\\n")}
115 };
116
117 let formated_value = if let Some(FieldFormat::Date { format }) = field.table_format {
118 quote!(#unformated_value.parse::<chrono::DateTime<chrono::Utc>>()
119 .into_diagnostic().wrap_err("Can't parse Date")?
120 .format(#format).to_string()
121 )
122 } else {
123 unformated_value
124 };
125
126 quote!(#formated_value ,)
127 })
128 .collect();
129 (headers, table_convertions)
130 }
131 };
132
133 let to_output = if is_pretty {
134 quote!{
135 fn to_output(&self) -> miette::Result<String>
136 where Self:crud_pretty_struct::PrettyPrint
137 {
138 use is_terminal::IsTerminal;
139 self.pretty(std::io::stdout().is_terminal(), None, None)
140 }
141 }
142 } else {
143 quote!{
144 fn to_output(&self) -> miette::Result<String>
145 where Self: Serialize
146 {
147 use miette::IntoDiagnostic;
148 serde_yaml::to_string(self).into_diagnostic()
149 }
150 }
151 };
152
153 quote! {impl crud_api::Api for #struct_ident {
154 fn to_table_header(&self) -> Vec<String> {
155 vec![#(#headers),*]
156 }
157 fn to_table(&self) -> miette::Result<Vec<String>> {
158 Ok(vec![#table_convertions])
159 }
160 #to_output
161 }}
162}
163
164fn is_option(ty: &Type) -> bool {
165 if let Type::Path(s) = ty {
166 if let Some(x) = s.path.segments.first() {
167 return x.ident.eq("Option");
168 }
169 }
170 false
171}
172fn is_vec(ty: &Type) -> bool {
173 if let Type::Path(s) = ty {
174 if let Some(x) = s.path.segments.first() {
175 return x.ident.eq("Vec");
176 }
177 }
178 false
179}
180
181fn is_option_vec(ty: &Type) -> bool {
182 fn strip_type_no_rec(ty: &Type) -> &Type {
184 if is_option(ty) || is_vec(ty) {
185 if let Type::Path(s) = ty {
186 if let Some(segment) = s.path.segments.first() {
187 if let PathArguments::AngleBracketed(first_arg) = &segment.arguments {
188 if let GenericArgument::Type(result_type) = first_arg.args.first().unwrap() {
189 return result_type;
190 }
191 }
192 }
193 }
194 }
195 ty
196 }
197
198 if is_option(ty) {
199 is_vec(strip_type_no_rec(ty))
200 } else {
201 false
202 }
203}