rialo_sol_attribute_error/
lib.rs

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