Skip to content

Commit

Permalink
add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyProgrammist committed May 31, 2024
1 parent 666eb9f commit 873627c
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 94 deletions.
68 changes: 68 additions & 0 deletions near-sdk-macros/src/core_impl/info_extractor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,79 @@ pub struct Returns {

#[derive(Clone, PartialEq, Eq)]
pub enum ReturnKind {
/// Return type is not specified.
///
/// Functions default to `()` and closures default to type inference.
/// When the contract call happens:
/// - Contract struct is initialized
/// - The method is called
/// - Contract state is written if it is modifying method.
/// In case of panic, state is not written.
///
/// # Example:
/// ```ignore
/// pub fn foo(&mut self);
/// ```
Default,

/// Return type is specified. But it does not have any specifics.
///
/// When the contract call happens, in addition to the Default:
/// - The return value is serialized and returned
///
/// # Example:
/// ```ignore
/// pub fn foo(&mut self) -> u64;
/// ```
General(Type),

/// Return type is Result<OkType, ErrType> and the function is marked with #[handle_result].
/// ErrType struct implements near_sdk::FunctionError. (i.e. used with #[derive(near_sdk::FunctionError)])
///
/// When the contract call happens, in addition to the General:
/// - In case Result value is Ok, the unwrapped object is returned
/// - In case Result value is Err, panic is called and state is not written.
///
/// # Example:
/// ```ignore
/// #[handle_result]
/// pub fn foo(&mut self) -> Result<u64, &'static str>;
/// ```
HandlesResultExplicit(Type),

/// Return type is Result<OkType, ErrType> and, the function is not marked with #[handle_result] and
/// ErrType struct implements near_sdk::ContractErrorTrait (i.e. used with #[near_sdk::contract_error])
///
/// When the contract call happens, in addition to General:
/// - In case Result value is Err, panic is called and state is not written.
/// As soon as ErrType implements ContractErrorTrait, it is returned as a well-defined structure.
/// You can see the structure in #[contract_error] documentation.
/// If the error struct does not implement ContractErrorTrait, the code should not compile.
/// - In case #[persist_on_error] is used on method, panic is not called.
/// And the contract state is written safely.
/// But the extra <method_name>_error method is generated.
/// And this method is called in a new Promise.
/// This method effectively panics with structured error.
///
/// # Example:
/// ```ignore
/// #[contract_error]
/// pub struct MyError;
///
/// // if Ok() is returned, everything ok, otherwise panic with well-structured error
/// pub fn foo(&mut self) -> Result<u64, MyError>;
/// ```
///
/// ```ignore
/// // Write state safely anyway.
/// // if Ok() is returned, just return. Otherwise call new Promise which will panic with well-structured error.
/// #[persist_on_error]
/// pub fn foo(&mut self) -> Result<u64, MyError>;
/// ```
///
HandlesResultImplicit(StatusResult),
}
/// In other cases the code should not compile

#[derive(Clone, PartialEq, Eq)]
pub struct StatusResult {
Expand Down
241 changes: 147 additions & 94 deletions near-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,100 +14,6 @@ use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{parse_quote, Expr, ImplItem, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereClause};

#[derive(FromMeta)]
struct ContractErrorArgs {
sdk: Option<bool>,
inside_nearsdk: Option<bool>,
}
#[proc_macro_attribute]
pub fn contract_error(attr: TokenStream, item: TokenStream) -> TokenStream {
let meta_list = match NestedMeta::parse_meta_list(attr.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(Error::from(e).write_errors());
}
};

let contract_error_args = match ContractErrorArgs::from_list(&meta_list) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};

let input = syn::parse_macro_input!(item as syn::DeriveInput);
let ident = &input.ident;

let error_type = if contract_error_args.sdk.unwrap_or(false) {
quote! {"SDK_CONTRACT_ERROR"}
} else {
quote! {"CUSTOM_CONTRACT_ERROR"}
};

let near_sdk_crate: proc_macro2::TokenStream;
let mut bool_inside_nearsdk_for_macro = quote! {false};

if contract_error_args.inside_nearsdk.unwrap_or(false) {
near_sdk_crate = quote! {crate};
bool_inside_nearsdk_for_macro = quote! {true};
} else {
near_sdk_crate = quote! {::near_sdk};
};

let mut expanded = quote! {
#[#near_sdk_crate ::near(serializers=[json], inside_nearsdk=#bool_inside_nearsdk_for_macro)]
#input

impl #near_sdk_crate ::ContractErrorTrait for #ident {
fn error_type(&self) -> &'static str {
#error_type
}

fn wrap(&self) -> #near_sdk_crate ::serde_json::Value {
#[#near_sdk_crate ::near(inside_nearsdk=#bool_inside_nearsdk_for_macro, serializers = [json])]
struct ErrorWrapper<T> {
name: String,
cause: ErrorCause<T>,
}

#[#near_sdk_crate ::near(inside_nearsdk=#bool_inside_nearsdk_for_macro, serializers = [json])]
struct ErrorCause<T> {
name: String,
info: T
}

#near_sdk_crate ::serde_json::json! {
{ "error" : ErrorWrapper {
name: String::from(self.error_type()),
cause: ErrorCause {
name: std::any::type_name::<#ident>().to_string(),
info: self
}
} }
}
}
}
};
if *ident != "BaseError" {
expanded.extend(quote! {
impl From<#ident> for String {
fn from(err: #ident) -> Self {
#near_sdk_crate ::serde_json::json!{#near_sdk_crate ::wrap_error(err)}.to_string()
}
}

impl From<#ident> for #near_sdk_crate ::BaseError {
fn from(err: #ident) -> Self {
#near_sdk_crate ::BaseError{
error: #near_sdk_crate ::wrap_error(err),
}
}
}
});
}
TokenStream::from(expanded)
}

#[derive(Debug, Clone)]
struct Serializers {
vec: Vec<Expr>,
Expand Down Expand Up @@ -1025,3 +931,150 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream {
)
}
}

/// This attribute macro is used on a struct or enum to generate the necessary code for an error
/// returned from a contract method call.
///
/// # Example:
///
/// For the error:
/// ```ignore
/// #[contract_error]
/// pub struct MyError {
/// field1: String,
/// field2: String,
/// }
/// ```
///
/// And the function:
/// ```ignore
/// pub fn my_method(&self) -> Result<(), MyError>;
/// ```
///
/// The error is serialized as a JSON object with the following structure:
/// ```ignore
/// {
/// "error": {
/// // this name can be "SDK_CONTRACT_ERROR" and "CUSTOM_CONTRACT_ERROR"
/// // To generate "SDK_CONTRACT_ERROR", use sdk attribute `#[contract_error(sdk)]`.
/// // Otherwise, it will generate "CUSTOM_CONTRACT_ERROR"
/// "name": "CUSTOM_CONTRACT_ERROR",
/// "cause": {
/// // this name is the name of error struct
/// "name": "MyError",
/// "info": {
/// /// fields of the error struct
/// "field1": "value1",
/// "field2": "value2"
/// }
/// }
/// }
/// ```
///
/// Note: you can assign any error defined like that to BaseError:
/// ```ignore
/// let base_error: BaseError = MyError { field1: "value1".to_string(), field2: "value2".to_string() }.into();
/// ```
///
/// Use inside_nearsdk attribute (#[contract_error(inside_nearsdk)]) if the error struct is defined inside near-sdk.
/// Don't use if it is defined outside.
///
/// Internally, it makes error struct to:
/// - implement `near_sdk::ContractErrorTrait` so that it becomes correct error
/// which can be returned from contract method with defined structure.
/// - implement `From<ErrorStruct> for near_sdk::BaseError` as a polymorphic solution
/// - implement `From<ErrorStruct> for String` to convert the error to a string

#[derive(FromMeta)]
struct ContractErrorArgs {
sdk: Option<bool>,
inside_nearsdk: Option<bool>,
}
#[proc_macro_attribute]
pub fn contract_error(attr: TokenStream, item: TokenStream) -> TokenStream {
let meta_list = match NestedMeta::parse_meta_list(attr.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(Error::from(e).write_errors());
}
};

let contract_error_args = match ContractErrorArgs::from_list(&meta_list) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};

let input = syn::parse_macro_input!(item as syn::DeriveInput);
let ident = &input.ident;

let error_type = if contract_error_args.sdk.unwrap_or(false) {
quote! {"SDK_CONTRACT_ERROR"}
} else {
quote! {"CUSTOM_CONTRACT_ERROR"}
};

let near_sdk_crate: proc_macro2::TokenStream;
let mut bool_inside_nearsdk_for_macro = quote! {false};

if contract_error_args.inside_nearsdk.unwrap_or(false) {
near_sdk_crate = quote! {crate};
bool_inside_nearsdk_for_macro = quote! {true};
} else {
near_sdk_crate = quote! {::near_sdk};
};

let mut expanded = quote! {
#[#near_sdk_crate ::near(serializers=[json], inside_nearsdk=#bool_inside_nearsdk_for_macro)]
#input

impl #near_sdk_crate ::ContractErrorTrait for #ident {
fn error_type(&self) -> &'static str {
#error_type
}

fn wrap(&self) -> #near_sdk_crate ::serde_json::Value {
#[#near_sdk_crate ::near(inside_nearsdk=#bool_inside_nearsdk_for_macro, serializers = [json])]
struct ErrorWrapper<T> {
name: String,
cause: ErrorCause<T>,
}

#[#near_sdk_crate ::near(inside_nearsdk=#bool_inside_nearsdk_for_macro, serializers = [json])]
struct ErrorCause<T> {
name: String,
info: T
}

#near_sdk_crate ::serde_json::json! {
{ "error" : ErrorWrapper {
name: String::from(self.error_type()),
cause: ErrorCause {
name: std::any::type_name::<#ident>().to_string(),
info: self
}
} }
}
}
}
};
if *ident != "BaseError" {
expanded.extend(quote! {
impl From<#ident> for String {
fn from(err: #ident) -> Self {
#near_sdk_crate ::serde_json::json!{#near_sdk_crate ::wrap_error(err)}.to_string()
}
}

impl From<#ident> for #near_sdk_crate ::BaseError {
fn from(err: #ident) -> Self {
#near_sdk_crate ::BaseError{
error: #near_sdk_crate ::wrap_error(err),
}
}
}
});
}
TokenStream::from(expanded)
}
Loading

0 comments on commit 873627c

Please sign in to comment.