1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//! Crate defining a procedural macro for building Solana program errors

// Required to include `#[allow(clippy::integer_arithmetic)]`
// below since the tokens generated by `quote!` in the implementation
// for `MacroType::PrintProgramError` and `MacroType::SplProgramError`
// trigger the lint upstream through `quote_token_with_context` within the
// `quote` crate
//
// Culprit is `macro_impl.rs:66`
#![allow(clippy::arithmetic_side_effects)]
#![deny(missing_docs)]
#![cfg_attr(not(test), forbid(unsafe_code))]

extern crate proc_macro;

mod macro_impl;
mod parser;

use {
    crate::parser::SplProgramErrorArgs,
    macro_impl::MacroType,
    proc_macro::TokenStream,
    syn::{parse_macro_input, ItemEnum},
};

/// Derive macro to add `Into<solana_program::program_error::ProgramError>`
/// trait
#[proc_macro_derive(IntoProgramError)]
pub fn into_program_error(input: TokenStream) -> TokenStream {
    let ItemEnum { ident, .. } = parse_macro_input!(input as ItemEnum);
    MacroType::IntoProgramError { ident }
        .generate_tokens()
        .into()
}

/// Derive macro to add `solana_program::decode_error::DecodeError` trait
#[proc_macro_derive(DecodeError)]
pub fn decode_error(input: TokenStream) -> TokenStream {
    let ItemEnum { ident, .. } = parse_macro_input!(input as ItemEnum);
    MacroType::DecodeError { ident }.generate_tokens().into()
}

/// Derive macro to add `solana_program::program_error::PrintProgramError` trait
#[proc_macro_derive(PrintProgramError)]
pub fn print_program_error(input: TokenStream) -> TokenStream {
    let ItemEnum {
        ident, variants, ..
    } = parse_macro_input!(input as ItemEnum);
    MacroType::PrintProgramError { ident, variants }
        .generate_tokens()
        .into()
}

/// Proc macro attribute to turn your enum into a Solana Program Error
///
/// Adds:
/// - `Clone`
/// - `Debug`
/// - `Eq`
/// - `PartialEq`
/// - `thiserror::Error`
/// - `num_derive::FromPrimitive`
/// - `Into<solana_program::program_error::ProgramError>`
/// - `solana_program::decode_error::DecodeError`
/// - `solana_program::program_error::PrintProgramError`
///
/// Optionally, you can add `hash_error_code_start: u32` argument to create
/// a unique `u32` _starting_ error codes from the names of the enum variants.
/// Notes:
/// - The _error_ variant will start at this value, and the rest will be
/// incremented by one
/// - The value provided is only for code readability, the actual error code
/// will be a hash of the input string and is checked against your input
///
/// Syntax: `#[spl_program_error(hash_error_code_start = 1275525928)]`
/// Hash Input: `spl_program_error:<enum name>:<variant name>`
/// Value: `u32::from_le_bytes(<hash of input>[13..17])`
#[proc_macro_attribute]
pub fn spl_program_error(attr: TokenStream, input: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as SplProgramErrorArgs);
    let item_enum = parse_macro_input!(input as ItemEnum);
    MacroType::SplProgramError { args, item_enum }
        .generate_tokens()
        .into()
}