Skip to content

Commit d0b974d

Browse files
authored
ssh-encoding: add derive feature + extend derive functionallity (#348)
* ssh-derive: add support for all struct and enum types Extend the derive macros to support all struct and enum types, not just simple structs. This also introduces some custom attributes to control the derived code. Supported attributes: - `#[ssh(length_prefixed)]`: will use length-prefix encoding/decoding for the struct/enum as a whole, or per field, depending on where the attribute is placed. - `#[repr(u8)]`, `#[repr(u32)]` etc.: Enum discriminants will be encoded/decoded depending on the specified repr used. * ssh-encoding: add error variant `InvalidDiscriminant` This is used in derived `Decode` implementations for enums. It holds a u128 because discriminants may be as large as u128/i128. Storing the value isn't strictly necessary, but allows a better error message. * ssh-encoding: add derive feature Adds an optional feature that re-exports `ssh-derive` macros and exposes a documentation module with examples. The examples also serve as tests. Also adds tests for the derive feature in the form of declaring various structs and enums and asserting that encoding/decoding works as expected.
1 parent 73df345 commit d0b974d

File tree

11 files changed

+1097
-177
lines changed

11 files changed

+1097
-177
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ssh-derive/src/attributes.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
4+
pub(crate) struct ContainerAttributes {
5+
pub(crate) length_prefixed: bool,
6+
pub(crate) discriminant_type: Option<TokenStream>,
7+
}
8+
9+
impl TryFrom<&syn::DeriveInput> for ContainerAttributes {
10+
type Error = syn::Error;
11+
12+
fn try_from(input: &syn::DeriveInput) -> Result<Self, Self::Error> {
13+
let mut length_prefixed = false;
14+
let mut discriminant_type = None;
15+
for attr in &input.attrs {
16+
if attr.path().is_ident("ssh") {
17+
attr.parse_nested_meta(|meta| {
18+
// #[ssh(length_prefixed)]
19+
if meta.path.is_ident("length_prefixed") {
20+
length_prefixed = true;
21+
} else {
22+
return Err(syn::Error::new_spanned(meta.path, "unknown attribute"));
23+
}
24+
Ok(())
25+
})?;
26+
} else if attr.path().is_ident("repr") {
27+
attr.parse_nested_meta(|meta| {
28+
// #[repr(u8)] or similar
29+
// https://doc.rust-lang.org/reference/type-layout.html#primitive-representations
30+
if meta.path.is_ident("u8") {
31+
discriminant_type = Some(quote! {u8});
32+
} else if meta.path.is_ident("u16") {
33+
discriminant_type = Some(quote! {u16});
34+
} else if meta.path.is_ident("u32") {
35+
discriminant_type = Some(quote! {u32});
36+
} else if meta.path.is_ident("u64") {
37+
discriminant_type = Some(quote! {u64});
38+
} else if meta.path.is_ident("u128") {
39+
discriminant_type = Some(quote! {u128});
40+
} else if meta.path.is_ident("usize") {
41+
discriminant_type = Some(quote! {usize});
42+
} else if meta.path.is_ident("i8") {
43+
discriminant_type = Some(quote! {i8});
44+
} else if meta.path.is_ident("i16") {
45+
discriminant_type = Some(quote! {i16});
46+
} else if meta.path.is_ident("i32") {
47+
discriminant_type = Some(quote! {i32});
48+
} else if meta.path.is_ident("i64") {
49+
discriminant_type = Some(quote! {i64});
50+
} else if meta.path.is_ident("i128") {
51+
discriminant_type = Some(quote! {i128});
52+
} else if meta.path.is_ident("isize") {
53+
discriminant_type = Some(quote! {isize});
54+
} else {
55+
return Err(syn::Error::new_spanned(
56+
meta.path,
57+
"unsupported repr for deriving Encode/Decode, must be a primitive integer type",
58+
));
59+
}
60+
Ok(())
61+
})?;
62+
}
63+
}
64+
65+
Ok(Self {
66+
length_prefixed,
67+
discriminant_type,
68+
})
69+
}
70+
}
71+
72+
pub(crate) struct FieldAttributes {
73+
pub(crate) length_prefixed: bool,
74+
}
75+
76+
impl TryFrom<&syn::Field> for FieldAttributes {
77+
type Error = syn::Error;
78+
79+
fn try_from(field: &syn::Field) -> Result<Self, Self::Error> {
80+
let mut length_prefixed = false;
81+
for attr in &field.attrs {
82+
if attr.path().is_ident("ssh") {
83+
attr.parse_nested_meta(|meta| {
84+
// #[ssh(length_prefixed)]
85+
if meta.path.is_ident("length_prefixed") {
86+
length_prefixed = true;
87+
} else {
88+
return Err(syn::Error::new_spanned(meta.path, "unknown attribute"));
89+
}
90+
Ok(())
91+
})?;
92+
}
93+
}
94+
95+
Ok(Self { length_prefixed })
96+
}
97+
}

0 commit comments

Comments
 (0)