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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Procedural macro providing a `#[drink::test]` attribute for `drink`-based contract testing.

#![warn(missing_docs)]

mod bundle_provision;
mod contract_building;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{ItemEnum, ItemFn};

use crate::contract_building::build_contracts;

type SynResult<T> = Result<T, syn::Error>;

/// Defines a drink!-based test.
///
/// # Requirements
///
/// - You mustn't import `drink::test` in the scope, where the macro is used. In other words, you
/// should always use the macro only with a qualified path `#[drink::test]`.
/// - Your crate cannot be part of a cargo workspace.
///
/// # Impact
///
/// This macro will take care of building all needed contracts for the test. The building process
/// will be executed during compile time.
///
/// Contracts to be built:
///  - current cargo package if contains a `ink-as-dependency` feature
///  - all dependencies declared in the `Cargo.toml` file with the `ink-as-dependency` feature
/// enabled
///
/// Note: Depending on a non-local contract is not tested yet.
///
/// # Example
///
/// ```rust, ignore
/// #[drink::test]
/// fn testcase() {
///     Session::<MinimalRuntime>::new()
///         .unwrap()
///         .deploy(bytes(), "new", NO_ARGS, vec![], None, &transcoder())
///         .unwrap();
/// }
/// ```
#[proc_macro_attribute]
pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
    match test_internal(attr.into(), item.into()) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Auxiliary function to enter ?-based error propagation.
fn test_internal(_attr: TokenStream2, item: TokenStream2) -> SynResult<TokenStream2> {
    let item_fn = syn::parse2::<ItemFn>(item)?;
    build_contracts();

    Ok(quote! {
        #[test]
        #item_fn
    })
}

/// Defines a contract bundle provider.
///
/// # Requirements
///
/// - Your crate cannot be part of a cargo workspace.
/// - Your crate must have `drink` in its dependencies (and it shouldn't be renamed).
/// - The attributed enum must not:
///     - be generic
///     - have variants
///     - have any attributes conflicting with `#[derive(Copy, Clone, PartialEq, Eq, Debug)]`
///
/// # Impact
///
/// This macro is intended to be used as an attribute of some empty enum. It will build all
/// contracts crates (with rules identical to those of `#[drink::test]`), and populate the decorated
/// enum with variants, one per built contract.
///
/// If the current crate is a contract crate, the enum will receive a method `local()` that returns
/// the contract bundle for the current crate.
///
/// Besides that, the enum will receive a method `bundle(self)` that returns the contract bundle
/// for corresponding contract variant.
///
/// Both methods return `DrinkResult<ContractBundle>`.
///
/// # Example
///
/// ```rust, ignore
/// #[drink::contract_bundle_provider]
/// enum BundleProvider {}
///
/// fn testcase() {
///     Session::<MinimalRuntime>::new()?
///         .deploy_bundle_and(BundleProvider::local()?, "new", NO_ARGS, vec![], None)
///         .deploy_bundle_and(BundleProvider::AnotherContract.bundle()?, "new", NO_ARGS, vec![], None)
///         .unwrap();
/// }
/// ```
#[proc_macro_attribute]
pub fn contract_bundle_provider(attr: TokenStream, item: TokenStream) -> TokenStream {
    match contract_bundle_provider_internal(attr.into(), item.into()) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Auxiliary function to enter ?-based error propagation.
fn contract_bundle_provider_internal(
    _attr: TokenStream2,
    item: TokenStream2,
) -> SynResult<TokenStream2> {
    let enum_item = parse_bundle_enum(item)?;
    let bundle_registry = build_contracts();
    Ok(bundle_registry.generate_bundle_provision(enum_item))
}

fn parse_bundle_enum(item: TokenStream2) -> SynResult<ItemEnum> {
    let enum_item = syn::parse2::<ItemEnum>(item)?;

    if !enum_item.generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            enum_item.generics.params,
            "ContractBundleProvider must not be generic",
        ));
    }
    if !enum_item.variants.is_empty() {
        return Err(syn::Error::new_spanned(
            enum_item.variants,
            "ContractBundleProvider must not have variants",
        ));
    }

    Ok(enum_item)
}