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

impl NearSchema derive macro #891

Merged
merged 28 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
15ee705
impl NearAbi derive macro
miraclx Aug 17, 2022
6138633
autoimpl BorshSchema
miraclx Aug 17, 2022
016aaf8
impl #[abi(json, borsh)] attribute macro
miraclx Aug 18, 2022
d18752f
remove abi feature gate for NearAbi derive
miraclx Aug 19, 2022
e5ad2bb
NearAbi -> NearSchema
miraclx Aug 22, 2022
b3417e7
allow single value abi impls
miraclx Aug 22, 2022
625d9cd
Merge branch 'master' into miraclx/near-abi-derive
miraclx Aug 24, 2022
f2158ad
replace bitwise flags with bools
miraclx Aug 24, 2022
851304d
Merge branch 'master' into miraclx/near-abi-derive
miraclx Aug 26, 2022
2391d9c
strip unknown attributes
miraclx Aug 26, 2022
8f19d30
feature gate ToTokens trait import
miraclx Aug 26, 2022
65eb70e
feature gate NearSchema export
miraclx Aug 26, 2022
9d23ab3
Merge branch 'master' into miraclx/near-abi-derive
miraclx Sep 2, 2022
3b9a874
Merge branch 'master' into miraclx/near-abi-derive
miraclx Nov 7, 2022
5278c49
strip all unknown attrs, regardless of expected activation
miraclx Nov 7, 2022
8a920e0
fix schema proxy
miraclx Nov 7, 2022
43f0f2c
update macro expansion format
miraclx Nov 8, 2022
f1ef003
add compilation tests
miraclx Nov 8, 2022
3cff8ca
hide NearSchema behind the unstable feature flag
miraclx Nov 8, 2022
993255c
do not import all
miraclx Nov 8, 2022
896b311
report all errors at once, instead of incrementally
miraclx Nov 8, 2022
dbb9c8b
add tests for schema errors
miraclx Nov 8, 2022
ac8f7dc
fix clippy
miraclx Nov 8, 2022
05f872c
adder builds with abi feature
miraclx Nov 8, 2022
8c2d230
localize format_ident import
miraclx Nov 8, 2022
8ad70c9
union errors take higher precedence than attr errors
miraclx Nov 8, 2022
2c54d70
Merge branch 'master' into miraclx/near-abi-derive
austinabell Nov 8, 2022
fa32145
add compilation test for edge case that should've failed
miraclx Nov 8, 2022
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
2 changes: 1 addition & 1 deletion examples/adder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
crate-type = ["cdylib"]

[dependencies]
near-sdk = { path = "../../near-sdk" }
near-sdk = { path = "../../near-sdk", features = ["unstable"] }
serde = { version = "1", features = ["derive"] }
schemars = "0.8"

Expand Down
11 changes: 6 additions & 5 deletions examples/adder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use near_sdk::__private::schemars::JsonSchema;
use near_sdk::borsh::{self, BorshDeserialize, BorshSchema, BorshSerialize};
use near_sdk::near_bindgen;
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::serde::{Deserialize, Serialize};
use near_sdk::{near_bindgen, NearSchema};

#[derive(JsonSchema, Serialize, Deserialize, BorshDeserialize, BorshSerialize, BorshSchema)]
#[derive(NearSchema, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
#[abi(json, borsh)]
pub struct Pair(u32, u32);

#[derive(JsonSchema, Serialize, Deserialize)]
#[derive(NearSchema, Serialize, Deserialize)]
#[abi(json, borsh)]
pub struct DoublePair {
first: Pair,
second: Pair,
Expand Down
1 change: 1 addition & 0 deletions near-sdk-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ quote = "1.0"
Inflector = { version = "0.11.4", default-features = false, features = [] }

[features]
abi = []
__abi-embed = []
__abi-generate = []
172 changes: 172 additions & 0 deletions near-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,178 @@ pub fn metadata(item: TokenStream) -> TokenStream {
}
}

#[cfg(feature = "abi")]
#[proc_macro_derive(NearSchema, attributes(abi, serde, borsh_skip, schemars, validate))]
pub fn derive_near_schema(input: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(input as syn::DeriveInput);

let (mut json_schema, mut borsh_schema) = (false, false);
let mut type_attrs = vec![];
let mut errors = vec![];
for attr in input.attrs {
match attr.parse_meta() {
// #[abi]
Ok(syn::Meta::Path(meta)) if meta.is_ident("abi") => {
errors.push(syn::Error::new_spanned(
meta.into_token_stream(),
"attribute requires at least one argument",
));
}
// #[abi(json, borsh)]
Ok(syn::Meta::List(meta)) if meta.path.is_ident("abi") => {
// #[abi()]
if meta.nested.is_empty() {
errors.push(syn::Error::new_spanned(
meta.into_token_stream(),
"attribute requires at least one argument",
));
continue;
}
for meta in meta.nested {
match meta {
// #[abi(.. json ..)]
syn::NestedMeta::Meta(m) if m.path().is_ident("json") => json_schema = true,
// #[abi(.. borsh ..)]
syn::NestedMeta::Meta(m) if m.path().is_ident("borsh") => {
borsh_schema = true
}
_ => {
errors.push(syn::Error::new_spanned(
meta.into_token_stream(),
"invalid argument, expected: `json` or `borsh`",
));
}
}
}
}
// #[serde(..)], #[schemars(..)]
Ok(syn::Meta::List(meta))
if meta.path.is_ident("serde") || meta.path.is_ident("schemars") =>
{
type_attrs.push(attr)
}
_ => continue,
}
}

input.attrs = type_attrs;

let strip_unknown_attr = |attrs: &mut Vec<syn::Attribute>| {
attrs.retain(|attr| {
["serde", "schemars", "validate", "borsh_skip"]
.iter()
.any(|&path| attr.path.is_ident(path))
});
};

match &mut input.data {
syn::Data::Struct(data) => {
for field in &mut data.fields {
strip_unknown_attr(&mut field.attrs);
}
}
syn::Data::Enum(data) => {
for variant in &mut data.variants {
strip_unknown_attr(&mut variant.attrs);
for field in &mut variant.fields {
strip_unknown_attr(&mut field.attrs);
}
}
}
syn::Data::Union(_) => {
return TokenStream::from(
syn::Error::new_spanned(
input.to_token_stream(),
"`NearSchema` does not support derive for unions",
)
.to_compile_error(),
)
}
}

if let Some(combined_errors) = errors.into_iter().reduce(|mut l, r| (l.combine(r), l).1) {
return TokenStream::from(combined_errors.to_compile_error());
}

let json_schema = json_schema || !borsh_schema;

let derive = match (json_schema, borsh_schema) {
// <unspecified> or #[abi(json)]
(_, false) => quote! {
#[derive(schemars::JsonSchema)]
},
// #[abi(borsh)]
(false, true) => quote! {
#[derive(borsh::BorshSchema)]
},
// #[abi(json, borsh)]
(true, true) => quote! {
#[derive(schemars::JsonSchema, borsh::BorshSchema)]
},
};

let input_ident = &input.ident;

let input_ident_proxy = quote::format_ident!("{}__NEAR_SCHEMA_PROXY", input_ident);

let json_impl = if json_schema {
quote! {
#[automatically_derived]
impl schemars::JsonSchema for #input_ident_proxy {
fn schema_name() -> ::std::string::String {
stringify!(#input_ident).to_string()
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<#input_ident as schemars::JsonSchema>::json_schema(gen)
}
}
}
} else {
quote! {}
};

let borsh_impl = if borsh_schema {
quote! {
#[automatically_derived]
impl borsh::BorshSchema for #input_ident_proxy {
fn declaration() -> ::std::string::String {
stringify!(#input_ident).to_string()
}

fn add_definitions_recursively(
definitions: &mut borsh::maybestd::collections::HashMap<
borsh::schema::Declaration,
borsh::schema::Definition,
>,
) {
<#input_ident as borsh::BorshSchema>::add_definitions_recursively(definitions);
}
}
}
} else {
quote! {}
};

TokenStream::from(quote! {
#[cfg(not(target_arch = "wasm32"))]
const _: () = {
#[allow(non_camel_case_types)]
type #input_ident_proxy = #input_ident;
{
use near_sdk::borsh;
use near_sdk::__private::schemars;

#derive
#input

#json_impl
#borsh_impl
};
};
})
}

/// `PanicOnDefault` generates implementation for `Default` trait that panics with the following
/// message `The contract is not initialized` when `default()` is called.
/// This is a helpful macro in case the contract is required to be initialized with either `init` or
Expand Down
3 changes: 2 additions & 1 deletion near-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Rust library for writing NEAR smart contracts.
[[test]]
name = "compilation_tests"
path = "compilation_tests/all.rs"
required-features = ["abi", "unstable"]

[dependencies]
# Provide near_bidgen macros.
Expand Down Expand Up @@ -56,7 +57,7 @@ default = ["wee_alloc", "unit-testing", "legacy", "abi"]
expensive-debug = []
unstable = []
legacy = []
abi = ["near-abi", "schemars"]
abi = ["near-abi", "schemars", "near-sdk-macros/abi"]
unit-testing = ["near-vm-logic", "near-primitives-core", "near-primitives", "near-crypto"]

__abi-embed = ["near-sdk-macros/__abi-embed"]
Expand Down
2 changes: 2 additions & 0 deletions near-sdk/compilation_tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ fn compilation_tests() {
t.pass("compilation_tests/borsh_storage_key_generics.rs");
t.pass("compilation_tests/function_error.rs");
t.pass("compilation_tests/enum_near_bindgen.rs");
t.pass("compilation_tests/schema_derive.rs");
t.compile_fail("compilation_tests/schema_derive_invalids.rs");
}
Loading