22// SPDX-License-Identifier: Apache-2.0
33// SPDX-License-Identifier: MIT
44
5- use heck:: ToSnakeCase ;
5+ use heck:: { ToLowerCamelCase , ToSnakeCase } ;
66use proc_macro:: TokenStream ;
7- use proc_macro2:: { Span , TokenStream as TokenStream2 , TokenTree } ;
7+ use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
88
99use quote:: { format_ident, quote, quote_spanned} ;
1010use syn:: {
1111 parse:: { Parse , ParseStream } ,
12+ parse_quote,
1213 spanned:: Spanned ,
13- Data , DeriveInput , Error , Fields , FnArg , Ident , ItemFn , LitStr , Pat , Token ,
14+ Data , DeriveInput , Error , Fields , Ident , ItemFn , LitStr , Token ,
1415} ;
1516
16- pub fn generate_run_fn ( input : DeriveInput ) -> TokenStream {
17+ pub ( crate ) fn generate_command_enum ( mut input : DeriveInput ) -> TokenStream {
18+ let mut deserialize_functions = TokenStream2 :: new ( ) ;
19+ let mut errors = TokenStream2 :: new ( ) ;
20+
21+ input. attrs . push ( parse_quote ! ( #[ allow( dead_code) ] ) ) ;
22+
23+ match & mut input. data {
24+ Data :: Enum ( data_enum) => {
25+ for variant in & mut data_enum. variants {
26+ let mut feature: Option < Ident > = None ;
27+ let mut error_message: Option < String > = None ;
28+
29+ for attr in & variant. attrs {
30+ if attr. path . is_ident ( "cmd" ) {
31+ let r = attr
32+ . parse_args_with ( |input : ParseStream | {
33+ if let Ok ( f) = input. parse :: < Ident > ( ) {
34+ feature. replace ( f) ;
35+ input. parse :: < Token ! [ , ] > ( ) ?;
36+ let error_message_raw: LitStr = input. parse ( ) ?;
37+ error_message. replace ( error_message_raw. value ( ) ) ;
38+ }
39+ Ok ( quote ! ( ) )
40+ } )
41+ . unwrap_or_else ( syn:: Error :: into_compile_error) ;
42+ errors. extend ( r) ;
43+ }
44+ }
45+
46+ if let Some ( f) = feature {
47+ let error_message = if let Some ( e) = error_message {
48+ let e = e. to_string ( ) ;
49+ quote ! ( #e)
50+ } else {
51+ quote ! ( "This API is not enabled in the allowlist." )
52+ } ;
53+
54+ let deserialize_function_name = quote:: format_ident!( "__{}_deserializer" , variant. ident) ;
55+ deserialize_functions. extend ( quote ! {
56+ #[ cfg( not( #f) ) ]
57+ #[ allow( non_snake_case) ]
58+ fn #deserialize_function_name<' de, D , T >( deserializer: D ) -> :: std:: result:: Result <T , D :: Error >
59+ where
60+ D : :: serde:: de:: Deserializer <' de>,
61+ {
62+ :: std:: result:: Result :: Err ( :: serde:: de:: Error :: custom( crate :: Error :: ApiNotAllowlisted ( #error_message. into( ) ) . to_string( ) ) )
63+ }
64+ } ) ;
65+
66+ let deserialize_function_name = deserialize_function_name. to_string ( ) ;
67+
68+ variant
69+ . attrs
70+ . push ( parse_quote ! ( #[ cfg_attr( not( #f) , serde( deserialize_with = #deserialize_function_name) ) ] ) ) ;
71+ }
72+ }
73+ }
74+ _ => {
75+ return Error :: new (
76+ Span :: call_site ( ) ,
77+ "`command_enum` is only implemented for enums" ,
78+ )
79+ . to_compile_error ( )
80+ . into ( )
81+ }
82+ } ;
83+
84+ TokenStream :: from ( quote ! {
85+ #errors
86+ #input
87+ #deserialize_functions
88+ } )
89+ }
90+
91+ pub ( crate ) fn generate_run_fn ( input : DeriveInput ) -> TokenStream {
1792 let name = & input. ident ;
1893 let data = & input. data ;
1994
95+ let mut errors = TokenStream2 :: new ( ) ;
96+
2097 let mut is_async = false ;
98+
2199 let attrs = input. attrs ;
22100 for attr in attrs {
23101 if attr. path . is_ident ( "cmd" ) {
24- let _ = attr. parse_args_with ( | input : ParseStream | {
25- while let Some ( token ) = input . parse ( ) ? {
26- if let TokenTree :: Ident ( ident ) = token {
27- is_async |= ident == "async" ;
102+ let r = attr
103+ . parse_args_with ( | input : ParseStream | {
104+ if let Ok ( token ) = input . parse :: < Ident > ( ) {
105+ is_async = token == "async" ;
28106 }
29- }
30- Ok ( ( ) )
31- } ) ;
107+ Ok ( quote ! ( ) )
108+ } )
109+ . unwrap_or_else ( syn:: Error :: into_compile_error) ;
110+ errors. extend ( r) ;
32111 }
33112 }
113+
34114 let maybe_await = if is_async { quote ! ( . await ) } else { quote ! ( ) } ;
35115 let maybe_async = if is_async { quote ! ( async ) } else { quote ! ( ) } ;
36116
@@ -43,6 +123,30 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
43123 for variant in & data_enum. variants {
44124 let variant_name = & variant. ident ;
45125
126+ let mut feature = None ;
127+
128+ for attr in & variant. attrs {
129+ if attr. path . is_ident ( "cmd" ) {
130+ let r = attr
131+ . parse_args_with ( |input : ParseStream | {
132+ if let Ok ( f) = input. parse :: < Ident > ( ) {
133+ feature. replace ( f) ;
134+ input. parse :: < Token ! [ , ] > ( ) ?;
135+ let _: LitStr = input. parse ( ) ?;
136+ }
137+ Ok ( quote ! ( ) )
138+ } )
139+ . unwrap_or_else ( syn:: Error :: into_compile_error) ;
140+ errors. extend ( r) ;
141+ }
142+ }
143+
144+ let maybe_feature_check = if let Some ( f) = feature {
145+ quote ! ( #[ cfg( #f) ] )
146+ } else {
147+ quote ! ( )
148+ } ;
149+
46150 let ( fields_in_variant, variables) = match & variant. fields {
47151 Fields :: Unit => ( quote_spanned ! { variant. span( ) => } , quote ! ( ) ) ,
48152 Fields :: Unnamed ( fields) => {
@@ -73,9 +177,13 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
73177 variant_execute_function_name. set_span ( variant_name. span ( ) ) ;
74178
75179 matcher. extend ( quote_spanned ! {
76- variant. span( ) => #name:: #variant_name #fields_in_variant => #name:: #variant_execute_function_name( context, #variables) #maybe_await. map( Into :: into) ,
180+ variant. span( ) => #maybe_feature_check # name:: #variant_name #fields_in_variant => #name:: #variant_execute_function_name( context, #variables) #maybe_await. map( Into :: into) ,
77181 } ) ;
78182 }
183+
184+ matcher. extend ( quote ! {
185+ _ => Err ( crate :: error:: into_anyhow( "API not in the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)" ) ) ,
186+ } ) ;
79187 }
80188 _ => {
81189 return Error :: new (
@@ -90,7 +198,8 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
90198 let ( impl_generics, ty_generics, where_clause) = input. generics . split_for_impl ( ) ;
91199
92200 let expanded = quote ! {
93- impl #impl_generics #name #ty_generics #where_clause {
201+ #errors
202+ impl #impl_generics #name #ty_generics #where_clause {
94203 pub #maybe_async fn run<R : crate :: Runtime >( self , context: crate :: endpoints:: InvokeContext <R >) -> super :: Result <crate :: endpoints:: InvokeResponse > {
95204 match self {
96205 #matcher
@@ -105,26 +214,25 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
105214/// Attributes for the module enum variant handler.
106215pub struct HandlerAttributes {
107216 allowlist : Ident ,
108- error_message : String ,
109217}
110218
111219impl Parse for HandlerAttributes {
112220 fn parse ( input : ParseStream ) -> syn:: Result < Self > {
113- let allowlist = input. parse ( ) ?;
114- input. parse :: < Token ! [ , ] > ( ) ?;
115- let raw: LitStr = input. parse ( ) ?;
116- let error_message = raw. value ( ) ;
117221 Ok ( Self {
118- allowlist,
119- error_message,
222+ allowlist : input. parse ( ) ?,
120223 } )
121224 }
122225}
123226
227+ pub enum AllowlistCheckKind {
228+ Runtime ,
229+ Serde ,
230+ }
231+
124232pub struct HandlerTestAttributes {
125233 allowlist : Ident ,
126234 error_message : String ,
127- is_async : bool ,
235+ allowlist_check_kind : AllowlistCheckKind ,
128236}
129237
130238impl Parse for HandlerTestAttributes {
@@ -133,67 +241,56 @@ impl Parse for HandlerTestAttributes {
133241 input. parse :: < Token ! [ , ] > ( ) ?;
134242 let error_message_raw: LitStr = input. parse ( ) ?;
135243 let error_message = error_message_raw. value ( ) ;
136- let _ = input. parse :: < Token ! [ , ] > ( ) ;
137- let is_async = input
138- . parse :: < Ident > ( )
139- . map ( |i| i == "async" )
140- . unwrap_or_default ( ) ;
244+ let allowlist_check_kind =
245+ if let ( Ok ( _) , Ok ( i) ) = ( input. parse :: < Token ! [ , ] > ( ) , input. parse :: < Ident > ( ) ) {
246+ if i == "runtime" {
247+ AllowlistCheckKind :: Runtime
248+ } else {
249+ AllowlistCheckKind :: Serde
250+ }
251+ } else {
252+ AllowlistCheckKind :: Serde
253+ } ;
141254
142255 Ok ( Self {
143256 allowlist,
144257 error_message,
145- is_async ,
258+ allowlist_check_kind ,
146259 } )
147260 }
148261}
149262
150263pub fn command_handler ( attributes : HandlerAttributes , function : ItemFn ) -> TokenStream2 {
151264 let allowlist = attributes. allowlist ;
152- let error_message = attributes. error_message . as_str ( ) ;
153- let signature = function. sig . clone ( ) ;
154265
155266 quote ! (
156267 #[ cfg( #allowlist) ]
157268 #function
158-
159- #[ cfg( not( #allowlist) ) ]
160- #[ allow( unused_variables) ]
161- #[ allow( unused_mut) ]
162- #signature {
163- Err ( anyhow:: anyhow!( crate :: Error :: ApiNotAllowlisted ( #error_message. to_string( ) ) . to_string( ) ) )
164- }
165269 )
166270}
167271
168272pub fn command_test ( attributes : HandlerTestAttributes , function : ItemFn ) -> TokenStream2 {
169273 let allowlist = attributes. allowlist ;
170- let is_async = attributes. is_async ;
171274 let error_message = attributes. error_message . as_str ( ) ;
172275 let signature = function. sig . clone ( ) ;
173- let test_name = function. sig . ident . clone ( ) ;
174- let mut args = quote ! ( ) ;
175- for arg in & function. sig . inputs {
176- if let FnArg :: Typed ( t) = arg {
177- if let Pat :: Ident ( i) = & * t. pat {
178- let ident = & i. ident ;
179- args. extend ( quote ! ( #ident, ) )
180- }
181- }
182- }
183276
184- let response = if is_async {
185- quote ! ( crate :: async_runtime:: block_on(
186- super :: Cmd :: #test_name( crate :: test:: mock_invoke_context( ) , #args)
187- ) )
188- } else {
189- quote ! ( super :: Cmd :: #test_name( crate :: test:: mock_invoke_context( ) , #args) )
277+ let enum_variant_name = function. sig . ident . to_string ( ) . to_lower_camel_case ( ) ;
278+ let response = match attributes. allowlist_check_kind {
279+ AllowlistCheckKind :: Runtime => {
280+ let test_name = function. sig . ident . clone ( ) ;
281+ quote ! ( super :: Cmd :: #test_name( crate :: test:: mock_invoke_context( ) ) )
282+ }
283+ AllowlistCheckKind :: Serde => quote ! {
284+ serde_json:: from_str:: <super :: Cmd >( & format!( r#"{{ "cmd": "{}", "data": null }}"# , #enum_variant_name) )
285+ } ,
190286 } ;
191287
192288 quote ! (
193289 #[ cfg( #allowlist) ]
194290 #function
195291
196292 #[ cfg( not( #allowlist) ) ]
293+ #[ allow( unused_variables) ]
197294 #[ quickcheck_macros:: quickcheck]
198295 #signature {
199296 if let Err ( e) = #response {
0 commit comments