sbor_derive/
lib.rs

1use proc_macro::TokenStream;
2use std::str::FromStr;
3
4/// Derive code that returns the value kind.
5#[proc_macro_derive(Categorize, attributes(sbor))]
6pub fn categorize(input: TokenStream) -> TokenStream {
7    sbor_derive_common::categorize::handle_categorize(proc_macro2::TokenStream::from(input), None)
8        .unwrap_or_else(|err| err.to_compile_error())
9        .into()
10}
11
12/// Derive code that encodes this data structure
13#[proc_macro_derive(Encode, attributes(sbor))]
14pub fn encode(input: TokenStream) -> TokenStream {
15    sbor_derive_common::encode::handle_encode(proc_macro2::TokenStream::from(input), None)
16        .unwrap_or_else(|err| err.to_compile_error())
17        .into()
18}
19
20/// Derive code that decodes this data structure from a byte array.
21#[proc_macro_derive(Decode, attributes(sbor))]
22pub fn decode(input: TokenStream) -> TokenStream {
23    sbor_derive_common::decode::handle_decode(proc_macro2::TokenStream::from(input), None)
24        .unwrap_or_else(|err| err.to_compile_error())
25        .into()
26}
27
28/// Derive code that describes this type.
29#[proc_macro_derive(Describe, attributes(sbor))]
30pub fn describe(input: TokenStream) -> TokenStream {
31    sbor_derive_common::describe::handle_describe(proc_macro2::TokenStream::from(input), None)
32        .unwrap_or_else(|err| err.to_compile_error())
33        .into()
34}
35
36/// A shortcut for [`Categorize`], [`Encode`], [`Decode`], and [`Describe`] derives.
37///
38#[proc_macro_derive(Sbor, attributes(sbor))]
39pub fn sbor(input: TokenStream) -> TokenStream {
40    sbor_derive_common::sbor::handle_sbor(proc_macro2::TokenStream::from(input), None, None)
41        .unwrap_or_else(|err| err.to_compile_error())
42        .into()
43}
44
45/// An empty derive which exists solely to allow the helper "sbor" attribute
46/// to be used without generating a compile error.
47///
48/// The intended use-case is as a utility for building other macros,
49/// which wish to add sbor attribute annotations to types for when they do
50/// use an Sbor derive - but wish to avoid the following error when they don't:
51/// ```text
52/// error: cannot find attribute `sbor` in this scope
53/// ```
54///
55/// Ideally this would output an empty token stream, but instead we
56/// return a simply comment, to avoid the proc macro system thinking
57/// the macro build has broken and returning this error:
58/// ```text
59/// proc macro `PermitSborAttributes` not expanded: internal error
60/// ```
61#[proc_macro_derive(PermitSborAttributes, attributes(sbor))]
62pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream {
63    TokenStream::from_str("// Empty PermitSborAttributes expansion").unwrap()
64}
65
66const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind";
67const BASIC_CUSTOM_TYPE_KIND: &str = "sbor::NoCustomTypeKind";
68const BASIC_CUSTOM_SCHEMA: &str = "sbor::NoCustomSchema";
69
70/// Derive code that returns the value kind - specifically for Basic SBOR.
71#[proc_macro_derive(BasicCategorize, attributes(sbor))]
72pub fn basic_categorize(input: TokenStream) -> TokenStream {
73    sbor_derive_common::categorize::handle_categorize(
74        proc_macro2::TokenStream::from(input),
75        Some(BASIC_CUSTOM_VALUE_KIND),
76    )
77    .unwrap_or_else(|err| err.to_compile_error())
78    .into()
79}
80
81/// Derive code that encodes this data structure - specifically for Basic SBOR.
82#[proc_macro_derive(BasicEncode, attributes(sbor))]
83pub fn basic_encode(input: TokenStream) -> TokenStream {
84    sbor_derive_common::encode::handle_encode(
85        proc_macro2::TokenStream::from(input),
86        Some(BASIC_CUSTOM_VALUE_KIND),
87    )
88    .unwrap_or_else(|err| err.to_compile_error())
89    .into()
90}
91
92/// Derive code that decodes this data structure from a byte array - specifically for Basic SBOR.
93#[proc_macro_derive(BasicDecode, attributes(sbor))]
94pub fn basic_decode(input: TokenStream) -> TokenStream {
95    sbor_derive_common::decode::handle_decode(
96        proc_macro2::TokenStream::from(input),
97        Some(BASIC_CUSTOM_VALUE_KIND),
98    )
99    .unwrap_or_else(|err| err.to_compile_error())
100    .into()
101}
102
103/// Derive code that describes the type - specifically for Basic SBOR.
104#[proc_macro_derive(BasicDescribe, attributes(sbor))]
105pub fn basic_describe(input: TokenStream) -> TokenStream {
106    sbor_derive_common::describe::handle_describe(
107        proc_macro2::TokenStream::from(input),
108        Some(BASIC_CUSTOM_TYPE_KIND),
109    )
110    .unwrap_or_else(|err| err.to_compile_error())
111    .into()
112}
113
114/// A shortcut for [`BasicCategorize`], [`BasicEncode`], [`BasicDecode`], and [`BasicDescribe`] derives.
115///
116#[proc_macro_derive(BasicSbor, attributes(sbor))]
117pub fn basic_sbor(input: TokenStream) -> TokenStream {
118    sbor_derive_common::sbor::handle_sbor(
119        proc_macro2::TokenStream::from(input),
120        Some(BASIC_CUSTOM_VALUE_KIND),
121        Some(BASIC_CUSTOM_TYPE_KIND),
122    )
123    .unwrap_or_else(|err| err.to_compile_error())
124    .into()
125}
126
127/// A macro for outputting tests and marker traits to assert that a type has maintained its shape over time.
128///
129/// There are two types of assertion modes:
130/// * `fixed` mode is used to ensure that a type is unchanged.
131/// * `backwards_compatible` mode is used when multiple versions of the type are permissible, but
132///   newer versions of the type must be compatible with the older version where they align.
133///   This mode (A) ensures that the type's current schema is equivalent to the latest version, and
134///   (B) ensures that each of the schemas is a strict extension of the previous mode.
135///
136/// ## Initial schema generation and regeneration
137///
138/// To output a generated schema, temporarily add a `generate` parameter or a `regenerate` parameter,
139/// after the `fixed` or `backwards_compatible` parameter, and then run the created test.
140/// If using Rust Analyzer this can be run from the IDE, or it can be run via `cargo test`.
141///
142/// To protect against accidentally doing the wrong thing, `generate` can only be used for initial generation,
143/// whereas `regenerate` can only be used for replacing an existing generation.
144///
145/// If a "FILE:.." path is specified, it will (re)generate that file, else it will output to the console:
146/// * In `fixed` mode, this will (re)generate against the given schema location.
147/// * In `backwards_compatible` mode, this will (re)generate against the latest schema location (the last in the list).
148///
149/// The test will then panic to ensure it fails, and can't be left accidentally in (re)generate state.
150///
151/// ```ignore
152/// # // Ignored because the generated code references sbor which can't be imported
153/// # // by the doctest framework, because it doesn't know what those crates are
154/// # extern crate sbor_derive;
155/// # use sbor_derive::*;
156/// #
157/// #[derive(BasicSbor, BasicSborAssertion)]
158/// #[sbor_assert(fixed("FILE:MyType-schema-v1.txt"), generate)]
159/// struct MyType {
160///     // ...
161/// }
162/// ```
163///
164/// ## Fixed schema verification
165///
166/// To verify the type's schema is unchanged, do:
167/// ```ignore
168/// # // Ignored because the generated code references sbor which can't be imported
169/// # // by the doctest framework, because it doesn't know what those crates are
170/// # extern crate sbor_derive;
171/// # use sbor_derive::*;
172/// #
173/// #[derive(BasicSbor, BasicSborAssertion)]
174/// #[sbor_assert(fixed("FILE:MyType-schema-v1.txt"))]
175/// struct MyType {
176///     // ...
177/// }
178/// ```
179///
180/// Instead of `"FILE:X"`, you can also use `"INLINE:<hex>"`, `"CONST:<Constant>"` or `"EXPR:<Expression>"`
181/// where the expression (such as `generate_schema()`) has to generate a `SingleTypeSchema<NoCustomSchema>`.
182///
183/// ## Backwards compatibility verification
184///
185/// To allow multiple backwards-compatible versions, you can do this:
186/// ```ignore
187/// # // Ignored because the generated code references sbor which can't be imported
188/// # // by the doctest framework, because it doesn't know what those crates are
189/// # extern crate sbor_derive;
190/// # use sbor_derive::*;
191/// #
192/// #[derive(BasicSbor, BasicSborAssertion)]
193/// #[sbor_assert(backwards_compatible(
194///     version1 = "FILE:MyType-schema-v1.txt",
195///     version2 = "FILE:MyType-schema-v2.txt",
196/// ))]
197/// struct MyType {
198///     // ...
199/// }
200/// ```
201///
202/// Instead of `"FILE:X"`, you can also use `"INLINE:<hex>"`, `"CONST:<Constant>"` or `"EXPR:<Expression>"`
203/// where the expression (such as `generate_schema()`) has to generate a `SingleTypeSchema<NoCustomSchema>`.
204///
205/// If you wish to configure exactly which schemas are used for comparison of the current schema with
206/// the latest named schema; and each named schema with its predecessor, you can use:
207///
208/// ```ignore
209/// # // Ignored because the generated code references sbor which can't be imported
210/// # // by the doctest framework, because it doesn't know what those crates are
211/// # extern crate sbor_derive;
212/// # use sbor_derive::*;
213/// #
214/// #[sbor_assert(backwards_compatible("EXPR:<Expression>"))
215/// ```
216/// Where the expression (such as `params_builder()`) has to generate a `SingleTypeSchemaCompatibilityParameters<NoCustomSchema>`.
217///
218/// ## Custom settings
219/// By default, the `fixed` mode will use `SchemaComparisonSettings::require_equality()` and
220/// the `backwards_compatible` mode will use `SchemaComparisonSettings::require_equality()` for the check
221/// of `current` aginst the latest version, and `SchemaComparisonSettings::allow_extension()` for the
222/// checks between consecutive versions.
223///
224/// You may wish to change these:
225/// * If you just wish to ignore the equality of metadata such as names, you can use the
226///   `allow_name_changes` flag.
227/// * If you wish to override all settings, you can provide a constant containing your
228///   own SchemaComparisonSettings.
229/// * If you wish to specify a builder for settings, you can provide `"EXPR:|builder| builder.<stuff>"`
230/// * If for `backwards_compatible`, you wish to provide a separate configuration for the "latest" and
231///   "named versions" checks, you can use `settings(comparison_between_versions = \"EXPR:F1\", comparison_between_current_and_latest = \"EXPR:F2\") `
232///    
233///
234/// For example:
235/// ```ignore
236/// # // Ignored because the generated code references sbor which can't be imported
237/// # // by the doctest framework, because it doesn't know what those crates are
238/// # extern crate sbor_derive;
239/// # use sbor_derive::*;
240/// #
241/// #[derive(BasicSbor, BasicSborAssertion)]
242/// #[sbor_assert(
243///     fixed("FILE:MyType-schema-v1.txt"),
244///     settings(allow_name_changes),
245/// )]
246/// struct MyType {
247///     // ...
248/// }
249///
250/// #[derive(BasicSbor, BasicSborAssertion)]
251/// #[sbor_assert(
252///     backwards_compatible(
253///         v1 = "FILE:MyType-schema-v1.txt",
254///         v2 = "FILE:MyType-schema-v2.txt",
255///     ),
256///     settings(
257///         // We allow name changes between versions, but require the current schema to exactly match
258///         // the latest version (v2 in this case).
259///         // This could be useful to e.g. ensure that we have a fixed schema with the latest naming available.
260///         comparison_between_versions = "EXPR: |s| s.allow_all_name_changes()",
261///         comparison_between_current_and_latest = "EXPR: |s| s",
262///     ),
263/// )]
264/// struct MyType {
265///     // ...
266/// }
267/// ```
268#[proc_macro_derive(BasicSborAssertion, attributes(sbor_assert))]
269pub fn basic_sbor_assertion(input: TokenStream) -> TokenStream {
270    sbor_derive_common::sbor_assert::handle_sbor_assert_derive(
271        proc_macro2::TokenStream::from(input),
272        BASIC_CUSTOM_SCHEMA,
273    )
274    .unwrap_or_else(|err| err.to_compile_error())
275    .into()
276}