Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: borsh_init to borsh(init). #187

Merged
merged 23 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ fn test_simple_struct() {
## Features

Opting out from Serde allows borsh to have some features that currently are not available for serde-compatible serializers.
Currently we support two features: `borsh_init` and `borsh_skip` (the former one not available in Serde).
Currently we support two features: `borsh(init=<your initilization method name>` and `borsh_skip` (the former one not available in Serde).

`borsh_init` allows to automatically run an initialization function right after deserialization. This adds a lot of convenience for objects that are architectured to be used as strictly immutable. Usage example:
`borsh(init=...)` allows to automatically run an initialization function right after deserialization. This adds a lot of convenience for objects that are architectured to be used as strictly immutable. Usage example:

```rust
#[derive(BorshSerialize, BorshDeserialize)]
#[borsh_init(init)]
#[borsh(init=init)]
struct Message {
message: String,
timestamp: u64,
Expand Down
188 changes: 146 additions & 42 deletions borsh-derive/src/internals/attributes/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@ use quote::ToTokens;
use syn::{spanned::Spanned, Attribute, DeriveInput, Expr, ItemEnum, Path};

pub fn check_item_attributes(derive_input: &DeriveInput) -> Result<(), TokenStream> {
for attr in &derive_input.attrs {
if attr.path().is_ident(SKIP.0) {
return Err(syn::Error::new(
derive_input.ident.span(),
"`borsh_skip` is not allowed as derive input attribute",
)
.to_compile_error());
}
// TODO remove in next P
let attr = derive_input
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
.attrs
.iter()
.find(|attr| attr.path().is_ident(SKIP.0));

if attr.is_some() {
return Err(syn::Error::new(
derive_input.ident.span(),
"`borsh_skip` is not allowed as derive input attribute",
)
.to_compile_error());
}

let attr = derive_input.attrs.iter().find(|attr| attr.path() == BORSH);
if let Some(attr) = attr {
if attr.path().is_ident(BORSH.0) {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
attr.parse_nested_meta(|meta| {
if !meta.path.is_ident(USE_DISCRIMINANT) {
if !meta.path.is_ident(USE_DISCRIMINANT) && meta.path != INIT {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
return Err(syn::Error::new(
meta.path.span(),
"`use_discriminant` is the only supported attribute for `borsh`",
"`use_discriminant` or `init` are only supported attributes for `borsh`",
));
}
if meta.path.is_ident(USE_DISCRIMINANT) {
Expand All @@ -29,6 +37,9 @@ pub fn check_item_attributes(derive_input: &DeriveInput) -> Result<(), TokenStre
));
}
}
if meta.path.is_ident(INIT.0) {
let _expr: Expr = meta.value()?.parse()?;
}

Ok(())
})
Expand All @@ -48,29 +59,31 @@ pub fn contains_use_discriminant(input: &ItemEnum) -> Result<bool, syn::Error> {

let attrs = &input.attrs;
let mut use_discriminant = None;
for attr in attrs {
if attr.path().is_ident(BORSH.0) {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident(USE_DISCRIMINANT) {
let value_expr: Expr = meta.value()?.parse()?;
let value = value_expr.to_token_stream().to_string();
match value.as_str() {
"true" => {
use_discriminant = Some(true);
}
"false" => use_discriminant = Some(false),
_ => {
return Err(syn::Error::new(
value_expr.span(),
"`use_discriminant` accepts only `true` or `false`",
));
}
};
}
let attr = attrs.iter().find(|attr| attr.path() == BORSH);
if let Some(attr) = attr {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident(USE_DISCRIMINANT) {
let value_expr: Expr = meta.value()?.parse()?;
let value = value_expr.to_token_stream().to_string();
match value.as_str() {
"true" => {
use_discriminant = Some(true);
}
"false" => use_discriminant = Some(false),
_ => {
return Err(syn::Error::new(
value_expr.span(),
"`use_discriminant` accepts only `true` or `false`",
));
}
};
}

Ok(())
})?;
}
if meta.path == INIT {
let _value_expr: Expr = meta.value()?.parse()?;
}
Ok(())
})?;
}
let has_explicit_discriminants = input
.variants
Expand All @@ -86,18 +99,21 @@ pub fn contains_use_discriminant(input: &ItemEnum) -> Result<bool, syn::Error> {
}

pub(crate) fn contains_initialize_with(attrs: &[Attribute]) -> Option<Path> {
for attr in attrs.iter() {
if attr.path() == INIT {
let mut res = None;
let _ = attr.parse_nested_meta(|meta| {
res = Some(meta.path);
Ok(())
});
return res;
}
let mut res = None;
let attr = attrs.iter().find(|attr| attr.path() == BORSH);
if let Some(attr) = attr {
let _ = attr.parse_nested_meta(|meta| {
if meta.path == INIT {
let value_expr: Path = meta.value()?.parse()?;
res = Some(value_expr);
} else if meta.path.is_ident(USE_DISCRIMINANT) {
let _value_expr: Expr = meta.value()?.parse()?;
};
Ok(())
});
}

None
res
}

#[cfg(test)]
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -199,4 +215,92 @@ mod tests {
let actual = check_item_attributes(&item_enum);
local_insta_assert_snapshot!(actual.unwrap_err().to_token_stream().to_string());
}
#[test]
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
fn test_init_function() {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
let item_struct = syn::parse2::<DeriveInput>(quote! {
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)]
#[borsh(init = initialization_method)]
struct A<'a> {
x: u64,
}
})
.unwrap();

let actual = check_item_attributes(&item_struct);
assert!(actual.is_ok());
}

#[test]
fn test_init_function_wrong_format() {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
let item_struct: DeriveInput = syn::parse2(quote! {
#[derive(BorshDeserialize, Debug)]
#[borsh(init_func = initialization_method)]
struct A<'a> {
x: u64,
b: B,
y: f32,
z: String,
v: Vec<String>,

}
})
.unwrap();
let actual = check_item_attributes(&item_struct);
local_insta_assert_snapshot!(actual.unwrap_err().to_token_stream().to_string());
}
#[test]
fn test_contains_initialize_with_function() {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
let item_struct = syn::parse2::<DeriveInput>(quote! {
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)]
#[borsh(init = initialization_method)]
struct A<'a> {
x: u64,
}
})
.unwrap();

let actual = contains_initialize_with(&item_struct.attrs);
assert_eq!(
actual.unwrap().to_token_stream().to_string(),
"initialization_method"
);
}

#[test]
fn test_contains_initialize_with_function_wrong_format() {
let item_struct: DeriveInput = syn::parse2(quote! {
#[derive(BorshDeserialize, Debug)]
#[borsh(init_func = initialization_method)]
struct A<'a> {
x: u64,
b: B,
y: f32,
z: String,
v: Vec<String>,

}
})
.unwrap();
let actual = contains_initialize_with(&item_struct.attrs);
assert!(actual.is_none());
}

#[test]
fn test_contains_initialize_with_function_wrong_attr_format() {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
let item_struct: DeriveInput = syn::parse2(quote! {
#[derive(BorshDeserialize, Debug)]
#[borsh_init(initialization_method)]
struct A<'a> {
x: u64,
b: B,
y: f32,
z: String,
v: Vec<String>,

}
})
.unwrap();
let actual = contains_initialize_with(&item_struct.attrs);
assert!(actual.is_none());
}
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
source: borsh-derive/src/internals/attributes/item/mod.rs
expression: actual.unwrap_err().to_token_stream().to_string()
---
:: core :: compile_error ! { "`use_discriminant` is the only supported attribute for `borsh`" }
:: core :: compile_error ! { "`use_discriminant` or `init` are only supported attributes for `borsh`" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: borsh-derive/src/internals/attributes/item/mod.rs
expression: actual.unwrap_err().to_token_stream().to_string()
---
:: core :: compile_error ! { "`use_discriminant` or `init` are only supported attributes for `borsh`" }
2 changes: 1 addition & 1 deletion borsh-derive/src/internals/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub const DESERIALIZE: Symbol = Symbol("deserialize", "deserialize = ...");
/// borsh_skip - field-level only attribute, `BorshSerialize`, `BorshDeserialize`, `BorshSchema` contexts
pub const SKIP: Symbol = Symbol("borsh_skip", "borsh_skip");
/// borsh_init - item-level only attribute `BorshDeserialize` context
pub const INIT: Symbol = Symbol("borsh_init", "borsh_init(...)");
pub const INIT: Symbol = Symbol("init", "init(...)");
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
/// serialize_with - sub-borsh nested meta, field-level only, `BorshSerialize` context
pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with", "serialize_with = ...");
/// deserialize_with - sub-borsh nested meta, field-level only, `BorshDeserialize` context
Expand Down
34 changes: 34 additions & 0 deletions borsh-derive/src/internals/deserialize/enums/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,38 @@ mod tests {

local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
}
#[test]
fn borsh_init_func() {
let item_enum: ItemEnum = syn::parse2(quote! {
#[borsh(init = initializon_method, use_discriminant = true)]
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
enum A {
A,
B = 20,
C,
D,
E = 10,
F,
}
})
.unwrap();
let actual = process(&item_enum, Ident::new("borsh", Span::call_site())).unwrap();
local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
}
#[test]
fn borsh_init_func_reversed() {
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
let item_enum: ItemEnum = syn::parse2(quote! {
#[borsh(use_discriminant = true, init = initializon_method )]
enum A {
A,
B = 20,
C,
D,
E = 10,
F,
}
})
.unwrap();
let actual = process(&item_enum, Ident::new("borsh", Span::call_site())).unwrap();
local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
source: borsh-derive/src/internals/deserialize/enums/mod.rs
expression: pretty_print_syn_str(&actual).unwrap()
---
impl borsh::de::BorshDeserialize for A {
fn deserialize_reader<R: borsh::__private::maybestd::io::Read>(
reader: &mut R,
) -> ::core::result::Result<Self, borsh::__private::maybestd::io::Error> {
let tag = <u8 as borsh::de::BorshDeserialize>::deserialize_reader(reader)?;
<Self as borsh::de::EnumExt>::deserialize_variant(reader, tag)
}
}
impl borsh::de::EnumExt for A {
fn deserialize_variant<R: borsh::__private::maybestd::io::Read>(
reader: &mut R,
variant_tag: u8,
) -> ::core::result::Result<Self, borsh::__private::maybestd::io::Error> {
let mut return_value = if variant_tag == 0 {
A::A
} else if variant_tag == 20 {
A::B
} else if variant_tag == 20 + 1 {
A::C
} else if variant_tag == 20 + 1 + 1 {
A::D
} else if variant_tag == 10 {
A::E
} else if variant_tag == 10 + 1 {
A::F
} else {
return Err(
borsh::__private::maybestd::io::Error::new(
borsh::__private::maybestd::io::ErrorKind::InvalidData,
borsh::__private::maybestd::format!(
"Unexpected variant tag: {:?}", variant_tag
),
),
)
};
return_value.initializon_method();
Ok(return_value)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
dj8yfo marked this conversation as resolved.
Show resolved Hide resolved
source: borsh-derive/src/internals/deserialize/enums/mod.rs
expression: pretty_print_syn_str(&actual).unwrap()
---
impl borsh::de::BorshDeserialize for A {
fn deserialize_reader<R: borsh::__private::maybestd::io::Read>(
reader: &mut R,
) -> ::core::result::Result<Self, borsh::__private::maybestd::io::Error> {
let tag = <u8 as borsh::de::BorshDeserialize>::deserialize_reader(reader)?;
<Self as borsh::de::EnumExt>::deserialize_variant(reader, tag)
}
}
impl borsh::de::EnumExt for A {
fn deserialize_variant<R: borsh::__private::maybestd::io::Read>(
reader: &mut R,
variant_tag: u8,
) -> ::core::result::Result<Self, borsh::__private::maybestd::io::Error> {
let mut return_value = if variant_tag == 0 {
A::A
} else if variant_tag == 20 {
A::B
} else if variant_tag == 20 + 1 {
A::C
} else if variant_tag == 20 + 1 + 1 {
A::D
} else if variant_tag == 10 {
A::E
} else if variant_tag == 10 + 1 {
A::F
} else {
return Err(
borsh::__private::maybestd::io::Error::new(
borsh::__private::maybestd::io::ErrorKind::InvalidData,
borsh::__private::maybestd::format!(
"Unexpected variant tag: {:?}", variant_tag
),
),
)
};
return_value.initializon_method();
Ok(return_value)
}
}

Loading
Loading