Skip to main content

dioxus_provider_macros/
lib.rs

1#![allow(unused_variables)] // Variables used in quote! macros aren't detected by compiler
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use std::time::Duration;
7use syn::{
8    FnArg, ItemFn, LitStr, Pat, PatType, Result, ReturnType, Token, Type, parse::Parse,
9    parse::ParseStream, parse_macro_input,
10};
11
12/// Attribute arguments for the provider macro
13#[derive(Default)]
14struct ProviderArgs {
15    interval: Option<Duration>,
16    cache_expiration: Option<Duration>,
17    stale_time: Option<Duration>,
18    compose: Vec<syn::Ident>, // List of provider functions to compose
19}
20
21/// Attribute arguments for the mutation macro
22#[derive(Default)]
23struct MutationArgs {
24    invalidates: Vec<syn::Ident>, // List of provider functions to invalidate
25    optimistic: Option<syn::ExprClosure>, // Optimistic closure applied to cached data
26}
27
28impl Parse for ProviderArgs {
29    fn parse(input: ParseStream) -> Result<Self> {
30        let mut args = ProviderArgs::default();
31
32        while !input.is_empty() {
33            let ident: syn::Ident = input.parse()?;
34            input.parse::<Token![=]>()?;
35
36            match ident.to_string().as_str() {
37                "interval" => {
38                    let lit: LitStr = input.parse()?;
39                    let duration_str = lit.value();
40                    let duration = humantime::parse_duration(&duration_str).map_err(|e| {
41                        syn::Error::new_spanned(lit, format!("Invalid duration format: {e}"))
42                    })?;
43                    args.interval = Some(duration);
44                }
45                "cache_expiration" => {
46                    let lit: LitStr = input.parse()?;
47                    let duration_str = lit.value();
48                    let duration = humantime::parse_duration(&duration_str).map_err(|e| {
49                        syn::Error::new_spanned(lit, format!("Invalid duration format: {e}"))
50                    })?;
51                    args.cache_expiration = Some(duration);
52                }
53                "stale_time" => {
54                    let lit: LitStr = input.parse()?;
55                    let duration_str = lit.value();
56                    let duration = humantime::parse_duration(&duration_str).map_err(|e| {
57                        syn::Error::new_spanned(lit, format!("Invalid duration format: {e}"))
58                    })?;
59                    args.stale_time = Some(duration);
60                }
61                "compose" => {
62                    // Parse compose list: compose = [provider1, provider2, ...]
63                    let content;
64                    syn::bracketed!(content in input);
65                    let providers = content.parse_terminated(syn::Ident::parse, Token![,])?;
66                    args.compose = providers.into_iter().collect();
67                }
68                _ => return Err(syn::Error::new_spanned(ident, "Unknown argument")),
69            }
70
71            if input.peek(Token![,]) {
72                input.parse::<Token![,]>()?;
73            }
74        }
75
76        Ok(args)
77    }
78}
79
80impl Parse for MutationArgs {
81    fn parse(input: ParseStream) -> Result<Self> {
82        let mut args = MutationArgs::default();
83
84        while !input.is_empty() {
85            let ident: syn::Ident = input.parse()?;
86            input.parse::<Token![=]>()?;
87
88            match ident.to_string().as_str() {
89                "invalidates" => {
90                    // Parse invalidation list: invalidates = [provider1, provider2, ...]
91                    let content;
92                    syn::bracketed!(content in input);
93                    let providers = content.parse_terminated(syn::Ident::parse, Token![,])?;
94                    args.invalidates = providers.into_iter().collect();
95                }
96                "optimistic" => {
97                    let expr: syn::ExprClosure = input.parse()?;
98                    args.optimistic = Some(expr);
99                }
100                _ => return Err(syn::Error::new_spanned(ident, "Unknown argument")),
101            }
102
103            if input.peek(Token![,]) {
104                input.parse::<Token![,]>()?;
105            }
106        }
107
108        Ok(args)
109    }
110}
111
112/// Provider macro for creating cached, composable data providers
113///
114/// This macro converts an async function into a Provider implementation with
115/// automatic caching, composition, and other advanced features.
116///
117/// # Supported Arguments
118/// - `interval = "30s"` - Background refresh interval
119/// - `cache_expiration = "5min"` - Cache expiration time  
120/// - `stale_time = "1min"` - Time before data is considered stale
121/// - `compose = [provider1, provider2, ...]` - Compose multiple providers in parallel
122///
123/// # Composition Requirements
124/// When using `compose = [...]`, the following requirements must be met:
125///
126/// ## Parameter Clone Requirements
127/// **All function parameters MUST implement `Clone`** when using composition.
128/// Parameters are cloned inside async blocks to enable parallel execution.
129///
130/// ```rust
131/// // ✅ Good - u32 implements Clone
132/// #[provider(compose = [fetch_permissions])]
133/// async fn fetch_user_profile(user_id: u32) -> Result<Profile, Error> {
134///     // fetch_permissions_result is available here
135/// }
136///
137/// // ❌ Bad - non-Clone parameter
138/// #[provider(compose = [fetch_permissions])]
139/// async fn fetch_user_profile(config: NonCloneConfig) -> Result<Profile, Error> {
140///     // This will cause a compile error
141/// }
142///
143/// // ✅ Solution - Add #[derive(Clone)] to your types
144/// #[derive(Clone)]
145/// struct UserConfig { /* fields */ }
146///
147/// #[provider(compose = [fetch_permissions])]
148/// async fn fetch_user_profile(config: UserConfig) -> Result<Profile, Error> {
149///     // Now this works
150/// }
151/// ```
152///
153/// ## Provider Existence Validation
154/// All providers listed in `compose = [...]` must:
155/// - Be valid Rust identifiers
156/// - Exist in the current scope when the macro is expanded
157/// - Have compatible signatures (same parameter types)
158///
159/// The macro generates compile-time calls to verify provider existence and
160/// provides clear error messages if providers are not found.
161///
162/// # Examples
163/// ```rust
164/// #[provider(cache_expiration = "5min")]
165/// async fn fetch_user(id: u32) -> Result<User, String> {
166///     // Implementation
167/// }
168///
169/// #[provider(compose = [fetch_user, fetch_settings], cache_expiration = "3min")]
170/// async fn fetch_full_profile(user_id: u32) -> Result<FullProfile, String> {
171///     // Composed results automatically available as variables:
172///     // - __dioxus_composed_fetch_user_result: Result<User, String>
173///     // - __dioxus_composed_fetch_settings_result: Result<Settings, String>
174///     let user = __dioxus_composed_fetch_user_result?;
175///     let settings = __dioxus_composed_fetch_settings_result?;
176///     Ok(FullProfile { user, settings })
177/// }
178/// ```
179///
180/// # Compilation Errors
181/// The macro provides clear error messages for common issues:
182/// - **Clone not implemented**: "Parameter type 'TypeName' must implement Clone for composition"
183/// - **Provider not found**: "Composed provider 'provider_name' not found in current scope"
184/// - **Signature mismatch**: "Composed provider 'provider_name' has incompatible signature"
185#[proc_macro_attribute]
186pub fn provider(args: TokenStream, input: TokenStream) -> TokenStream {
187    let provider_args = if args.is_empty() {
188        ProviderArgs::default()
189    } else {
190        match syn::parse(args) {
191            Ok(args) => args,
192            Err(err) => return err.to_compile_error().into(),
193        }
194    };
195
196    let input_fn = parse_macro_input!(input as ItemFn);
197
198    let result = generate_provider(input_fn, provider_args);
199
200    match result {
201        Ok(tokens) => tokens.into(),
202        Err(err) => err.to_compile_error().into(),
203    }
204}
205
206/// Mutation macro for creating data mutations with cache invalidation
207///
208/// This macro converts an async function into a Mutation implementation that can
209/// invalidate related provider caches when executed.
210///
211/// # Supported Arguments
212/// - `invalidates = [provider1, provider2, ...]` - Providers to invalidate after mutation
213/// - `optimistic = |data, ...args| { ... }` - Optimistic update closure (requires MutationContext)
214///
215/// ## Optimistic Updates
216/// The optimistic closure receives:
217/// - First param: `&mut Data` - mutable reference to current cached data
218/// - Remaining params: references to mutation inputs
219///
220/// Examples:
221/// - No args: `optimistic = |data: &mut Vec<Item>| { data.clear() }`
222/// - One arg: `optimistic = |data: &mut Vec<Item>, id: &u64| { data.retain(|i| i.id != *id) }`
223/// - Multi-arg: `optimistic = |data: &mut Item, name: &String, status: &bool| { data.name = name.clone(); data.active = *status; }`
224///
225/// ## Return Values
226/// Mutation return values serve multiple purposes:
227/// - Update `MutationState` for UI feedback (Success/Error)
228/// - With optimistic updates: replace cache with server response (avoids refetch)
229/// - Without optimistic: cache is invalidated and providers refetch automatically
230///
231/// # Examples
232/// ```rust
233/// // Simple mutation with cache invalidation
234/// #[mutation(invalidates = [fetch_user, fetch_user_list])]
235/// async fn update_user(user: User) -> Result<User, String> {
236///     // Update user implementation
237///     // Will automatically invalidate fetch_user and fetch_user_list caches
238/// }
239///
240/// // Optimistic mutation with single argument
241/// #[mutation(
242///     invalidates = [load_items],
243///     optimistic = |items: &mut Vec<Item>, id: &u64| {
244///         items.retain(|i| i.id != *id)
245///     }
246/// )]
247/// async fn delete_item(
248///     id: u64,
249///     ctx: MutationContext<Vec<Item>, Error>,
250/// ) -> Result<Vec<Item>, Error> {
251///     ctx.map_current(|items| items.retain(|i| i.id != id))
252///         .ok_or(Error::NoData)
253/// }
254///
255/// // Optimistic mutation with multiple arguments
256/// #[mutation(
257///     invalidates = [load_items],
258///     optimistic = |items: &mut Vec<Item>, id: &u64, name: &String| {
259///         if let Some(item) = items.iter_mut().find(|i| i.id == *id) {
260///             item.name = name.clone();
261///         }
262///     }
263/// )]
264/// async fn update_item(
265///     id: u64,
266///     name: String,
267///     ctx: MutationContext<Vec<Item>, Error>,
268/// ) -> Result<Vec<Item>, Error> {
269///     ctx.map_current(|items| {
270///         if let Some(item) = items.iter_mut().find(|i| i.id == id) {
271///             item.name = name;
272///         }
273///     }).ok_or(Error::NoData)
274/// }
275/// ```
276#[proc_macro_attribute]
277pub fn mutation(args: TokenStream, input: TokenStream) -> TokenStream {
278    let mutation_args = if args.is_empty() {
279        MutationArgs::default()
280    } else {
281        match syn::parse(args) {
282            Ok(args) => args,
283            Err(err) => return err.to_compile_error().into(),
284        }
285    };
286
287    let input_fn = parse_macro_input!(input as ItemFn);
288
289    let result = generate_mutation(input_fn, mutation_args);
290
291    match result {
292        Ok(tokens) => tokens.into(),
293        Err(err) => err.to_compile_error().into(),
294    }
295}
296
297fn generate_provider(input_fn: ItemFn, provider_args: ProviderArgs) -> Result<TokenStream2> {
298    let info = extract_provider_info(&input_fn)?;
299
300    let ProviderInfo {
301        fn_vis,
302        fn_block,
303        output_type,
304        error_type,
305        struct_name,
306        ..
307    } = &info;
308
309    // Extract parameters once
310    let params = extract_all_params(&input_fn)?;
311
312    // Validate composition requirements if compose is used
313    if !provider_args.compose.is_empty() {
314        validate_composition_requirements(&provider_args.compose, &params)?;
315    }
316
317    // Generate enhanced function body with dependency injection and composition
318    let enhanced_fn_block =
319        generate_enhanced_function_body(&provider_args.compose, &params, fn_block);
320
321    // Generate interval and cache expiration implementations
322    let interval_impl = generate_interval_impl(&provider_args);
323    let cache_expiration_impl = generate_cache_expiration_impl(&provider_args);
324    let stale_time_impl = generate_stale_time_impl(&provider_args);
325
326    // Generate common struct and const
327    let common_struct = generate_common_struct_and_const(&info);
328
329    // Determine parameter type and implementation based on function parameters
330    if params.is_empty() {
331        // No parameters - Provider<()>
332        Ok(quote! {
333            #common_struct
334
335            impl #struct_name {
336                #fn_vis async fn call() -> Result<#output_type, #error_type> {
337                    #enhanced_fn_block
338                }
339            }
340
341            impl ::dioxus_provider::hooks::Provider<()> for #struct_name {
342                type Output = #output_type;
343                type Error = #error_type;
344
345                fn run(&self, _param: ()) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send {
346                    Self::call()
347                }
348
349                #interval_impl
350                #cache_expiration_impl
351                #stale_time_impl
352            }
353        })
354    } else if params.len() == 1 {
355        // Single parameter - Provider<ParamType>
356        let param = &params[0];
357        let param_name = &param.name;
358        let param_type = &param.ty;
359
360        Ok(quote! {
361            #common_struct
362
363            impl #struct_name {
364                #fn_vis async fn call(#param_name: #param_type) -> Result<#output_type, #error_type> {
365                    #enhanced_fn_block
366                }
367            }
368
369            impl ::dioxus_provider::hooks::Provider<#param_type> for #struct_name {
370                type Output = #output_type;
371                type Error = #error_type;
372
373                fn run(&self, #param_name: #param_type) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send {
374                    Self::call(#param_name)
375                }
376
377                #interval_impl
378                #cache_expiration_impl
379                #stale_time_impl
380            }
381        })
382    } else {
383        // Multiple parameters - Provider<(Param1, Param2, ...)>
384        let param_names: Vec<_> = params.iter().map(|p| &p.name).collect();
385        let param_types: Vec<_> = params.iter().map(|p| &p.ty).collect();
386        let tuple_type = quote! { (#(#param_types,)*) };
387
388        Ok(quote! {
389            #common_struct
390
391            impl #struct_name {
392                #fn_vis async fn call(#(#param_names: #param_types,)*) -> Result<#output_type, #error_type> {
393                    #enhanced_fn_block
394                }
395            }
396
397            impl ::dioxus_provider::hooks::Provider<#tuple_type> for #struct_name {
398                type Output = #output_type;
399                type Error = #error_type;
400
401                fn run(&self, params: #tuple_type) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send {
402                    let (#(#param_names,)*) = params;
403                    Self::call(#(#param_names,)*)
404                }
405
406                #interval_impl
407                #cache_expiration_impl
408                #stale_time_impl
409            }
410        })
411    }
412}
413
414fn generate_mutation(input_fn: ItemFn, mutation_args: MutationArgs) -> Result<TokenStream2> {
415    let info = extract_provider_info(&input_fn)?;
416
417    let ProviderInfo {
418        fn_vis,
419        fn_block,
420        output_type,
421        error_type,
422        struct_name,
423        fn_name: _fn_name,
424        ..
425    } = &info;
426
427    let enhanced_fn_block = generate_enhanced_function_body(&[], &[], fn_block);
428    let invalidation_impl = generate_invalidation_impl(&mutation_args);
429    let common_struct = generate_common_struct_and_const(&info);
430
431    let raw_params = extract_all_params(&input_fn)?;
432    let has_optimistic = mutation_args.optimistic.is_some();
433    let (input_params, context_param, data_param) =
434        split_mutation_params(raw_params.clone(), output_type, has_optimistic)?;
435
436    // Detect auto-apply mode: optimistic is present and there's a data parameter
437    let is_auto_apply = has_optimistic && data_param.is_some();
438
439    // Build call parameters based on the original function signature
440    let call_params: Vec<_> = raw_params
441        .iter()
442        .map(|p| {
443            let name = &p.name;
444            if let Some(ctx) = &context_param && ctx.name == p.name {
445                let data_ty = &ctx.data_ty;
446                let error_ty = &ctx.error_ty;
447                quote! { #name: ::dioxus_provider::mutation::MutationContext<'_, #data_ty, #error_ty> }
448            } else {
449                let ty = &p.ty;
450                quote! { #name: #ty }
451            }
452        })
453        .collect();
454
455    let call_signature = quote! { #fn_vis async fn call(#(#call_params),*) -> Result<#output_type, #error_type> {
456        #enhanced_fn_block
457    } };
458
459    let input_count = input_params.len();
460    let input_type = build_input_type(&input_params);
461
462    let data_param_name = data_param.as_ref().map(|p| &p.name);
463
464    let call_args_builder = |ctx_ident: Option<&syn::Ident>,
465                             auto_apply_data_expr: Option<TokenStream2>|
466     -> Vec<TokenStream2> {
467        raw_params
468            .iter()
469            .map(|param| {
470                // If this is the context parameter, use the ctx_ident
471                if let Some(ctx) = ctx_ident {
472                    if param.name == *ctx {
473                        return quote! { #ctx };
474                    }
475                }
476                // If this is the data parameter and we have auto-applied data, use that
477                if let Some(data_name) = data_param_name {
478                    if param.name == *data_name {
479                        if let Some(ref data_expr) = auto_apply_data_expr {
480                            return data_expr.clone();
481                        }
482                    }
483                }
484                // Otherwise, use the parameter name as-is
485                let name = &param.name;
486                quote! { #name }
487            })
488            .collect()
489    };
490
491    let context_ident = context_param.as_ref().map(|ctx| ctx.name.clone());
492    let context_data_ty = context_param.as_ref().map(|ctx| ctx.data_ty.clone());
493    let context_error_ty = context_param.as_ref().map(|ctx| ctx.error_ty.clone());
494
495    let optimistic_impl = if let Some(optimistic_expr) = &mutation_args.optimistic {
496        // Generate the call to optimistic closure based on param count
497        let optimistic_call = match input_params.len() {
498            0 => quote! { (#optimistic_expr)(&mut updated) },
499            1 => quote! { (#optimistic_expr)(&mut updated, input) },
500            _ => {
501                let names: Vec<_> = input_params.iter().map(|p| &p.name).collect();
502                quote! {
503                    let (#(ref #names,)*) = *input;
504                    (#optimistic_expr)(&mut updated, #(#names,)*)
505                }
506            }
507        };
508
509        quote! {
510            fn optimistic_updates_with_current(
511                &self,
512                input: &#input_type,
513                current_data: Option<&Result<Self::Output, Self::Error>>,
514            ) -> Vec<(String, Result<Self::Output, Self::Error>)> {
515                let keys = self.invalidates();
516                if keys.is_empty() {
517                    return Vec::new();
518                }
519
520                if let Some(Ok(current)) = current_data {
521                    let mut updated = current.clone();
522                    #optimistic_call;
523
524                    let mut results = Vec::with_capacity(keys.len());
525                    for key in keys {
526                        results.push((key, Ok(updated.clone())));
527                    }
528                    results
529                } else {
530                    Vec::new()
531                }
532            }
533        }
534    } else {
535        quote! {}
536    };
537
538    let (mutate_signature, mutate_body) = {
539        let (signature, mut prelude) = match input_count {
540            0 => (
541                quote! { fn mutate(&self, _input: ()) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
542                Vec::<TokenStream2>::new(),
543            ),
544            1 => {
545                let param = &input_params[0];
546                let name = &param.name;
547                let ty = &param.ty;
548                (
549                    quote! { fn mutate(&self, #name: #ty) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
550                    Vec::<TokenStream2>::new(),
551                )
552            }
553            _ => {
554                let names: Vec<_> = input_params.iter().map(|p| &p.name).collect();
555                (
556                    quote! { fn mutate(&self, input: #input_type) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
557                    vec![quote! { let (#(#names),*) = input; }],
558                )
559            }
560        };
561
562        // Create MutationContext if needed in manual mode
563        if !is_auto_apply {
564            if let (Some(ctx_ident), Some(data_ty), Some(err_ty)) = (
565                context_ident.as_ref(),
566                context_data_ty.as_ref(),
567                context_error_ty.as_ref(),
568            ) {
569                prelude.push(quote! { let #ctx_ident = ::dioxus_provider::mutation::MutationContext::<'static, #data_ty, #err_ty>::new(None); });
570            }
571        }
572
573        let call_args = if is_auto_apply {
574            // Auto-apply mode: provide default data (rarely called - should use mutate_with_current)
575            call_args_builder(None, Some(quote! { Default::default() }))
576        } else {
577            // Manual mode: use context if present
578            call_args_builder(context_ident.as_ref(), None)
579        };
580
581        let call_expr = quote! { Self::call(#(#call_args),*) };
582        let body = quote! { async move { #(#prelude)* #call_expr.await } };
583        (signature, body)
584    };
585
586    let (mutate_with_current_signature, mutate_with_current_body) = {
587        let (signature, mut prelude) = match input_count {
588            0 => (
589                quote! { fn mutate_with_current(
590                    &self,
591                    _input: (),
592                    current_data: Option<&Result<Self::Output, Self::Error>>,
593                ) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
594                Vec::<TokenStream2>::new(),
595            ),
596            1 => {
597                let param = &input_params[0];
598                let name = &param.name;
599                let ty = &param.ty;
600                (
601                    quote! { fn mutate_with_current(
602                        &self,
603                        #name: #ty,
604                        current_data: Option<&Result<Self::Output, Self::Error>>,
605                    ) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
606                    Vec::<TokenStream2>::new(),
607                )
608            }
609            _ => {
610                let names: Vec<_> = input_params.iter().map(|p| &p.name).collect();
611                (
612                    quote! { fn mutate_with_current(
613                        &self,
614                        input: #input_type,
615                        current_data: Option<&Result<Self::Output, Self::Error>>,
616                    ) -> impl ::std::future::Future<Output = Result<Self::Output, Self::Error>> + Send },
617                    vec![quote! { let (#(#names),*) = input; }],
618                )
619            }
620        };
621
622        let call_args = if is_auto_apply {
623            // Auto-apply mode: use current_data directly (already has optimistic update applied by runtime)
624            // DO NOT re-apply the optimistic closure here - that would cause double-application!
625            prelude.push(quote! {
626                let __auto_apply_data = if let Some(Ok(current)) = current_data {
627                    current.clone()
628                } else {
629                    // If no current data, use default
630                    Default::default()
631                };
632            });
633
634            call_args_builder(None, Some(quote! { __auto_apply_data }))
635        } else {
636            // Manual mode: create MutationContext from current_data
637            if let Some(ctx_ident) = context_ident.as_ref() {
638                prelude.push(quote! { let #ctx_ident = ::dioxus_provider::mutation::MutationContext::new(current_data); });
639            }
640            call_args_builder(context_ident.as_ref(), None)
641        };
642
643        let call_expr = quote! { Self::call(#(#call_args),*) };
644        let body = quote! { async move { #(#prelude)* #call_expr.await } };
645        (signature, body)
646    };
647
648    let has_optimistic_impl = if has_optimistic {
649        quote! {
650            fn has_optimistic(&self) -> bool {
651                true
652            }
653        }
654    } else {
655        quote! {}
656    };
657
658    let mutation_impl = quote! {
659        impl ::dioxus_provider::mutation::Mutation<#input_type> for #struct_name {
660            type Output = #output_type;
661            type Error = #error_type;
662
663            #mutate_signature {
664                #mutate_body
665            }
666
667            #mutate_with_current_signature {
668                #mutate_with_current_body
669            }
670
671            #optimistic_impl
672
673            #invalidation_impl
674
675            #has_optimistic_impl
676        }
677    };
678
679    Ok(quote! {
680        #common_struct
681
682        impl #struct_name {
683            #call_signature
684        }
685
686        #mutation_impl
687    })
688}
689
690/// Generate duration implementation for provider methods
691fn generate_duration_impl(method_name: &str, duration: Option<Duration>) -> TokenStream2 {
692    if let Some(duration) = duration {
693        let duration_secs = duration.as_secs();
694        let method_ident = syn::Ident::new(method_name, proc_macro2::Span::call_site());
695
696        quote! {
697            fn #method_ident(&self) -> Option<::std::time::Duration> {
698                Some(::std::time::Duration::from_secs(#duration_secs))
699            }
700        }
701    } else {
702        quote! {}
703    }
704}
705
706/// Generate interval implementation
707fn generate_interval_impl(provider_args: &ProviderArgs) -> TokenStream2 {
708    generate_duration_impl("interval", provider_args.interval)
709}
710
711/// Generate cache expiration implementation
712fn generate_cache_expiration_impl(provider_args: &ProviderArgs) -> TokenStream2 {
713    generate_duration_impl("cache_expiration", provider_args.cache_expiration)
714}
715
716/// Generate stale time implementation
717fn generate_stale_time_impl(provider_args: &ProviderArgs) -> TokenStream2 {
718    generate_duration_impl("stale_time", provider_args.stale_time)
719}
720
721/// Generate invalidation implementation for mutations
722fn generate_invalidation_impl(mutation_args: &MutationArgs) -> TokenStream2 {
723    if mutation_args.invalidates.is_empty() {
724        quote! {}
725    } else {
726        let provider_calls: Vec<_> = mutation_args
727            .invalidates
728            .iter()
729            .map(|provider_fn| {
730                quote! {
731                    ::dioxus_provider::mutation::provider_cache_key_simple(#provider_fn())
732                }
733            })
734            .collect();
735
736        quote! {
737            fn invalidates(&self) -> Vec<String> {
738                vec![#(#provider_calls,)*]
739            }
740        }
741    }
742}
743
744/// Information extracted from the provider function
745struct ProviderInfo {
746    fn_vis: syn::Visibility,
747    fn_attrs: Vec<syn::Attribute>,
748    fn_block: Box<syn::Block>,
749    output_type: Type,
750    error_type: Type,
751    struct_name: syn::Ident,
752    fn_name: syn::Ident,
753}
754
755/// Information about a function parameter
756#[derive(Clone)]
757struct ParamInfo {
758    name: syn::Ident,
759    ty: Type,
760}
761
762#[derive(Clone)]
763struct ContextInfo {
764    name: syn::Ident,
765    data_ty: Type,
766    error_ty: Type,
767}
768
769fn parse_context_type(ty: &Type) -> Option<(Type, Type)> {
770    if let Type::Path(type_path) = ty {
771        if let Some(segment) = type_path.path.segments.last() {
772            if segment.ident == "MutationContext" {
773                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
774                    if args.args.len() == 2 {
775                        let mut iter = args.args.iter();
776                        let data_ty = match iter.next()? {
777                            syn::GenericArgument::Type(ty) => ty.clone(),
778                            _ => return None,
779                        };
780                        let error_ty = match iter.next()? {
781                            syn::GenericArgument::Type(ty) => ty.clone(),
782                            _ => return None,
783                        };
784                        return Some((data_ty, error_ty));
785                    }
786                }
787            }
788        }
789    }
790    None
791}
792
793#[allow(dead_code)]
794fn split_params(params: Vec<ParamInfo>) -> Result<(Vec<ParamInfo>, Option<ContextInfo>)> {
795    let mut input_params = Vec::new();
796    let mut context_param = None;
797
798    for param in params {
799        if let Some((data_ty, error_ty)) = parse_context_type(&param.ty) {
800            if context_param.is_some() {
801                return Err(syn::Error::new_spanned(
802                    param.ty,
803                    "Only one MutationContext parameter is allowed",
804                ));
805            }
806            context_param = Some(ContextInfo {
807                name: param.name,
808                data_ty,
809                error_ty,
810            });
811        } else {
812            input_params.push(param);
813        }
814    }
815
816    Ok((input_params, context_param))
817}
818
819/// Split mutation parameters into input params, context param, and auto-apply data param
820fn split_mutation_params(
821    params: Vec<ParamInfo>,
822    output_type: &Type,
823    has_optimistic: bool,
824) -> Result<(Vec<ParamInfo>, Option<ContextInfo>, Option<ParamInfo>)> {
825    let mut input_params = Vec::new();
826    let mut context_param = None;
827    let mut data_param = None;
828
829    for param in params {
830        if let Some((data_ty, error_ty)) = parse_context_type(&param.ty) {
831            if context_param.is_some() {
832                return Err(syn::Error::new_spanned(
833                    param.ty,
834                    "Only one MutationContext parameter is allowed",
835                ));
836            }
837            context_param = Some(ContextInfo {
838                name: param.name,
839                data_ty,
840                error_ty,
841            });
842        } else {
843            input_params.push(param);
844        }
845    }
846
847    // In auto-apply mode (has optimistic but no context), the last param might be the data param
848    if has_optimistic && context_param.is_none() && !input_params.is_empty() {
849        // Check if the last parameter's type matches the output type
850        if let Some(last_param) = input_params.last() {
851            if types_equal(&last_param.ty, output_type) {
852                data_param = input_params.pop();
853            }
854        }
855    }
856
857    Ok((input_params, context_param, data_param))
858}
859
860/// Compare two types for structural equality
861fn types_equal(ty1: &Type, ty2: &Type) -> bool {
862    ty1 == ty2
863}
864
865/// Extract provider information from the input function
866fn extract_provider_info(input_fn: &ItemFn) -> Result<ProviderInfo> {
867    let fn_name = input_fn.sig.ident.clone();
868    let fn_vis = input_fn.vis.clone();
869    let fn_attrs = input_fn.attrs.clone();
870    let fn_block = input_fn.block.clone();
871
872    let (output_type, error_type) = extract_result_types(&input_fn.sig.output)?;
873    let struct_name = syn::Ident::new(
874        &to_pascal_case(&fn_name.to_string()),
875        proc_macro2::Span::call_site(),
876    );
877
878    Ok(ProviderInfo {
879        fn_vis,
880        fn_attrs,
881        fn_block,
882        output_type,
883        error_type,
884        struct_name,
885        fn_name,
886    })
887}
888
889/// Generate common struct and const for the provider
890fn generate_common_struct_and_const(info: &ProviderInfo) -> TokenStream2 {
891    let struct_name = &info.struct_name;
892    let fn_attrs = &info.fn_attrs;
893    let fn_name = &info.fn_name;
894
895    quote! {
896        #[derive(Clone, PartialEq)]
897        #(#fn_attrs)*
898        pub struct #struct_name;
899
900        impl Default for #struct_name {
901            fn default() -> Self {
902                Self
903            }
904        }
905
906        // Generate a function that returns an instance of the struct
907        pub fn #fn_name() -> #struct_name {
908            #struct_name
909        }
910    }
911}
912
913/// Extract all parameters from the function signature
914fn extract_all_params(input_fn: &ItemFn) -> Result<Vec<ParamInfo>> {
915    let mut params = Vec::new();
916
917    for input in &input_fn.sig.inputs {
918        match input {
919            FnArg::Typed(PatType { pat, ty, .. }) => {
920                if let Pat::Ident(pat_ident) = &**pat {
921                    params.push(ParamInfo {
922                        name: pat_ident.ident.clone(),
923                        ty: (**ty).clone(),
924                    });
925                } else {
926                    return Err(syn::Error::new_spanned(
927                        pat,
928                        "Only simple parameter names are supported",
929                    ));
930                }
931            }
932            FnArg::Receiver(_) => {
933                return Err(syn::Error::new_spanned(
934                    input,
935                    "Methods with self parameter are not supported",
936                ));
937            }
938        }
939    }
940
941    Ok(params)
942}
943
944/// Build the input type: () for 0 params, T for 1 param, (T1, T2, ...) for N params
945fn build_input_type(params: &[ParamInfo]) -> TokenStream2 {
946    match params.len() {
947        0 => quote! { () },
948        1 => {
949            let ty = &params[0].ty;
950            quote! { #ty }
951        }
952        _ => {
953            let types: Vec<_> = params.iter().map(|p| &p.ty).collect();
954            quote! { (#(#types,)*) }
955        }
956    }
957}
958
959/// Extract result types from the function return type
960fn extract_result_types(return_type: &ReturnType) -> Result<(Type, Type)> {
961    match return_type {
962        ReturnType::Default => Err(syn::Error::new_spanned(
963            return_type,
964            "Provider functions must return Result<T, E>",
965        )),
966        ReturnType::Type(_, ty) => {
967            if let Type::Path(type_path) = &**ty {
968                if let Some(segment) = type_path.path.segments.last() {
969                    if segment.ident == "Result" {
970                        if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
971                            if args.args.len() == 2 {
972                                let mut args_iter = args.args.iter();
973
974                                let output_type = match args_iter.next().unwrap() {
975                                    syn::GenericArgument::Type(ty) => ty.clone(),
976                                    _ => {
977                                        return Err(syn::Error::new_spanned(
978                                            args,
979                                            "Result must have type arguments",
980                                        ));
981                                    }
982                                };
983
984                                let error_type = match args_iter.next().unwrap() {
985                                    syn::GenericArgument::Type(ty) => ty.clone(),
986                                    _ => {
987                                        return Err(syn::Error::new_spanned(
988                                            args,
989                                            "Result must have type arguments",
990                                        ));
991                                    }
992                                };
993
994                                return Ok((output_type, error_type));
995                            }
996                        }
997                    }
998                }
999            }
1000
1001            Err(syn::Error::new_spanned(
1002                return_type,
1003                "Provider functions must return Result<T, E>",
1004            ))
1005        }
1006    }
1007}
1008
1009/// Convert a string to PascalCase
1010fn to_pascal_case(s: &str) -> String {
1011    let mut result = String::new();
1012    let mut capitalize_next = true;
1013
1014    for c in s.chars() {
1015        if c == '_' {
1016            capitalize_next = true;
1017        } else if capitalize_next {
1018            result.push(c.to_ascii_uppercase());
1019            capitalize_next = false;
1020        } else {
1021            result.push(c);
1022        }
1023    }
1024
1025    result
1026}
1027
1028/// Validate composition requirements for compose providers
1029fn validate_composition_requirements(
1030    compose_providers: &[syn::Ident],
1031    params: &[ParamInfo],
1032) -> Result<()> {
1033    // Validate that all parameters implement Clone when composition is used
1034    if !params.is_empty() {
1035        validate_clone_requirements(params)?;
1036    }
1037
1038    // Validate that composed providers exist (generates compile-time checks)
1039    validate_provider_existence(compose_providers)?;
1040
1041    Ok(())
1042}
1043
1044/// Validate that all parameters implement Clone for composition
1045fn validate_clone_requirements(params: &[ParamInfo]) -> Result<()> {
1046    for param in params {
1047        let param_type = &param.ty;
1048        let param_name = &param.name;
1049
1050        // Generate a compile-time assertion that the type implements Clone
1051        // This will be added to the generated code to provide clear error messages
1052        let _clone_check = quote! {
1053            const _: fn() = || {
1054                fn assert_clone<T: Clone>() {}
1055                assert_clone::<#param_type>();
1056            };
1057        };
1058
1059        // Note: The actual Clone validation happens at compile-time when the generated
1060        // code tries to clone the parameters. The error message will be improved by
1061        // the explicit clone calls we generate in generate_composition_statements_with_validation.
1062    }
1063
1064    Ok(())
1065}
1066
1067/// Validate that composed providers exist by generating compile-time checks
1068fn validate_provider_existence(compose_providers: &[syn::Ident]) -> Result<()> {
1069    // We can't fully validate provider existence at macro expansion time,
1070    // but we can generate code that will provide better error messages
1071    // if the providers don't exist or have incompatible signatures.
1072
1073    for provider in compose_providers {
1074        // Generate a compile-time check that will give a clear error if the provider doesn't exist
1075        let _existence_check = quote! {
1076            const _: fn() = || {
1077                // This will cause a compile error with a clear message if the provider doesn't exist
1078                let _ = #provider;
1079            };
1080        };
1081    }
1082
1083    Ok(())
1084}
1085
1086/// Generate enhanced function body with composition
1087fn generate_enhanced_function_body(
1088    compose_providers: &[syn::Ident],
1089    params: &[ParamInfo],
1090    original_block: &syn::Block,
1091) -> syn::Block {
1092    let mut statements = Vec::new();
1093
1094    // Add composition statements
1095    if !compose_providers.is_empty() {
1096        let composition_statements = generate_composition_statements(compose_providers, params);
1097        statements.extend(composition_statements);
1098    }
1099
1100    // Add original function body statements
1101    statements.extend(original_block.stmts.clone());
1102
1103    syn::Block {
1104        brace_token: original_block.brace_token,
1105        stmts: statements,
1106    }
1107}
1108
1109/// Generate composition statements that can be directly added to a statement list
1110fn generate_composition_statements(
1111    compose_providers: &[syn::Ident],
1112    params: &[ParamInfo],
1113) -> Vec<syn::Stmt> {
1114    if compose_providers.is_empty() {
1115        return vec![];
1116    }
1117
1118    let mut statements = Vec::new();
1119
1120    // Add compile-time validation checks for better error messages
1121    statements.extend(generate_validation_statements(compose_providers, params));
1122
1123    // Generate variable names for composed results with unique prefix to avoid collisions
1124    let result_vars: Vec<_> = compose_providers
1125        .iter()
1126        .map(|provider| {
1127            syn::Ident::new(
1128                &format!("__dioxus_composed_{provider}_result"),
1129                proc_macro2::Span::call_site(),
1130            )
1131        })
1132        .collect();
1133
1134    // Generate provider calls based on parameter count
1135    if params.is_empty() {
1136        // No parameters - call providers with ()
1137        let provider_calls: Vec<_> = compose_providers
1138            .iter()
1139            .map(|provider| {
1140                quote! {
1141                    async { #provider().run(()).await }
1142                }
1143            })
1144            .collect();
1145
1146        let join_stmt: syn::Stmt = syn::parse_quote! {
1147            let (#(#result_vars,)*) = ::futures::join!(
1148                #(#provider_calls,)*
1149            );
1150        };
1151        statements.push(join_stmt);
1152    } else if params.len() == 1 {
1153        // Single parameter - clone it inside each async block
1154        let param_name = &params[0].name;
1155        let param_type = &params[0].ty;
1156
1157        let provider_calls: Vec<_> = compose_providers
1158            .iter()
1159            .map(|provider| {
1160                quote! {
1161                    async {
1162                        // Explicit clone with helpful error context
1163                        let param: #param_type = #param_name.clone();
1164                        #provider().run(param).await
1165                    }
1166                }
1167            })
1168            .collect();
1169
1170        let join_stmt: syn::Stmt = syn::parse_quote! {
1171            let (#(#result_vars,)*) = ::futures::join!(
1172                #(#provider_calls,)*
1173            );
1174        };
1175        statements.push(join_stmt);
1176    } else {
1177        // Multiple parameters - clone each parameter inside each async block
1178        let param_names: Vec<_> = params.iter().map(|p| &p.name).collect();
1179        let param_types: Vec<_> = params.iter().map(|p| &p.ty).collect();
1180
1181        let provider_calls: Vec<_> = compose_providers
1182            .iter()
1183            .map(|provider| {
1184                quote! {
1185                    async {
1186                        // Explicit clone with helpful error context for each parameter
1187                        let params: (#(#param_types,)*) = (#(#param_names.clone(),)*);
1188                        #provider().run(params).await
1189                    }
1190                }
1191            })
1192            .collect();
1193
1194        let join_stmt: syn::Stmt = syn::parse_quote! {
1195            let (#(#result_vars,)*) = ::futures::join!(
1196                #(#provider_calls,)*
1197            );
1198        };
1199        statements.push(join_stmt);
1200    }
1201
1202    statements
1203}
1204
1205/// Generate compile-time validation statements for better error messages
1206fn generate_validation_statements(
1207    compose_providers: &[syn::Ident],
1208    params: &[ParamInfo],
1209) -> Vec<syn::Stmt> {
1210    let mut statements = Vec::new();
1211
1212    // Add Clone validation for parameters if composition is used
1213    if !params.is_empty() {
1214        for param in params {
1215            let param_type = &param.ty;
1216            let param_name = &param.name;
1217
1218            // Generate a compile-time Clone assertion with helpful error message
1219            let clone_check: syn::Stmt = syn::parse_quote! {
1220                const _: () = {
1221                    fn __dioxus_provider_assert_clone<T: ::std::clone::Clone>() {}
1222                    fn __dioxus_provider_validate_parameter_clone() {
1223                        __dioxus_provider_assert_clone::<#param_type>();
1224                    }
1225                };
1226            };
1227            statements.push(clone_check);
1228        }
1229    }
1230
1231    // Add provider existence validation
1232    for provider in compose_providers {
1233        // Generate a compile-time check that the provider exists and is callable
1234        let existence_check: syn::Stmt = syn::parse_quote! {
1235            const _: () = {
1236                fn __dioxus_provider_validate_existence() {
1237                    // This will cause a clear compile error if the provider doesn't exist
1238                    let _provider_exists = #provider;
1239                }
1240            };
1241        };
1242        statements.push(existence_check);
1243    }
1244
1245    statements
1246}