satellite_attribute_error/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6use satellite_syn::codegen;
7use satellite_syn::parser::error::{self as error_parser, ErrorInput};
8use satellite_syn::ErrorArgs;
9use syn::{parse_macro_input, Expr};
10
11/// Generates `Error` and `type Result<T> = Result<T, Error>` types to be
12/// used as return types from Anchor instruction handlers. Importantly, the
13/// attribute implements
14/// [`From`](https://doc.rust-lang.org/std/convert/trait.From.html) on the
15/// `ErrorCode` to support converting from the user defined error enum *into*
16/// the generated `Error`.
17///
18/// # Example
19///
20/// ```ignore
21/// use satellite_lang::prelude::*;
22///
23/// #[program]
24/// mod errors {
25///     use super::*;
26///     pub fn hello(_ctx: Context<Hello>) -> Result<()> {
27///         Err(error!(MyError::Hello))
28///     }
29/// }
30///
31/// #[derive(Accounts)]
32/// pub struct Hello {}
33///
34/// #[error_code]
35/// pub enum MyError {
36///     #[msg("This is an error message clients will automatically display")]
37///     Hello,
38/// }
39/// ```
40///
41/// Note that we generate a new `Error` type so that we can return either the
42/// user defined error enum *or* a
43/// [`ProgramError`](../arch_program/enum.ProgramError.html), which is used
44/// pervasively, throughout solana program crates. The generated `Error` type
45/// should almost never be used directly, as the user defined error is
46/// preferred. In the example above, `error!(MyError::Hello)`.
47///
48/// # Msg
49///
50/// The `#[msg(..)]` attribute is inert, and is used only as a marker so that
51/// parsers  and IDLs can map error codes to error messages.
52#[proc_macro_attribute]
53pub fn error_code(
54    args: proc_macro::TokenStream,
55    input: proc_macro::TokenStream,
56) -> proc_macro::TokenStream {
57    let args = match args.is_empty() {
58        true => None,
59        false => Some(parse_macro_input!(args as ErrorArgs)),
60    };
61    let mut error_enum = parse_macro_input!(input as syn::ItemEnum);
62    let error = codegen::error::generate(error_parser::parse(&mut error_enum, args));
63    proc_macro::TokenStream::from(error)
64}
65
66/// Generates an [`Error::AnchorError`](../../satellite_lang/error/enum.Error.html) that includes file and line information.
67///
68/// # Example
69/// ```rust,ignore
70/// #[program]
71/// mod errors {
72///     use super::*;
73///     pub fn example(_ctx: Context<Example>) -> Result<()> {
74///         Err(error!(MyError::Hello))
75///     }
76/// }
77///
78/// #[error_code]
79/// pub enum MyError {
80///     #[msg("This is an error message clients will automatically display")]
81///     Hello,
82/// }
83/// ```
84#[proc_macro]
85pub fn error(ts: proc_macro::TokenStream) -> TokenStream {
86    let input = parse_macro_input!(ts as ErrorInput);
87    let error_code = input.error_code;
88    create_error(error_code, true, None)
89}
90
91fn create_error(error_code: Expr, source: bool, account_name: Option<Expr>) -> TokenStream {
92    let error_origin = match (source, account_name) {
93        (false, None) => quote! { None },
94        (false, Some(account_name)) => quote! {
95            Some(satellite_lang::error::ErrorOrigin::AccountName(#account_name.to_string()))
96        },
97        (true, _) => quote! {
98            Some(satellite_lang::error::ErrorOrigin::Source(satellite_lang::error::Source {
99                filename: file!(),
100                line: line!()
101            }))
102        },
103    };
104
105    TokenStream::from(quote! {
106        satellite_lang::error::Error::from(
107            satellite_lang::error::AnchorError {
108                error_name: #error_code.name(),
109                error_code_number: #error_code.into(),
110                error_msg: #error_code.to_string(),
111                error_origin: #error_origin,
112                compared_values: None
113            }
114        )
115    })
116}