1 | use proc_macro2::TokenStream; |
2 | use quote::{format_ident, quote}; |
3 | use std::collections::BTreeMap; |
4 | use syn::{ |
5 | parse_quote, punctuated::Punctuated, spanned::Spanned, AngleBracketedGenericArguments, |
6 | AttributeArgs, Error, FnArg, GenericArgument, ImplItem, ItemImpl, Lit::Str, Meta, |
7 | Meta::NameValue, MetaList, MetaNameValue, NestedMeta, PatType, PathArguments, ReturnType, |
8 | Signature, Token, Type, TypePath, |
9 | }; |
10 | use zvariant_utils::{case, def_attrs}; |
11 | |
12 | use crate::utils::*; |
13 | |
14 | // FIXME: The list name should once be "zbus" instead of "dbus_interface" (like in serde). |
15 | def_attrs! { |
16 | crate dbus_interface; |
17 | |
18 | pub TraitAttributes("trait" ) { |
19 | interface str, |
20 | name str |
21 | }; |
22 | |
23 | pub MethodAttributes("method" ) { |
24 | name str, |
25 | signal none, |
26 | property none, |
27 | out_args [str] |
28 | }; |
29 | } |
30 | |
31 | mod arg_attrs { |
32 | use zvariant_utils::def_attrs; |
33 | |
34 | def_attrs! { |
35 | crate zbus; |
36 | |
37 | pub ArgAttributes("argument" ) { |
38 | object_server none, |
39 | connection none, |
40 | header none, |
41 | signal_context none |
42 | }; |
43 | } |
44 | } |
45 | |
46 | use arg_attrs::ArgAttributes; |
47 | |
48 | #[derive (Debug)] |
49 | struct Property<'a> { |
50 | read: bool, |
51 | write: bool, |
52 | ty: Option<&'a Type>, |
53 | doc_comments: TokenStream, |
54 | } |
55 | |
56 | impl<'a> Property<'a> { |
57 | fn new() -> Self { |
58 | Self { |
59 | read: false, |
60 | write: false, |
61 | ty: None, |
62 | doc_comments: quote!(), |
63 | } |
64 | } |
65 | } |
66 | |
67 | pub fn expand(args: AttributeArgs, mut input: ItemImpl) -> syn::Result<TokenStream> { |
68 | let zbus = zbus_path(); |
69 | |
70 | let self_ty = &input.self_ty; |
71 | let mut properties = BTreeMap::new(); |
72 | let mut set_dispatch = quote!(); |
73 | let mut set_mut_dispatch = quote!(); |
74 | let mut get_dispatch = quote!(); |
75 | let mut get_all = quote!(); |
76 | let mut call_dispatch = quote!(); |
77 | let mut call_mut_dispatch = quote!(); |
78 | let mut introspect = quote!(); |
79 | let mut generated_signals = quote!(); |
80 | |
81 | // the impl Type |
82 | let ty = match input.self_ty.as_ref() { |
83 | Type::Path(p) => { |
84 | &p.path |
85 | .segments |
86 | .last() |
87 | .ok_or_else(|| Error::new_spanned(p, "Unsupported 'impl' type" ))? |
88 | .ident |
89 | } |
90 | _ => return Err(Error::new_spanned(&input.self_ty, "Invalid type" )), |
91 | }; |
92 | |
93 | let iface_name = |
94 | { |
95 | let TraitAttributes { name, interface } = TraitAttributes::parse_nested_metas(&args)?; |
96 | |
97 | match (name, interface) { |
98 | (Some(name), None) | (None, Some(name)) => name, |
99 | (None, None) => format!("org.freedesktop. {ty}" ), |
100 | (Some(_), Some(_)) => return Err(syn::Error::new( |
101 | input.span(), |
102 | "`name` and `interface` attributes should not be specified at the same time" , |
103 | )), |
104 | } |
105 | }; |
106 | |
107 | for method in &mut input.items { |
108 | let method = match method { |
109 | ImplItem::Method(m) => m, |
110 | _ => continue, |
111 | }; |
112 | |
113 | let is_async = method.sig.asyncness.is_some(); |
114 | |
115 | let Signature { |
116 | ident, |
117 | inputs, |
118 | output, |
119 | .. |
120 | } = &mut method.sig; |
121 | |
122 | let attrs = MethodAttributes::parse(&method.attrs)?; |
123 | method |
124 | .attrs |
125 | .retain(|attr| !attr.path.is_ident("dbus_interface" )); |
126 | |
127 | let docs = get_doc_attrs(&method.attrs) |
128 | .iter() |
129 | .filter_map(|attr| { |
130 | if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { |
131 | Some(s.value()) |
132 | } else { |
133 | // non #[doc = "..."] attributes are not our concern |
134 | // we leave them for rustc to handle |
135 | None |
136 | } |
137 | }) |
138 | .collect(); |
139 | |
140 | let doc_comments = to_xml_docs(docs); |
141 | let is_property = attrs.property; |
142 | let is_signal = attrs.signal; |
143 | let out_args = attrs.out_args.as_deref(); |
144 | assert!(!is_property || !is_signal); |
145 | |
146 | let has_inputs = inputs.len() > 1; |
147 | |
148 | let is_mut = if let FnArg::Receiver(r) = inputs |
149 | .first() |
150 | .ok_or_else(|| Error::new_spanned(&ident, "not &self method" ))? |
151 | { |
152 | r.mutability.is_some() |
153 | } else if is_signal { |
154 | false |
155 | } else { |
156 | return Err(Error::new_spanned(&method, "missing receiver" )); |
157 | }; |
158 | if is_signal && !is_async { |
159 | return Err(Error::new_spanned(&method, "signals must be async" )); |
160 | } |
161 | let method_await = if is_async { |
162 | quote! { .await } |
163 | } else { |
164 | quote! {} |
165 | }; |
166 | |
167 | let handle_fallible_property = quote! { .map(|e| <#zbus::zvariant::Value as ::std::convert::From<_>>::from(e).to_owned()) }; |
168 | |
169 | let mut typed_inputs = inputs |
170 | .iter() |
171 | .filter_map(typed_arg) |
172 | .cloned() |
173 | .collect::<Vec<_>>(); |
174 | let signal_context_arg = if is_signal { |
175 | if typed_inputs.is_empty() { |
176 | return Err(Error::new_spanned( |
177 | &inputs, |
178 | "Expected a `&zbus::SignalContext<'_> argument" , |
179 | )); |
180 | } |
181 | Some(typed_inputs.remove(0)) |
182 | } else { |
183 | None |
184 | }; |
185 | |
186 | let mut intro_args = quote!(); |
187 | intro_args.extend(introspect_input_args(&typed_inputs, is_signal)); |
188 | let is_result_output = introspect_add_output_args(&mut intro_args, output, out_args)?; |
189 | |
190 | let (args_from_msg, args_names) = get_args_from_inputs(&typed_inputs, &zbus)?; |
191 | |
192 | clean_input_args(inputs); |
193 | |
194 | let reply = if is_result_output { |
195 | let ret = quote!(r); |
196 | |
197 | quote!(match reply { |
198 | ::std::result::Result::Ok(r) => c.reply(m, &#ret).await, |
199 | ::std::result::Result::Err(e) => { |
200 | let hdr = m.header()?; |
201 | c.reply_dbus_error(&hdr, e).await |
202 | } |
203 | }) |
204 | } else { |
205 | quote!(c.reply(m, &reply).await) |
206 | }; |
207 | |
208 | let member_name = attrs.name.clone().unwrap_or_else(|| { |
209 | let mut name = ident.to_string(); |
210 | if is_property && has_inputs { |
211 | assert!(name.starts_with("set_" )); |
212 | name = name[4..].to_string(); |
213 | } |
214 | pascal_case(&name) |
215 | }); |
216 | |
217 | if is_signal { |
218 | introspect.extend(doc_comments); |
219 | introspect.extend(introspect_signal(&member_name, &intro_args)); |
220 | let signal_context = signal_context_arg.unwrap().pat; |
221 | |
222 | method.block = parse_quote!({ |
223 | #signal_context.connection().emit_signal( |
224 | #signal_context.destination(), |
225 | #signal_context.path(), |
226 | <#self_ty as #zbus::Interface>::name(), |
227 | #member_name, |
228 | &(#args_names), |
229 | ) |
230 | .await |
231 | }); |
232 | } else if is_property { |
233 | let p = properties.entry(member_name.to_string()); |
234 | |
235 | let sk_member_name = case::snake_case(&member_name); |
236 | let prop_changed_method_name = format_ident!(" {sk_member_name}_changed" ); |
237 | let prop_invalidate_method_name = format_ident!(" {sk_member_name}_invalidate" ); |
238 | |
239 | let p = p.or_insert_with(Property::new); |
240 | p.doc_comments.extend(doc_comments); |
241 | if has_inputs { |
242 | p.write = true; |
243 | |
244 | let set_call = if is_result_output { |
245 | quote!(self.#ident(val)#method_await) |
246 | } else if is_async { |
247 | quote!( |
248 | #zbus::export::futures_util::future::FutureExt::map( |
249 | self.#ident(val), |
250 | ::std::result::Result::Ok, |
251 | ) |
252 | .await |
253 | ) |
254 | } else { |
255 | quote!(::std::result::Result::Ok(self.#ident(val))) |
256 | }; |
257 | |
258 | // * For reference arg, we convert from `&Value` (so `TryFrom<&Value<'_>>` is |
259 | // required). |
260 | // |
261 | // * For argument type with lifetimes, we convert from `Value` (so |
262 | // `TryFrom<Value<'_>>` is required). |
263 | // |
264 | // * For all other arg types, we convert the passed value to `OwnedValue` first and |
265 | // then pass it as `Value` (so `TryFrom<Value<'static>>` is required). |
266 | let value_to_owned = quote! { |
267 | ::zbus::zvariant::Value::from(zbus::zvariant::Value::to_owned(value)) |
268 | }; |
269 | let value_arg = match &*typed_inputs |
270 | .first() |
271 | .ok_or_else(|| Error::new_spanned(&inputs, "Expected a value argument" ))? |
272 | .ty |
273 | { |
274 | Type::Reference(_) => quote!(value), |
275 | Type::Path(path) => path |
276 | .path |
277 | .segments |
278 | .first() |
279 | .map(|segment| match &segment.arguments { |
280 | PathArguments::AngleBracketed(angled) => angled |
281 | .args |
282 | .first() |
283 | .filter(|arg| matches!(arg, GenericArgument::Lifetime(_))) |
284 | .map(|_| quote!(value.clone())) |
285 | .unwrap_or_else(|| value_to_owned.clone()), |
286 | _ => value_to_owned.clone(), |
287 | }) |
288 | .unwrap_or_else(|| value_to_owned.clone()), |
289 | _ => value_to_owned, |
290 | }; |
291 | let do_set = quote!({ |
292 | let value = #value_arg; |
293 | match ::std::convert::TryInto::try_into(value) { |
294 | ::std::result::Result::Ok(val) => { |
295 | match #set_call { |
296 | ::std::result::Result::Ok(set_result) => { |
297 | self |
298 | .#prop_changed_method_name(&signal_context) |
299 | .await |
300 | .map(|_| set_result) |
301 | .map_err(Into::into) |
302 | } |
303 | e => e, |
304 | } |
305 | } |
306 | ::std::result::Result::Err(e) => { |
307 | ::std::result::Result::Err( |
308 | ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))), |
309 | ) |
310 | } |
311 | } |
312 | }); |
313 | |
314 | if is_mut { |
315 | let q = quote!( |
316 | #member_name => { |
317 | ::std::option::Option::Some(#do_set) |
318 | } |
319 | ); |
320 | set_mut_dispatch.extend(q); |
321 | |
322 | let q = quote!( |
323 | #member_name => #zbus::DispatchResult::RequiresMut, |
324 | ); |
325 | set_dispatch.extend(q); |
326 | } else { |
327 | let q = quote!( |
328 | #member_name => { |
329 | #zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { |
330 | #do_set |
331 | })) |
332 | } |
333 | ); |
334 | set_dispatch.extend(q); |
335 | } |
336 | } else { |
337 | let is_fallible_property = is_result_output; |
338 | |
339 | p.ty = Some(get_property_type(output)?); |
340 | p.read = true; |
341 | let inner = if is_fallible_property { |
342 | quote!(self.#ident()#method_await#handle_fallible_property) |
343 | } else { |
344 | quote!(::std::result::Result::Ok( |
345 | ::std::convert::Into::into( |
346 | <#zbus::zvariant::Value as ::std::convert::From<_>>::from( |
347 | self.#ident()#method_await, |
348 | ), |
349 | ), |
350 | )) |
351 | }; |
352 | |
353 | let q = quote!( |
354 | #member_name => { |
355 | ::std::option::Option::Some(#inner) |
356 | }, |
357 | ); |
358 | get_dispatch.extend(q); |
359 | |
360 | let q = if is_fallible_property { |
361 | quote!(if let Ok(prop) = self.#ident()#method_await { |
362 | props.insert( |
363 | ::std::string::ToString::to_string(#member_name), |
364 | ::std::convert::Into::into( |
365 | <#zbus::zvariant::Value as ::std::convert::From<_>>::from( |
366 | prop, |
367 | ), |
368 | ), |
369 | ); |
370 | }) |
371 | } else { |
372 | quote!(props.insert( |
373 | ::std::string::ToString::to_string(#member_name), |
374 | ::std::convert::Into::into( |
375 | <#zbus::zvariant::Value as ::std::convert::From<_>>::from( |
376 | self.#ident()#method_await, |
377 | ), |
378 | ), |
379 | );) |
380 | }; |
381 | |
382 | get_all.extend(q); |
383 | |
384 | let prop_value_handled = if is_fallible_property { |
385 | quote!(self.#ident()#method_await?) |
386 | } else { |
387 | quote!(self.#ident()#method_await) |
388 | }; |
389 | |
390 | let prop_changed_method = quote!( |
391 | pub async fn #prop_changed_method_name( |
392 | &self, |
393 | signal_context: &#zbus::SignalContext<'_>, |
394 | ) -> #zbus::Result<()> { |
395 | let mut changed = ::std::collections::HashMap::new(); |
396 | let value = <#zbus::zvariant::Value as ::std::convert::From<_>>::from(#prop_value_handled); |
397 | changed.insert(#member_name, &value); |
398 | #zbus::fdo::Properties::properties_changed( |
399 | signal_context, |
400 | #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name), |
401 | &changed, |
402 | &[], |
403 | ).await |
404 | } |
405 | ); |
406 | generated_signals.extend(prop_changed_method); |
407 | |
408 | let prop_invalidate_method = quote!( |
409 | pub async fn #prop_invalidate_method_name( |
410 | &self, |
411 | signal_context: &#zbus::SignalContext<'_>, |
412 | ) -> #zbus::Result<()> { |
413 | #zbus::fdo::Properties::properties_changed( |
414 | signal_context, |
415 | #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name), |
416 | &::std::collections::HashMap::new(), |
417 | &[#member_name], |
418 | ).await |
419 | } |
420 | ); |
421 | generated_signals.extend(prop_invalidate_method); |
422 | } |
423 | } else { |
424 | introspect.extend(doc_comments); |
425 | introspect.extend(introspect_method(&member_name, &intro_args)); |
426 | |
427 | let m = quote! { |
428 | #member_name => { |
429 | let future = async move { |
430 | #args_from_msg |
431 | let reply = self.#ident(#args_names)#method_await; |
432 | #reply |
433 | }; |
434 | #zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { |
435 | future.await.map(|_seq: u32| ()) |
436 | })) |
437 | }, |
438 | }; |
439 | |
440 | if is_mut { |
441 | call_dispatch.extend(quote! { |
442 | #member_name => #zbus::DispatchResult::RequiresMut, |
443 | }); |
444 | call_mut_dispatch.extend(m); |
445 | } else { |
446 | call_dispatch.extend(m); |
447 | } |
448 | } |
449 | } |
450 | |
451 | introspect_properties(&mut introspect, properties)?; |
452 | |
453 | let generics = &input.generics; |
454 | let where_clause = &generics.where_clause; |
455 | |
456 | Ok(quote! { |
457 | #input |
458 | |
459 | impl #generics #self_ty |
460 | #where_clause |
461 | { |
462 | #generated_signals |
463 | } |
464 | |
465 | #[#zbus::export::async_trait::async_trait] |
466 | impl #generics #zbus::Interface for #self_ty |
467 | #where_clause |
468 | { |
469 | fn name() -> #zbus::names::InterfaceName<'static> { |
470 | #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name) |
471 | } |
472 | |
473 | async fn get( |
474 | &self, |
475 | property_name: &str, |
476 | ) -> ::std::option::Option<#zbus::fdo::Result<#zbus::zvariant::OwnedValue>> { |
477 | match property_name { |
478 | #get_dispatch |
479 | _ => ::std::option::Option::None, |
480 | } |
481 | } |
482 | |
483 | async fn get_all( |
484 | &self, |
485 | ) -> ::std::collections::HashMap< |
486 | ::std::string::String, |
487 | #zbus::zvariant::OwnedValue, |
488 | > { |
489 | let mut props: ::std::collections::HashMap< |
490 | ::std::string::String, |
491 | #zbus::zvariant::OwnedValue, |
492 | > = ::std::collections::HashMap::new(); |
493 | #get_all |
494 | props |
495 | } |
496 | |
497 | fn set<'call>( |
498 | &'call self, |
499 | property_name: &'call str, |
500 | value: &'call #zbus::zvariant::Value<'_>, |
501 | signal_context: &'call #zbus::SignalContext<'_>, |
502 | ) -> #zbus::DispatchResult<'call> { |
503 | match property_name { |
504 | #set_dispatch |
505 | _ => #zbus::DispatchResult::NotFound, |
506 | } |
507 | } |
508 | |
509 | async fn set_mut( |
510 | &mut self, |
511 | property_name: &str, |
512 | value: &#zbus::zvariant::Value<'_>, |
513 | signal_context: &#zbus::SignalContext<'_>, |
514 | ) -> ::std::option::Option<#zbus::fdo::Result<()>> { |
515 | match property_name { |
516 | #set_mut_dispatch |
517 | _ => ::std::option::Option::None, |
518 | } |
519 | } |
520 | |
521 | fn call<'call>( |
522 | &'call self, |
523 | s: &'call #zbus::ObjectServer, |
524 | c: &'call #zbus::Connection, |
525 | m: &'call #zbus::Message, |
526 | name: #zbus::names::MemberName<'call>, |
527 | ) -> #zbus::DispatchResult<'call> { |
528 | match name.as_str() { |
529 | #call_dispatch |
530 | _ => #zbus::DispatchResult::NotFound, |
531 | } |
532 | } |
533 | |
534 | fn call_mut<'call>( |
535 | &'call mut self, |
536 | s: &'call #zbus::ObjectServer, |
537 | c: &'call #zbus::Connection, |
538 | m: &'call #zbus::Message, |
539 | name: #zbus::names::MemberName<'call>, |
540 | ) -> #zbus::DispatchResult<'call> { |
541 | match name.as_str() { |
542 | #call_mut_dispatch |
543 | _ => #zbus::DispatchResult::NotFound, |
544 | } |
545 | } |
546 | |
547 | fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) { |
548 | ::std::writeln!( |
549 | writer, |
550 | r#"{:indent$}<interface name="{}">"# , |
551 | "" , |
552 | <Self as #zbus::Interface>::name(), |
553 | indent = level |
554 | ).unwrap(); |
555 | { |
556 | use #zbus::zvariant::Type; |
557 | |
558 | let level = level + 2; |
559 | #introspect |
560 | } |
561 | ::std::writeln!(writer, r#"{:indent$}</interface>"# , "" , indent = level).unwrap(); |
562 | } |
563 | } |
564 | }) |
565 | } |
566 | |
567 | fn get_args_from_inputs( |
568 | inputs: &[PatType], |
569 | zbus: &TokenStream, |
570 | ) -> syn::Result<(TokenStream, TokenStream)> { |
571 | if inputs.is_empty() { |
572 | Ok((quote!(), quote!())) |
573 | } else { |
574 | let mut server_arg_decl = None; |
575 | let mut conn_arg_decl = None; |
576 | let mut header_arg_decl = None; |
577 | let mut signal_context_arg_decl = None; |
578 | let mut args_names = Vec::new(); |
579 | let mut tys = Vec::new(); |
580 | |
581 | for input in inputs { |
582 | let attrs = ArgAttributes::parse(&input.attrs)?; |
583 | |
584 | if attrs.object_server { |
585 | if server_arg_decl.is_some() { |
586 | return Err(Error::new_spanned( |
587 | input, |
588 | "There can only be one object_server argument" , |
589 | )); |
590 | } |
591 | |
592 | let server_arg = &input.pat; |
593 | server_arg_decl = Some(quote! { let #server_arg = &s; }); |
594 | } else if attrs.connection { |
595 | if conn_arg_decl.is_some() { |
596 | return Err(Error::new_spanned( |
597 | input, |
598 | "There can only be one connection argument" , |
599 | )); |
600 | } |
601 | |
602 | let conn_arg = &input.pat; |
603 | conn_arg_decl = Some(quote! { let #conn_arg = &c; }); |
604 | } else if attrs.header { |
605 | if header_arg_decl.is_some() { |
606 | return Err(Error::new_spanned( |
607 | input, |
608 | "There can only be one header argument" , |
609 | )); |
610 | } |
611 | |
612 | let header_arg = &input.pat; |
613 | |
614 | header_arg_decl = Some(quote! { |
615 | let #header_arg = m.header()?; |
616 | }); |
617 | } else if attrs.signal_context { |
618 | if signal_context_arg_decl.is_some() { |
619 | return Err(Error::new_spanned( |
620 | input, |
621 | "There can only be one `signal_context` argument" , |
622 | )); |
623 | } |
624 | |
625 | let signal_context_arg = &input.pat; |
626 | |
627 | signal_context_arg_decl = Some(quote! { |
628 | let #signal_context_arg = match m.path() { |
629 | ::std::option::Option::Some(p) => { |
630 | #zbus::SignalContext::new(c, p).expect("Infallible conversion failed" ) |
631 | } |
632 | ::std::option::Option::None => { |
633 | let hdr = m.header()?; |
634 | let err = #zbus::fdo::Error::UnknownObject("Path Required" .into()); |
635 | return c.reply_dbus_error(&hdr, err).await; |
636 | } |
637 | }; |
638 | }); |
639 | } else { |
640 | args_names.push(pat_ident(input).unwrap()); |
641 | tys.push(&input.ty); |
642 | } |
643 | } |
644 | |
645 | let args_from_msg = quote! { |
646 | #server_arg_decl |
647 | |
648 | #conn_arg_decl |
649 | |
650 | #header_arg_decl |
651 | |
652 | #signal_context_arg_decl |
653 | |
654 | let (#(#args_names),*): (#(#tys),*) = |
655 | match m.body() { |
656 | ::std::result::Result::Ok(r) => r, |
657 | ::std::result::Result::Err(e) => { |
658 | let hdr = m.header()?; |
659 | let err = <#zbus::fdo::Error as ::std::convert::From<_>>::from(e); |
660 | return c.reply_dbus_error(&hdr, err).await; |
661 | } |
662 | }; |
663 | }; |
664 | |
665 | let all_args_names = inputs.iter().filter_map(pat_ident); |
666 | let all_args_names = quote! { #(#all_args_names,)* }; |
667 | |
668 | Ok((args_from_msg, all_args_names)) |
669 | } |
670 | } |
671 | |
672 | fn clean_input_args(inputs: &mut Punctuated<FnArg, Token![,]>) { |
673 | for input: &mut FnArg in inputs { |
674 | if let FnArg::Typed(t: &mut PatType) = input { |
675 | t.attrs.retain(|attr: &Attribute| !attr.path.is_ident("zbus" )); |
676 | } |
677 | } |
678 | } |
679 | |
680 | fn introspect_signal(name: &str, args: &TokenStream) -> TokenStream { |
681 | quote!( |
682 | ::std::writeln!(writer, "{:indent$}<signal name= \"{} \">" , "" , #name, indent = level).unwrap(); |
683 | { |
684 | let level = level + 2; |
685 | #args |
686 | } |
687 | ::std::writeln!(writer, "{:indent$}</signal>" , "" , indent = level).unwrap(); |
688 | ) |
689 | } |
690 | |
691 | fn introspect_method(name: &str, args: &TokenStream) -> TokenStream { |
692 | quote!( |
693 | ::std::writeln!(writer, "{:indent$}<method name= \"{} \">" , "" , #name, indent = level).unwrap(); |
694 | { |
695 | let level = level + 2; |
696 | #args |
697 | } |
698 | ::std::writeln!(writer, "{:indent$}</method>" , "" , indent = level).unwrap(); |
699 | ) |
700 | } |
701 | |
702 | fn introspect_input_args( |
703 | inputs: &[PatType], |
704 | is_signal: bool, |
705 | ) -> impl Iterator<Item = TokenStream> + '_ { |
706 | inputs |
707 | .iter() |
708 | .filter_map(move |pat_type @ PatType { ty, attrs, .. }| { |
709 | let is_special_arg = attrs.iter().any(|attr| { |
710 | if !attr.path.is_ident("zbus" ) { |
711 | return false; |
712 | } |
713 | |
714 | let meta = match attr.parse_meta() { |
715 | ::std::result::Result::Ok(meta) => meta, |
716 | ::std::result::Result::Err(_) => return false, |
717 | }; |
718 | |
719 | let nested = match meta { |
720 | Meta::List(MetaList { nested, .. }) => nested, |
721 | _ => return false, |
722 | }; |
723 | |
724 | let res = nested.iter().any(|nested_meta| { |
725 | matches!( |
726 | nested_meta, |
727 | NestedMeta::Meta(Meta::Path(path)) |
728 | if path.is_ident("object_server" ) || path.is_ident("connection" ) || path.is_ident("header" ) || path.is_ident("signal_context" ) |
729 | ) |
730 | }); |
731 | |
732 | res |
733 | }); |
734 | if is_special_arg { |
735 | return None; |
736 | } |
737 | |
738 | let ident = pat_ident(pat_type).unwrap(); |
739 | let arg_name = quote!(#ident).to_string(); |
740 | let dir = if is_signal { "" } else { " direction= \"in \"" }; |
741 | Some(quote!( |
742 | ::std::writeln!(writer, "{:indent$}<arg name= \"{} \" type= \"{} \"{}/>" , "" , |
743 | #arg_name, <#ty>::signature(), #dir, indent = level).unwrap(); |
744 | )) |
745 | }) |
746 | } |
747 | |
748 | fn introspect_output_arg(ty: &Type, arg_name: Option<&String>) -> TokenStream { |
749 | let arg_name: String = match arg_name { |
750 | Some(name: &String) => format!("name= \"{name}\" " ), |
751 | None => String::from("" ), |
752 | }; |
753 | |
754 | quote!( |
755 | ::std::writeln!(writer, "{:indent$}<arg {}type= \"{} \" direction= \"out \"/>" , "" , |
756 | #arg_name, <#ty>::signature(), indent = level).unwrap(); |
757 | ) |
758 | } |
759 | |
760 | fn get_result_type(p: &TypePath) -> syn::Result<&Type> { |
761 | if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args: &Punctuated, .. }) = &p&PathSegment |
762 | .path |
763 | .segments |
764 | .last() |
765 | .ok_or_else(|| Error::new_spanned(tokens:p, message:"unsupported result type" ))? |
766 | .arguments |
767 | { |
768 | if let Some(syn::GenericArgument::Type(ty: &Type)) = args.first() { |
769 | return Ok(ty); |
770 | } |
771 | } |
772 | |
773 | Err(Error::new_spanned(tokens:p, message:"unhandled Result return" )) |
774 | } |
775 | |
776 | fn introspect_add_output_args( |
777 | args: &mut TokenStream, |
778 | output: &ReturnType, |
779 | arg_names: Option<&[String]>, |
780 | ) -> syn::Result<bool> { |
781 | let mut is_result_output = false; |
782 | |
783 | if let ReturnType::Type(_, ty) = output { |
784 | let mut ty = ty.as_ref(); |
785 | |
786 | if let Type::Path(p) = ty { |
787 | is_result_output = p |
788 | .path |
789 | .segments |
790 | .last() |
791 | .ok_or_else(|| Error::new_spanned(ty, "unsupported output type" ))? |
792 | .ident |
793 | == "Result" ; |
794 | if is_result_output { |
795 | ty = get_result_type(p)?; |
796 | } |
797 | } |
798 | |
799 | if let Type::Tuple(t) = ty { |
800 | if let Some(arg_names) = arg_names { |
801 | if t.elems.len() != arg_names.len() { |
802 | // Turn into error |
803 | panic!("Number of out arg names different from out args specified" ) |
804 | } |
805 | } |
806 | for i in 0..t.elems.len() { |
807 | let name = arg_names.map(|names| &names[i]); |
808 | args.extend(introspect_output_arg(&t.elems[i], name)); |
809 | } |
810 | } else { |
811 | args.extend(introspect_output_arg(ty, None)); |
812 | } |
813 | } |
814 | |
815 | Ok(is_result_output) |
816 | } |
817 | |
818 | fn get_property_type(output: &ReturnType) -> syn::Result<&Type> { |
819 | if let ReturnType::Type(_, ty: &Box) = output { |
820 | let ty: &Type = ty.as_ref(); |
821 | |
822 | if let Type::Path(p: &TypePath) = ty { |
823 | let is_result_output: bool = p&PathSegment |
824 | .path |
825 | .segments |
826 | .last() |
827 | .ok_or_else(|| Error::new_spanned(tokens:ty, message:"unsupported property type" ))? |
828 | .ident |
829 | == "Result" ; |
830 | if is_result_output { |
831 | return get_result_type(p); |
832 | } |
833 | } |
834 | |
835 | Ok(ty) |
836 | } else { |
837 | Err(Error::new_spanned(tokens:output, message:"Invalid property getter" )) |
838 | } |
839 | } |
840 | |
841 | fn introspect_properties( |
842 | introspection: &mut TokenStream, |
843 | properties: BTreeMap<String, Property<'_>>, |
844 | ) -> syn::Result<()> { |
845 | for (name, prop) in properties { |
846 | let access = if prop.read && prop.write { |
847 | "readwrite" |
848 | } else if prop.read { |
849 | "read" |
850 | } else if prop.write { |
851 | "write" |
852 | } else { |
853 | return Err(Error::new_spanned( |
854 | name, |
855 | "property is neither readable nor writable" , |
856 | )); |
857 | }; |
858 | let ty = prop.ty.ok_or_else(|| { |
859 | Error::new_spanned(&name, "Write-only properties aren't supported yet" ) |
860 | })?; |
861 | |
862 | let doc_comments = prop.doc_comments; |
863 | introspection.extend(quote!( |
864 | #doc_comments |
865 | ::std::writeln!( |
866 | writer, |
867 | "{:indent$}<property name= \"{} \" type= \"{} \" access= \"{} \"/>" , |
868 | "" , #name, <#ty>::signature(), #access, indent = level, |
869 | ).unwrap(); |
870 | )); |
871 | } |
872 | |
873 | Ok(()) |
874 | } |
875 | |
876 | pub fn to_xml_docs(lines: Vec<String>) -> TokenStream { |
877 | let mut docs = quote!(); |
878 | |
879 | let mut lines: Vec<&str> = lines |
880 | .iter() |
881 | .skip_while(|s| is_blank(s)) |
882 | .flat_map(|s| s.split(' \n' )) |
883 | .collect(); |
884 | |
885 | while let Some(true) = lines.last().map(|s| is_blank(s)) { |
886 | lines.pop(); |
887 | } |
888 | |
889 | if lines.is_empty() { |
890 | return docs; |
891 | } |
892 | |
893 | docs.extend(quote!(::std::writeln!(writer, "{:indent$}<!--" , "" , indent = level).unwrap();)); |
894 | for line in lines { |
895 | if !line.is_empty() { |
896 | docs.extend( |
897 | quote!(::std::writeln!(writer, "{:indent$}{}" , "" , #line, indent = level).unwrap();), |
898 | ); |
899 | } else { |
900 | docs.extend(quote!(::std::writeln!(writer, "" ).unwrap();)); |
901 | } |
902 | } |
903 | docs.extend(quote!(::std::writeln!(writer, "{:indent$} -->" , "" , indent = level).unwrap();)); |
904 | |
905 | docs |
906 | } |
907 | |