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