Skip to main content

turbocharger_impl/
lib.rs

1//! This crate provides Turbocharger's procedural macros.
2//!
3//! Please refer to the `turbocharger` crate for details.
4
5#![forbid(unsafe_code)]
6
7#[cfg(test)]
8todo_or_die::crates_io!("bincode", ">=2");
9
10mod extract;
11use proc_macro_error::{abort, proc_macro_error};
12use quote::{format_ident, quote, quote_spanned};
13use syn::{parse_macro_input, parse_quote, spanned::Spanned};
14
15#[proc_macro]
16pub fn remote_addr(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
17 quote!(_turbocharger_connection_info.as_ref().and_then(|ref i| i.remote_addr)).into()
18}
19
20#[proc_macro]
21pub fn user_agent(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
22 quote!(_turbocharger_connection_info.as_ref().and_then(|ref i| i.user_agent.as_ref())).into()
23}
24
25struct ConnectionLocal {
26 pub ident: syn::Ident,
27 pub ty: syn::Type,
28}
29
30impl syn::parse::Parse for ConnectionLocal {
31 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
32  let ident = input.parse()?;
33
34  let _: syn::Token![:] = input.parse()?;
35  let _: syn::Token![&] = input.parse()?;
36  let _: syn::Token![mut] = input.parse()?;
37
38  let ty = input.parse()?;
39  Ok(Self { ident, ty })
40 }
41}
42
43#[proc_macro]
44pub fn connection_local(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
45 let ConnectionLocal { ident, ty } = parse_macro_input!(input as ConnectionLocal);
46 let ident_str = ident.to_string();
47
48 quote! {
49  let mut _turbocharger_connection_local_map = match _turbocharger_connection_info.as_ref() {
50   Some(c) => Some(c.connection_local.lock().await),
51   None => None,
52  };
53
54  let #ident = {
55   _turbocharger_connection_local_map
56    .as_mut()
57    .unwrap()
58    .entry(( #ident_str , std::any::TypeId::of::< #ty >()))
59    .or_insert_with(|| Box::new( #ty ::default()))
60    .downcast_mut::< #ty >()
61    .unwrap()
62  };
63 }
64 .into()
65}
66
67/// Apply this to an item to make it available on the server target only.
68///
69/// Only adds `#[cfg(not(target_arch = "wasm32"))]`
70#[proc_macro_attribute]
71pub fn server_only(
72 _args: proc_macro::TokenStream,
73 input: proc_macro::TokenStream,
74) -> proc_macro::TokenStream {
75 let orig_item = parse_macro_input!(input as syn::Item);
76 proc_macro::TokenStream::from(quote! {
77  #[cfg(not(target_arch = "wasm32"))]
78  #orig_item
79 })
80}
81
82/// Apply this to an item to make it available on the wasm target only.
83///
84/// Only adds `#[cfg(target_arch = "wasm32")]` and ensures `wasm_bindgen::prelude::*` is available.
85#[proc_macro_attribute]
86pub fn wasm_only(
87 _args: proc_macro::TokenStream,
88 input: proc_macro::TokenStream,
89) -> proc_macro::TokenStream {
90 let orig_item = parse_macro_input!(input as syn::Item);
91 proc_macro::TokenStream::from(quote! {
92  #[cfg(any(feature = "wasm", target_arch = "wasm32"))]
93  #[allow(unused_imports)]
94  use wasm_bindgen::prelude::*;
95
96  #[cfg(any(feature = "wasm", target_arch = "wasm32"))]
97  #orig_item
98 })
99}
100
101/// Apply this to a `pub async fn` to make it available (over the network) to the JS frontend. Also apply to any `struct`s used in backend function signatures.
102#[proc_macro_attribute]
103#[proc_macro_error]
104pub fn backend(
105 args: proc_macro::TokenStream,
106 input: proc_macro::TokenStream,
107) -> proc_macro::TokenStream {
108 backend_item(args, syn::parse_macro_input!(input as syn::Item)).into()
109}
110
111fn backend_item(args: proc_macro::TokenStream, orig_item: syn::Item) -> proc_macro2::TokenStream {
112 match orig_item {
113  syn::Item::Fn(orig) => backend_fn(args, orig),
114  syn::Item::Struct(orig) => backend_struct(orig),
115  // syn::Item::Mod(orig) => backend_mod(orig),
116  _ => abort!(orig_item, "Apply #[backend] to `fn` or `struct`."),
117 }
118}
119
120// fn backend_mod_item(orig_item: syn::Item) -> proc_macro2::TokenStream {
121//  match orig_item {
122//   syn::Item::Fn(orig) => backend_fn(orig),
123//   syn::Item::Struct(orig) => backend_struct(orig),
124//   orig => quote! { #orig },
125//  }
126// }
127
128// fn backend_mod(orig_mod: syn::ItemMod) -> proc_macro2::TokenStream {
129//  let content = orig_mod.content.clone();
130
131//  let items: Vec<_> = content
132//   .unwrap_or_else(|| abort!(orig_mod, "Apply #[backend] to a `mod` with a body."))
133//   .1
134//   .into_iter()
135//   .map(backend_mod_item)
136//   .collect();
137
138//  quote! { #(#items)* }
139// }
140
141fn backend_struct(orig_struct: syn::ItemStruct) -> proc_macro2::TokenStream {
142 if std::env::current_exe().unwrap().file_stem().unwrap() != "rust-analyzer" {
143  let mut api_struct = orig_struct.clone();
144  api_struct.vis = parse_quote!();
145  api_struct.attrs.retain(|attr| attr.path.is_ident("doc"));
146  for field in &mut api_struct.fields {
147   field.vis = parse_quote!();
148   field.attrs.retain(|attr| attr.path.is_ident("doc"));
149  }
150
151  let lockfile = std::fs::File::create(std::env::temp_dir().join("turbocharger.lock")).unwrap();
152  fs2::FileExt::lock_exclusive(&lockfile).unwrap();
153
154  // add api_struct to file if it doesn't already exist, or replace it if it does
155  let mut file = read_backend_api_rs();
156
157  let mut found = false;
158  for item in &mut file.items {
159   if let syn::Item::Struct(ref mut item) = item {
160    if item.ident == api_struct.ident {
161     found = true;
162     *item = api_struct.clone();
163     break;
164    }
165   }
166  }
167  if !found {
168   file.items.push(syn::Item::Struct(api_struct));
169  }
170
171  write_backend_api_rs(file);
172 }
173
174 #[allow(clippy::redundant_clone)]
175 let syn::ItemStruct { attrs, ident, fields, .. } = orig_struct.clone();
176
177 let output = quote! {
178  #[cfg(target_arch = "wasm32")]
179  #[allow(unused_imports)]
180  use wasm_bindgen::prelude::*;
181
182  #[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone, inspectable))]
183  #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize, Clone)]
184  #(#attrs)*
185  #[serde(crate = "::turbocharger::serde")]
186  pub struct #ident #fields
187
188  #[cfg(target_arch = "wasm32")]
189  #[wasm_bindgen]
190  impl #ident {
191   #[wasm_bindgen(constructor)]
192   pub fn new() -> #ident {
193    #ident::default()
194   }
195  }
196 };
197
198 #[cfg(feature = "debug_expansions")]
199 {
200  std::fs::create_dir_all("debug_expansions").ok();
201  std::fs::write(
202   project_root_path_with(format!("debug_expansions/{}.rs", orig_struct.ident)),
203   prettyplease::unparse(&parse_quote!( #output )),
204  )
205  .unwrap();
206
207  std::process::Command::new("rustfmt")
208   .current_dir(project_root_path_with("debug_expansions"))
209   .arg("--edition")
210   .arg("2021")
211   .arg("--config")
212   .arg("tab_spaces=1")
213   .arg(format!("{}.rs", orig_struct.ident))
214   .output()
215   .unwrap();
216 }
217
218 output
219}
220
221fn backend_fn(args: proc_macro::TokenStream, orig_fn: syn::ItemFn) -> proc_macro2::TokenStream {
222 let is_js = args.to_string() == "js";
223
224 if std::env::current_exe().unwrap().file_stem().unwrap() != "rust-analyzer" {
225  let mut api_fn = orig_fn.clone();
226  api_fn.vis = parse_quote!();
227  api_fn.attrs.retain(|attr| attr.path.is_ident("doc"));
228  api_fn.block = parse_quote!({});
229
230  let lockfile = std::fs::File::create(std::env::temp_dir().join("turbocharger.lock")).unwrap();
231  fs2::FileExt::lock_exclusive(&lockfile).unwrap();
232
233  // add api_fn to file if it doesn't already exist, or replace it if it does
234  let mut file = read_backend_api_rs();
235
236  let mut found = false;
237  for item in &mut file.items {
238   if let syn::Item::Fn(ref mut item) = item {
239    if item.sig.ident == api_fn.sig.ident {
240     found = true;
241     *item = api_fn.clone();
242     break;
243    }
244   }
245  }
246  if !found {
247   file.items.push(syn::Item::Fn(api_fn));
248  }
249
250  write_backend_api_rs(file);
251 }
252
253 let orig_fn_ident = orig_fn.sig.ident.clone();
254 let orig_fn_string = orig_fn_ident.to_string();
255 let orig_fn_params = orig_fn.sig.inputs.clone();
256 let orig_fn_stmts = &orig_fn.block.stmts;
257 let orig_fn_stmts = quote!(#( #orig_fn_stmts )*);
258
259 let store_name = format_ident!("_TURBOCHARGER_STORE_{}", orig_fn_ident);
260 let dispatch = format_ident!("_TURBOCHARGER_DISPATCH_{}", orig_fn_ident);
261 let req = format_ident!("_TURBOCHARGER_REQ_{}", orig_fn_ident);
262 let resp = format_ident!("_TURBOCHARGER_RESP_{}", orig_fn_ident);
263 let js_fn_ident = format_ident!("_TURBOCHARGER_JS_{}", orig_fn_ident);
264 let remote_fn_ident = format_ident!("remote_{}", orig_fn_ident);
265 let remote_impl_ident = format_ident!("_TURBOCHARGER_REMOTEIMPL_{}", orig_fn_ident);
266 let subscriber_fn_ident = format_ident!("_TURBOCHARGER_SUBSCRIBERFN_{}", orig_fn_ident);
267
268 let orig_fn_ret_ty = match orig_fn.sig.output.clone() {
269  syn::ReturnType::Type(_, path) => *path,
270  syn::ReturnType::Default => parse_quote! { () },
271 };
272 let stream_inner_ty = extract::extract_stream(&orig_fn_ret_ty);
273 let result_inner_ty = extract::extract_result(stream_inner_ty.unwrap_or(&orig_fn_ret_ty));
274 let store_value_ty = if result_inner_ty.is_some() {
275  quote! { Result<#result_inner_ty, JsValue> }
276 } else {
277  quote! { #stream_inner_ty }
278 };
279
280 let maybe_map_err_jsvalue = match result_inner_ty {
281  Some(_) => quote! { .map_err(|e| ::turbocharger::js_sys::Error::new(&e.to_string()).into()) },
282  None => quote! {},
283 };
284
285 let send_value_to_subscription = if result_inner_ty.is_some() {
286  quote! {
287   if let Some(value) = self.value.lock().unwrap().clone() {
288    let promise: ::turbocharger::js_sys::Promise = match value.clone() {
289     Ok(t) => ::turbocharger::js_sys::Promise::resolve(&t.into()).into(),
290     Err(e) => ::turbocharger::js_sys::Promise::reject(&e.into()).into(),
291    };
292    subscription.call1(&JsValue::null(), &promise).ok();
293   }
294  }
295 } else {
296  quote! {
297   if let Some(value) = self.value.lock().unwrap().clone() {
298    subscription.call1(&JsValue::null(), &value.into()).ok();
299   }
300  }
301 };
302
303 let send_value_to_subscriptions = if result_inner_ty.is_some() {
304  quote! {
305   if let Some(value) = value.lock().unwrap().clone() {
306    let promise: ::turbocharger::js_sys::Promise = match value.clone() {
307     Ok(t) => ::turbocharger::js_sys::Promise::resolve(&t.into()).into(),
308     Err(e) => ::turbocharger::js_sys::Promise::reject(&e.into()).into(),
309    };
310    for subscription in subscriptions.lock().unwrap().iter() {
311     if let Some(subscription) = subscription.lock().unwrap().as_ref() {
312      subscription.call1(&JsValue::null(), &promise).ok();
313     }
314    }
315   }
316  }
317 } else {
318  quote! {
319   if let Some(value) = value.lock().unwrap().clone() {
320    for subscription in subscriptions.lock().unwrap().iter() {
321     if let Some(subscription) = subscription.lock().unwrap().as_ref() {
322      subscription.call1(&JsValue::null(), &value.clone().into()).ok();
323     }
324    }
325   }
326  }
327 };
328
329 let bindgen_ret_ty = match (stream_inner_ty, result_inner_ty) {
330  (None, Some(ty)) => quote! { Result<#ty, JsValue> },
331  (Some(_ty), _) => quote! { #store_name },
332  (None, None) => quote! { #orig_fn_ret_ty },
333 };
334 let serialize_ret_ty = match &stream_inner_ty {
335  Some(ty) => quote! { #ty },
336  None => quote! { #orig_fn_ret_ty },
337 };
338
339 let orig_fn_ret_ty = if let Some(ty) = stream_inner_ty {
340  quote_spanned! {ty.span()=> impl ::turbocharger::futures_util::stream::Stream<Item = #ty > }
341 } else {
342  quote_spanned! {orig_fn_ret_ty.span()=> #orig_fn_ret_ty }
343 };
344
345 let tuple_indexes = (0..orig_fn_params.len()).map(syn::Index::from);
346 let orig_fn_param_names: Vec<_> = orig_fn_params
347  .iter()
348  .map(|p| match p {
349   syn::FnArg::Receiver(_) => abort!(p, "I don't know what to do with `self` here."),
350   syn::FnArg::Typed(pattype) => match *pattype.pat.clone() {
351    syn::Pat::Ident(i) => i.ident,
352    _ => abort!(pattype, "Parameter name is not Ident"),
353   },
354  })
355  .collect();
356
357 let orig_fn_param_tys: Vec<_> = orig_fn_params
358  .iter()
359  .map(|p| match p {
360   syn::FnArg::Receiver(_) => abort!(p, "I don't know what to do with `self` here."),
361   syn::FnArg::Typed(pattype) => &pattype.ty,
362  })
363  .collect();
364
365 let orig_fn_params_maybe_comma = if orig_fn_params.is_empty() { quote!() } else { quote!( , ) };
366
367 let mut orig_fn = orig_fn;
368 orig_fn.sig.output = parse_quote! { -> #orig_fn_ret_ty };
369 orig_fn.block = parse_quote!({
370  let _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo> = None;
371  #orig_fn_stmts
372 });
373
374 let mut remote_impl_fn = orig_fn.clone();
375 remote_impl_fn.sig.ident = remote_impl_ident.clone();
376 remote_impl_fn.sig.inputs = parse_quote!(
377  _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo>
378  #orig_fn_params_maybe_comma
379  #orig_fn_params
380 );
381 remote_impl_fn.block = parse_quote!({ #orig_fn_stmts });
382
383 let executebody = match &stream_inner_ty {
384  Some(_ty) => quote! {
385   use ::turbocharger::futures_util::stream::StreamExt as _;
386   use ::turbocharger::stream_cancel::StreamExt as _;
387   let stream = #remote_impl_ident(_turbocharger_connection_info #orig_fn_params_maybe_comma #( self.params. #tuple_indexes .clone() ),*);
388   ::turbocharger::futures_util::pin_mut!(stream);
389
390   if let Some(tripwire) = tripwire {
391    let mut incoming = stream.take_until_if(tripwire);
392    while let Some(result) = incoming.next().await {
393     let response = #resp {
394      txid: self.txid,
395      result: result.clone()
396     };
397     sender(::turbocharger::bincode::serialize(&response).unwrap());
398    }
399   }
400   else {
401    while let Some(result) = stream.next().await {
402     let response = #resp {
403      txid: self.txid,
404      result: result.clone()
405     };
406     sender(::turbocharger::bincode::serialize(&response).unwrap());
407    }
408   }
409  },
410  None => quote! {
411   let result = #remote_impl_ident(_turbocharger_connection_info #orig_fn_params_maybe_comma #( self.params. #tuple_indexes .clone() ),*).await;
412   let response = #resp {
413    txid: self.txid,
414    result
415   };
416   sender(::turbocharger::bincode::serialize(&response).unwrap());
417  },
418 };
419
420 let maybe_svelte_typescript_type = if cfg!(feature = "svelte") {
421  quote! {
422   #[cfg(target_arch = "wasm32")]
423   #[wasm_bindgen]
424   extern "C" {
425    #[wasm_bindgen(typescript_type = "Subscriber<any>")]
426    #[allow(non_camel_case_types)]
427    pub type #subscriber_fn_ident;
428   }
429  }
430 } else {
431  quote!()
432 };
433
434 let wasm_side = match &stream_inner_ty {
435  Some(_ty) => quote! {
436   #[cfg(target_arch = "wasm32")]
437   pub fn #orig_fn_ident(#orig_fn_params) -> #orig_fn_ret_ty {
438    let tx = ::turbocharger::_Transaction::new();
439    let req = ::turbocharger::bincode::serialize(&#req {
440     typetag_const_one: 1,
441     dispatch_name: #orig_fn_string,
442     txid: tx.txid,
443     params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
444    })
445    .unwrap();
446    tx.send_ws(req);
447
448    let (resp_tx, resp_rx) = ::turbocharger::futures_channel::mpsc::unbounded();
449
450    tx.set_sender(Box::new(move |response| {
451     use ::turbocharger::futures_util::SinkExt as _;
452     let #resp { result, .. } =
453      ::turbocharger::bincode::deserialize(&response).unwrap();
454     let mut resp_tx = resp_tx.clone();
455     wasm_bindgen_futures::spawn_local(async move {
456      resp_tx.send(result).await.unwrap();
457     });
458    }));
459
460    resp_rx
461   }
462  },
463  None => quote! {
464   #[cfg(target_arch = "wasm32")]
465   pub async fn #orig_fn_ident(#orig_fn_params) -> #orig_fn_ret_ty {
466    let tx = ::turbocharger::_Transaction::new();
467    let req = ::turbocharger::bincode::serialize(&#req {
468     typetag_const_one: 1,
469     dispatch_name: #orig_fn_string,
470     txid: tx.txid,
471     params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
472    })
473    .unwrap();
474    tx.send_ws(req);
475    let response = tx.resp().await;
476    let #resp { result, .. } =
477     ::turbocharger::bincode::deserialize(&response).unwrap();
478    result
479   }
480  },
481 };
482
483 let js_side = if !is_js {
484  quote!()
485 } else {
486  match &stream_inner_ty {
487   Some(_ty) => quote! {
488    #[cfg(target_arch = "wasm32")]
489    #[allow(non_camel_case_types)]
490    #[wasm_bindgen]
491    pub struct #store_name {
492     req: std::sync::Arc<std::sync::Mutex<#req>>,
493     value: std::sync::Arc<std::sync::Mutex<Option< #store_value_ty >>>,
494     subscriptions: std::sync::Arc<std::sync::Mutex<Vec<std::sync::Arc<std::sync::Mutex<Option<::turbocharger::js_sys::Function>>>>>>,
495    }
496
497    #maybe_svelte_typescript_type
498
499    #[cfg(target_arch = "wasm32")]
500    #[wasm_bindgen]
501    impl #store_name {
502     #[wasm_bindgen]
503     pub fn subscribe(&mut self, subscription: #subscriber_fn_ident) -> JsValue {
504      let subscription: ::turbocharger::js_sys::Function = JsValue::from(subscription).into();
505      if self.subscriptions.lock().unwrap().is_empty() {
506       let tx = ::turbocharger::_Transaction::new();
507       self.req.lock().unwrap().txid = tx.txid;
508       tx.send_ws(::turbocharger::bincode::serialize(&*self.req.lock().unwrap()).unwrap());
509       let subscriptions = self.subscriptions.clone();
510       let value = self.value.clone();
511       tx.set_sender(Box::new(move |response| {
512        let #resp { result, .. } =
513         ::turbocharger::bincode::deserialize(&response).unwrap();
514        value.lock().unwrap().replace(result.clone() #maybe_map_err_jsvalue );
515        #send_value_to_subscriptions
516       }));
517      }
518
519      #send_value_to_subscription
520      let subscription_handle = std::sync::Arc::new(std::sync::Mutex::new(Some(subscription)));
521      self.subscriptions.lock().unwrap().push(subscription_handle.clone());
522      let subscriptions = self.subscriptions.clone();
523      let req_clone = self.req.clone();
524
525      Closure::wrap(Box::new(move || {
526       subscription_handle.lock().unwrap().take();
527       subscriptions.lock().unwrap().retain(|s| { s.lock().unwrap().is_some() });
528       if subscriptions.lock().unwrap().is_empty() {
529        let tx = ::turbocharger::_Transaction::new();
530        tx.send_ws(::turbocharger::bincode::serialize(&*req_clone.lock().unwrap()).unwrap());
531       }
532      }) as Box<dyn Fn()>)
533      .into_js_value()
534     }
535    }
536
537    #[cfg(target_arch = "wasm32")]
538    #[allow(non_snake_case)]
539    #[wasm_bindgen(js_name = #orig_fn_ident)]
540    pub fn #js_fn_ident(#orig_fn_params) -> #bindgen_ret_ty {
541     let req = #req {
542      typetag_const_one: 1,
543      dispatch_name: #orig_fn_string,
544      txid: 1,
545      params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
546     };
547     #store_name {
548      req: std::sync::Arc::new(std::sync::Mutex::new(req)),
549      value: Default::default(),
550      subscriptions: Default::default()
551     }
552    }
553   },
554   None => quote! {
555    #[cfg(target_arch = "wasm32")]
556    #[allow(non_snake_case)]
557    #[wasm_bindgen(js_name = #orig_fn_ident)]
558    pub async fn #js_fn_ident(#orig_fn_params) -> #bindgen_ret_ty {
559     #orig_fn_ident(#( #orig_fn_param_names ),*) .await #maybe_map_err_jsvalue
560    }
561   },
562  }
563 };
564
565 let output = quote! {
566  #[cfg(target_arch = "wasm32")]
567  #[allow(unused_imports)]
568  use wasm_bindgen::prelude::*;
569
570  #[cfg(not(target_arch = "wasm32"))]
571  #[allow(unused_imports)]
572  use turbocharger::prelude::*;
573
574  #[cfg(not(target_arch = "wasm32"))]
575  #[tracked]
576  #orig_fn
577
578  #[cfg(not(target_arch = "wasm32"))]
579  #[allow(non_snake_case)]
580  #[tracked]
581  #remote_impl_fn
582
583  #[cfg(not(target_arch = "wasm32"))]
584  #[allow(non_snake_case)]
585  #[::turbocharger::typetag::serde(name = #orig_fn_string)]
586  #[::turbocharger::async_trait]
587  impl ::turbocharger::RPC for #dispatch {
588   async fn execute(
589    &self,
590    sender: Box<dyn Fn(Vec<u8>) + Send>,
591    tripwire: Option<::turbocharger::stream_cancel::Tripwire>,
592    _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo>
593   ) {
594    #executebody
595   }
596   fn txid(&self) -> i64 {
597    self.txid
598   }
599  }
600
601  #wasm_side
602  #js_side
603
604  #[cfg(not(target_arch = "wasm32"))]
605  #[allow(non_snake_case)]
606  async fn #remote_fn_ident(peer: &str, #orig_fn_params) -> #serialize_ret_ty {
607   let tx = ::turbocharger::_Transaction::new();
608   let req = ::turbocharger::bincode::serialize(&#req {
609    typetag_const_one: 1,
610    dispatch_name: #orig_fn_string,
611    txid: tx.txid,
612    params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
613   })
614   .unwrap();
615   tx.send_udp(peer, req).await;
616   let response = tx.resp().await;
617   let #resp { result, .. } =
618    ::turbocharger::bincode::deserialize(&response).unwrap();
619   result
620  }
621
622  #[allow(non_camel_case_types)]
623  #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
624  #[serde(crate = "::turbocharger::serde")]
625  struct #req {
626   typetag_const_one: i64,
627   dispatch_name: &'static str,
628   txid: i64,
629   params: (#( #orig_fn_param_tys ),* #orig_fn_params_maybe_comma),
630  }
631
632  #[allow(non_camel_case_types)]
633  #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
634  #[serde(crate = "::turbocharger::serde")]
635  struct #dispatch {
636   txid: i64,
637   params: (#( #orig_fn_param_tys ),* #orig_fn_params_maybe_comma),
638  }
639
640  #[allow(non_camel_case_types)]
641  #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
642  #[serde(crate = "::turbocharger::serde")]
643  struct #resp {
644   txid: i64,
645   result: #serialize_ret_ty,
646  }
647 };
648
649 #[cfg(feature = "debug_expansions")]
650 {
651  std::fs::create_dir_all("debug_expansions").ok();
652  std::fs::write(
653   project_root_path_with(format!("debug_expansions/{}.rs", orig_fn_string)),
654   prettyplease::unparse(&parse_quote!( #output )),
655  )
656  .unwrap();
657
658  std::process::Command::new("rustfmt")
659   .current_dir(project_root_path_with("debug_expansions"))
660   .arg("--edition")
661   .arg("2021")
662   .arg("--config")
663   .arg("tab_spaces=1")
664   .arg(format!("{}.rs", orig_fn_string))
665   .output()
666   .unwrap();
667 }
668
669 output
670}
671
672fn read_backend_api_rs() -> syn::File {
673 syn::parse_file(&std::fs::read_to_string(backend_api_rs_path()).unwrap_or_default()).unwrap()
674}
675
676fn write_backend_api_rs(file: syn::File) {
677 let mut output = "// This file is auto-generated by Turbocharger.\n// Check it into version control to track API changes over time.\n// To regenerate: \"cargo clean && rm backend_api.rs && cargo check\"\n".to_string();
678 let mut items = file.items;
679 items.sort_by_key(|item| match item {
680  syn::Item::Struct(s) => s.ident.to_string().to_lowercase(),
681  syn::Item::Fn(f) => f.sig.ident.to_string().to_lowercase(),
682  _ => unreachable!(),
683 });
684 for item in items {
685  output.push('\n');
686  output.push_str(&prettyplease::unparse(&parse_quote!( #item )));
687 }
688 if output != std::fs::read_to_string(backend_api_rs_path()).unwrap_or_default() {
689  std::fs::write(backend_api_rs_path(), output).unwrap();
690 }
691}
692
693fn backend_api_rs_path() -> std::path::PathBuf {
694 project_root_path_with("backend_api.rs")
695}
696
697fn project_root_path_with<P: AsRef<std::path::Path>>(pushpath: P) -> std::path::PathBuf {
698 let mut path = std::path::PathBuf::from(env!("OUT_DIR"));
699 while path.file_name() != Some(std::ffi::OsStr::new("target")) {
700  path.pop();
701 }
702 path.pop();
703 path.push(pushpath);
704 path
705}