diff --git a/varlink_generator/src/lib.rs b/varlink_generator/src/lib.rs index 80614a6..b552164 100644 --- a/varlink_generator/src/lib.rs +++ b/varlink_generator/src/lib.rs @@ -41,7 +41,7 @@ use std::process::{exit, Command}; use std::str::FromStr; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::quote; use varlink_parser::{Typedef, VEnum, VError, VStruct, VStructOrEnum, VType, VTypeExt, IDL}; @@ -169,6 +169,31 @@ fn to_snake_case(mut str: &str) -> String { words.join("_") } +// Build a valid Rust identifier for a varlink field/variant/type name. Names +// that collide with Rust keywords are normally emitted as raw identifiers +// (`r#type`); serde transparently strips the `r#`, so the on-the-wire name is +// unchanged. The four keywords that cannot be raw identifiers +// (`self`, `Self`, `crate`, `super`) are instead suffixed with `_`. Callers that +// emit a *serializable* field/variant must pair this with keyword_serde_rename() +// so the wire name is preserved despite the mangled Rust identifier. +fn safe_ident(name: &str) -> Ident { + match name { + "self" | "Self" | "crate" | "super" => Ident::new(&format!("{name}_"), Span::call_site()), + _ => syn::parse_str(&(String::from("r#") + name)).unwrap(), + } +} + +// Companion to safe_ident(): for the four keywords mangled with a trailing `_`, +// return a `#[serde(rename = "...")]` attribute that restores the original wire +// name. Returns an empty token stream for every other name (raw identifiers need +// no rename, since serde already ignores the `r#` prefix). +fn keyword_serde_rename(name: &str) -> TokenStream { + match name { + "self" | "Self" | "crate" | "super" => quote!(#[serde(rename = #name)]), + _ => quote!(), + } +} + impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VStruct<'long> { fn to_tokenstream( &'long self, @@ -176,12 +201,24 @@ impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VStruct<'long> { tokenstream: &mut TokenStream, options: &'long GeneratorOptions, ) { - let tname: Ident = format_ident!("r#{}", name); + let tname: Ident = safe_ident(name); let mut enames = vec![]; let mut etypes = vec![]; + let mut anot = vec![]; for e in &self.elts { - let ename_ident: Ident = syn::parse_str(&(String::from("r#") + e.name)).unwrap(); + // Omit null optionals on the wire, matching how method argument and + // reply structs are emitted. Without this, a minimally-populated + // named type (e.g. systemd's UnitContext) serializes every absent + // field as `null`, which servers may reject as an unsettable property. + let skip = if let VTypeExt::Option(_) = e.vtype { + quote!(#[serde(skip_serializing_if = "Option::is_none")]) + } else { + quote!() + }; + let rename = keyword_serde_rename(e.name); + anot.push(quote!(#skip #rename)); + let ename_ident: Ident = safe_ident(e.name); enames.push(ename_ident); etypes.push( TokenStream::from_str( @@ -197,9 +234,9 @@ impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VStruct<'long> { ); } tokenstream.extend(quote!( - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct #tname { - #(pub #enames: #etypes,)* + #(#anot pub #enames: #etypes,)* } )); } @@ -212,18 +249,26 @@ impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VEnum<'long> { tokenstream: &mut TokenStream, _options: &'long GeneratorOptions, ) { - let tname: Ident = syn::parse_str(&(String::from("r#") + name)).unwrap(); + let tname: Ident = safe_ident(name); let mut enames = vec![]; - - for elt in &self.elts { - let ename_ident: Ident = syn::parse_str(&(String::from("r#") + elt)).unwrap(); + let mut anot = vec![]; + + for (i, elt) in self.elts.iter().enumerate() { + // derive(Default) on an enum requires exactly one #[default] variant; + // pick the first. This only affects Enum::default() — used when a + // struct that holds this enum by value is default-constructed — and + // never the wire encoding. + let default_attr = if i == 0 { quote!(#[default]) } else { quote!() }; + let rename = keyword_serde_rename(elt); + anot.push(quote!(#default_attr #rename)); + let ename_ident: Ident = safe_ident(elt); enames.push(ename_ident); } tokenstream.extend(quote!( - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum #tname { - #(#enames, )* + #(#anot #enames, )* } )); } @@ -256,12 +301,14 @@ impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VError<'long> { let mut args_anot = vec![]; for e in &self.parm.elts { - args_anot.push(if let VTypeExt::Option(_) = e.vtype { + let skip = if let VTypeExt::Option(_) = e.vtype { quote!(#[serde(skip_serializing_if = "Option::is_none")]) } else { quote!() - }); - let ename_ident: Ident = syn::parse_str(&(String::from("r#") + e.name)).unwrap(); + }; + let rename = keyword_serde_rename(e.name); + args_anot.push(quote!(#skip #rename)); + let ename_ident: Ident = safe_ident(e.name); args_enames.push(ename_ident); args_etypes.push( TokenStream::from_str( @@ -277,7 +324,7 @@ impl<'short, 'long: 'short> ToTokenStream<'short, 'long> for VError<'long> { ); } tokenstream.extend(quote!( - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct #args_name { #(#args_anot pub #args_enames: #args_etypes,)* } @@ -379,14 +426,14 @@ fn varlink_to_rust(idl: &IDL, options: &GeneratorOptions, tosource: bool) -> Res let in_field_types = in_field_types.iter(); ts.extend(quote!( - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct #out_struct_name { #(#out_anot pub #out_field_names: #out_field_types,)* } impl varlink::VarlinkReply for #out_struct_name {} - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct #in_struct_name { #(#in_anot pub #in_field_names: #in_field_types,)* } @@ -570,7 +617,7 @@ fn varlink_to_rust(idl: &IDL, options: &GeneratorOptions, tosource: bool) -> Res let mut in_field_names = Vec::new(); for e in &t.input.elts { - let ename_ident: Ident = syn::parse_str(&(String::from("r#") + e.name)).unwrap(); + let ename_ident: Ident = safe_ident(e.name); in_field_names.push(ename_ident); } @@ -843,12 +890,14 @@ fn generate_anon_struct( anot: &mut Vec, ) { for e in &vstruct.elts { - anot.push(if let VTypeExt::Option(_) = e.vtype { + let skip = if let VTypeExt::Option(_) = e.vtype { quote!(#[serde(skip_serializing_if = "Option::is_none")]) } else { quote!() - }); - let ename_ident: Ident = syn::parse_str(&(String::from("r#") + e.name)).unwrap(); + }; + let rename = keyword_serde_rename(e.name); + anot.push(quote!(#skip #rename)); + let ename_ident: Ident = safe_ident(e.name); field_names.push(ename_ident); field_types.push( TokenStream::from_str( @@ -1037,8 +1086,7 @@ fn generate_error_code( let args_name = Ident::new(&format!("{}_Args", t.name), Span::call_site()); if !t.parm.elts.is_empty() { for e in &t.parm.elts { - let ename_ident: Ident = - syn::parse_str(&(String::from("r#") + e.name)).unwrap(); + let ename_ident: Ident = safe_ident(e.name); inparms_name.push(ename_ident); inparms_type.push( TokenStream::from_str( diff --git a/varlink_generator/tests/org.example.complex.rs_out b/varlink_generator/tests/org.example.complex.rs_out index c60c398..d9c1d99 100644 --- a/varlink_generator/tests/org.example.complex.rs_out +++ b/varlink_generator/tests/org.example.complex.rs_out @@ -103,7 +103,8 @@ impl From<&varlink::Reply> for ErrorKind { #[allow(unused_variables)] fn from(e: &varlink::Reply) -> Self { match e { - varlink::Reply { error: Some(t), .. } if t == "org.example.complex.ErrorBar" => match e { + varlink::Reply { error: Some(t), .. } if t == "org.example.complex.ErrorBar" => match e + { varlink::Reply { parameters: Some(p), .. @@ -113,7 +114,8 @@ impl From<&varlink::Reply> for ErrorKind { }, _ => ErrorKind::ErrorBar(None), }, - varlink::Reply { error: Some(t), .. } if t == "org.example.complex.ErrorFoo" => match e { + varlink::Reply { error: Some(t), .. } if t == "org.example.complex.ErrorFoo" => match e + { varlink::Reply { parameters: Some(p), .. @@ -127,14 +129,15 @@ impl From<&varlink::Reply> for ErrorKind { } } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#ErrorFoo_Args_enum { pub r#b: bool, pub r#c: i64, pub r#interface: Interface, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#ErrorFoo_Args_bar { + #[default] r#type, r#enum, r#int, @@ -170,69 +173,102 @@ pub trait VarlinkCallError: varlink::CallTrait { } } impl VarlinkCallError for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#Enum { + #[default] r#enum, r#b, r#c, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#Interface { + #[default] r#interface, r#b, r#c, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct r#Reserved { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "self")] + pub self_: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "Self")] + pub Self_: Option, + #[serde(rename = "crate")] + pub crate_: String, + #[serde(rename = "super")] + pub super_: bool, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub enum r#ReservedEnum { + #[default] + #[serde(rename = "self")] + self_, + #[serde(rename = "Self")] + Self_, + #[serde(rename = "crate")] + crate_, + #[serde(rename = "super")] + super_, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#Type { + #[default] r#type, r#b, r#c, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#TypeEnum { + #[default] r#type, r#b, r#c, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#TypeFoo_enum { + #[default] r#foo, r#bar, r#baz, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#TypeFoo_anon_baz { pub r#a: i64, pub r#b: i64, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#TypeFoo_anon { pub r#foo: bool, pub r#bar: i64, pub r#baz: Vec, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#TypeFoo { pub r#bool: bool, pub r#int: i64, pub r#float: f64, pub r#string: String, + #[serde(skip_serializing_if = "Option::is_none")] pub r#enum: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub r#type: Option, pub r#anon: TypeFoo_anon, pub r#object: serde_json::Value, pub r#stringset: varlink::StringHashSet, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct ErrorBar_Args {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#ErrorFoo_Args_enum { pub r#b: bool, pub r#c: i64, pub r#interface: Interface, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub enum r#ErrorFoo_Args_bar { + #[default] r#type, r#enum, r#int, @@ -241,17 +277,17 @@ pub enum r#ErrorFoo_Args_bar { r#if, r#let, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct ErrorFoo_Args { pub r#enum: ErrorFoo_Args_enum, pub r#foo: TypeFoo, pub r#bar: ErrorFoo_Args_bar, pub r#interface: Interface, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Bar_Reply {} impl varlink::VarlinkReply for Bar_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Bar_Args {} #[allow(dead_code)] pub trait Call_Bar: VarlinkCallError { @@ -260,24 +296,45 @@ pub trait Call_Bar: VarlinkCallError { } } impl Call_Bar for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct Baz_Reply { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "self")] + pub self_: Option, +} +impl varlink::VarlinkReply for Baz_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct Baz_Args { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "self")] + pub self_: Option, + pub r#type: ReservedEnum, +} +#[allow(dead_code)] +pub trait Call_Baz: VarlinkCallError { + fn reply(&mut self, self_: Option) -> varlink::Result<()> { + self.reply_struct(Baz_Reply { self_ }.into()) + } +} +impl Call_Baz for varlink::Call<'_> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#Foo_Args_enum { pub r#b: bool, pub r#c: i64, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#Foo_Reply_a { pub r#b: bool, pub r#c: i64, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Foo_Reply { pub r#a: Vec, pub r#foo: TypeFoo, pub r#interface: Interface, } impl varlink::VarlinkReply for Foo_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Foo_Args { pub r#enum: Foo_Args_enum, pub r#foo: TypeFoo, @@ -305,6 +362,12 @@ impl Call_Foo for varlink::Call<'_> {} #[allow(dead_code)] pub trait VarlinkInterface { fn bar(&self, call: &mut dyn Call_Bar) -> varlink::Result<()>; + fn baz( + &self, + call: &mut dyn Call_Baz, + self_: Option, + r#type: ReservedEnum, + ) -> varlink::Result<()>; fn foo( &self, call: &mut dyn Call_Foo, @@ -323,6 +386,11 @@ pub trait VarlinkInterface { #[allow(dead_code)] pub trait VarlinkClientInterface { fn bar(&mut self) -> varlink::MethodCall; + fn baz( + &mut self, + self_: Option, + r#type: ReservedEnum, + ) -> varlink::MethodCall; fn foo( &mut self, r#enum: Foo_Args_enum, @@ -348,6 +416,17 @@ impl VarlinkClientInterface for VarlinkClient { Bar_Args {}, ) } + fn baz( + &mut self, + self_: Option, + r#type: ReservedEnum, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "org.example.complex.Baz", + Baz_Args { self_, r#type }, + ) + } fn foo( &mut self, r#enum: Foo_Args_enum, @@ -375,7 +454,7 @@ pub fn new(inner: Box) -> VarlinkInterfacePr } impl varlink::Interface for VarlinkInterfaceProxy { fn get_description(&self) -> &'static str { - "interface org.example.complex\n\ntype Enum (enum, b, c)\n\ntype Type (type, b, c)\n\ntype TypeEnum (type, b, c)\n\ntype Interface (interface, b, c)\n\ntype TypeFoo (\n bool: bool,\n int: int,\n float: float,\n string: string,\n enum: ?[string]?(foo, bar, baz),\n type: ?TypeEnum,\n anon: (\n foo: bool,\n bar: int,\n baz: [](a: int, b: int)\n ),\n object: object,\n stringset: [string]()\n)\n\nmethod Foo(\n enum: (b: bool, c: int),\n foo: TypeFoo,\n interface: Interface\n) -> (\n a: [](b: bool, c: int),\n foo: TypeFoo,\n interface: Interface\n)\n\nmethod Bar() -> ()\n\nerror ErrorFoo (\n enum: (\n b: bool,\n c: int,\n interface: Interface\n ),\n foo: TypeFoo,\n bar: (type, enum, int, bool, string, if, let),\n interface: Interface\n)\n\nerror ErrorBar ()\n" + "interface org.example.complex\n\ntype Enum (enum, b, c)\n\ntype Type (type, b, c)\n\ntype TypeEnum (type, b, c)\n\ntype Interface (interface, b, c)\n\ntype ReservedEnum (self, Self, crate, super)\n\ntype Reserved (\n self: ?string,\n Self: ?int,\n crate: string,\n super: bool\n)\n\ntype TypeFoo (\n bool: bool,\n int: int,\n float: float,\n string: string,\n enum: ?[string]?(foo, bar, baz),\n type: ?TypeEnum,\n anon: (\n foo: bool,\n bar: int,\n baz: [](a: int, b: int)\n ),\n object: object,\n stringset: [string]()\n)\n\nmethod Foo(\n enum: (b: bool, c: int),\n foo: TypeFoo,\n interface: Interface\n) -> (\n a: [](b: bool, c: int),\n foo: TypeFoo,\n interface: Interface\n)\n\nmethod Bar() -> ()\n\nmethod Baz(\n self: ?Reserved,\n type: ReservedEnum\n) -> (\n self: ?Reserved\n)\n\nerror ErrorFoo (\n enum: (\n b: bool,\n c: int,\n interface: Interface\n ),\n foo: TypeFoo,\n bar: (type, enum, int, bool, string, if, let),\n interface: Interface\n)\n\nerror ErrorBar ()\n" } fn get_name(&self) -> &'static str { "org.example.complex" @@ -391,6 +470,22 @@ impl varlink::Interface for VarlinkInterfaceProxy { let req = call.request.unwrap(); match req.method.as_ref() { "org.example.complex.Bar" => self.inner.bar(call as &mut dyn Call_Bar), + "org.example.complex.Baz" => { + if let Some(args) = req.parameters.clone() { + let args: Baz_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .baz(call as &mut dyn Call_Baz, args.self_, args.r#type) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } "org.example.complex.Foo" => { if let Some(args) = req.parameters.clone() { let args: Foo_Args = match serde_json::from_value(args) { diff --git a/varlink_generator/tests/org.example.complex.varlink b/varlink_generator/tests/org.example.complex.varlink index 6550245..9136599 100644 --- a/varlink_generator/tests/org.example.complex.varlink +++ b/varlink_generator/tests/org.example.complex.varlink @@ -8,6 +8,15 @@ type TypeEnum (type, b, c) type Interface (interface, b, c) +type ReservedEnum (self, Self, crate, super) + +type Reserved ( + self: ?string, + Self: ?int, + crate: string, + super: bool +) + type TypeFoo ( bool: bool, int: int, @@ -36,6 +45,13 @@ method Foo( method Bar() -> () +method Baz( + self: ?Reserved, + type: ReservedEnum +) -> ( + self: ?Reserved +) + error ErrorFoo ( enum: ( b: bool,