@layerzerolabs/common-utils-macros-stellar-contracts 0.2.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Cargo.toml +21 -0
  2. package/LICENSE +23 -0
  3. package/clippy.toml +7 -0
  4. package/package.json +37 -0
  5. package/rust-toolchain.toml +4 -0
  6. package/rustfmt.toml +15 -0
  7. package/src/auth.rs +95 -0
  8. package/src/contract_ttl.rs +92 -0
  9. package/src/error.rs +43 -0
  10. package/src/lib.rs +585 -0
  11. package/src/lz_contract.rs +105 -0
  12. package/src/rbac.rs +90 -0
  13. package/src/storage.rs +522 -0
  14. package/src/tests/auth.rs +230 -0
  15. package/src/tests/contract_ttl.rs +695 -0
  16. package/src/tests/error.rs +156 -0
  17. package/src/tests/lz_contract.rs +87 -0
  18. package/src/tests/mod.rs +11 -0
  19. package/src/tests/rbac.rs +523 -0
  20. package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +31 -0
  21. package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +39 -0
  22. package/src/tests/snapshots/common_macros__tests__auth__snapshot_only_auth_preserves_function_signature.snap +19 -0
  23. package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +77 -0
  24. package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contracttrait_code.snap +46 -0
  25. package/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  26. package/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +51 -0
  27. package/src/tests/snapshots/common_macros__tests__rbac__snapshot_authorizer_role.snap +21 -0
  28. package/src/tests/snapshots/common_macros__tests__rbac__snapshot_preserve_function_signature.snap +21 -0
  29. package/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +10 -0
  30. package/src/tests/snapshots/common_macros__tests__ttl_extendable__snapshot_generated_ttl_extendable_code.snap +8 -0
  31. package/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +28 -0
  32. package/src/tests/storage/extract_fields.rs +87 -0
  33. package/src/tests/storage/gen_accessor_methods.rs +223 -0
  34. package/src/tests/storage/gen_args.rs +65 -0
  35. package/src/tests/storage/gen_enum_variant.rs +78 -0
  36. package/src/tests/storage/gen_key.rs +108 -0
  37. package/src/tests/storage/gen_params.rs +105 -0
  38. package/src/tests/storage/generate_storage.rs +410 -0
  39. package/src/tests/storage/is_primitive_type.rs +48 -0
  40. package/src/tests/storage/mod.rs +16 -0
  41. package/src/tests/storage/parse_default.rs +164 -0
  42. package/src/tests/storage/parse_name.rs +158 -0
  43. package/src/tests/storage/parse_no_ttl_extension.rs +124 -0
  44. package/src/tests/storage/parse_storage_type.rs +174 -0
  45. package/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +412 -0
  46. package/src/tests/storage/storage_kind.rs +39 -0
  47. package/src/tests/storage/test_setup.rs +25 -0
  48. package/src/tests/storage/validate_attrs.rs +138 -0
  49. package/src/tests/storage/variant_config.rs +226 -0
  50. package/src/tests/test_helpers.rs +87 -0
  51. package/src/tests/ttl_configurable.rs +34 -0
  52. package/src/tests/ttl_extendable.rs +32 -0
  53. package/src/tests/upgradeable.rs +169 -0
  54. package/src/tests/utils.rs +267 -0
  55. package/src/ttl_configurable.rs +24 -0
  56. package/src/ttl_extendable.rs +28 -0
  57. package/src/upgradeable.rs +136 -0
  58. package/src/utils.rs +56 -0
package/src/rbac.rs ADDED
@@ -0,0 +1,90 @@
1
+ //! RBAC attribute macros for Stellar contracts.
2
+ //!
3
+ //! Provides `#[has_role]` and `#[only_role]` for role-based access control,
4
+ //! delegating to `utils::rbac::ensure_role`.
5
+
6
+ use crate::utils;
7
+ use proc_macro2::TokenStream;
8
+ use quote::{quote, ToTokens};
9
+ use syn::parse_quote;
10
+ use syn::{
11
+ parse::{Parse, ParseStream},
12
+ Expr, FnArg, Ident, ItemFn, Pat, Token, Type,
13
+ };
14
+
15
+ /// Helper that generates the role check for both `has_role` and `only_role`.
16
+ /// If `require_auth` is true, also injects `account.require_auth()`.
17
+ pub fn generate_role_check(args: TokenStream, input: TokenStream, require_auth: bool) -> TokenStream {
18
+ let HasRoleArgs { param, role } =
19
+ syn::parse2(args).unwrap_or_else(|e| panic!("failed to parse has_role/only_role args: {}", e));
20
+ let mut input_fn: ItemFn = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse function: {}", e));
21
+
22
+ let is_address_ref = validate_address_type(&input_fn, &param);
23
+ let param_ref = if is_address_ref { quote!(#param) } else { quote!(&#param) };
24
+
25
+ let env_param = utils::expect_env_param(&input_fn.sig.inputs);
26
+ let env_ref = env_param.as_ref_tokens();
27
+
28
+ // Insert the role check at the beginning of the function body
29
+ input_fn.block.stmts.insert(
30
+ 0,
31
+ parse_quote!(utils::rbac::ensure_role::<Self>(#env_ref, &soroban_sdk::Symbol::new(#env_ref, #role), #param_ref);),
32
+ );
33
+ if require_auth {
34
+ input_fn.block.stmts.insert(1, parse_quote!(#param.require_auth();));
35
+ }
36
+ input_fn.into_token_stream()
37
+ }
38
+
39
+ struct HasRoleArgs {
40
+ param: Ident,
41
+ role: Expr,
42
+ }
43
+
44
+ impl Parse for HasRoleArgs {
45
+ fn parse(input: ParseStream) -> syn::Result<Self> {
46
+ // Parse the parameter name (the account identifier to check)
47
+ let param: Ident = input.parse()?;
48
+ // Expect a comma separator between param and role
49
+ input.parse::<Token![,]>()?;
50
+ // Parse the role expression (e.g., a string literal or constant)
51
+ let role: Expr = input.parse()?;
52
+ Ok(HasRoleArgs { param, role })
53
+ }
54
+ }
55
+
56
+ /// Looks up `param_name` in the function signature and validates that its type
57
+ /// is `Address` or `&Address`. Returns `true` when the parameter is a reference,
58
+ /// so the caller knows whether an extra `&` is needed when forwarding it.
59
+ ///
60
+ /// Panics at macro-expansion time if the parameter doesn't exist.
61
+ fn validate_address_type(func: &ItemFn, param_name: &Ident) -> bool {
62
+ for arg in &func.sig.inputs {
63
+ let FnArg::Typed(pat_type) = arg else { continue };
64
+ let Pat::Ident(pat_ident) = &*pat_type.pat else { continue };
65
+ if pat_ident.ident != *param_name {
66
+ continue;
67
+ }
68
+ return match &*pat_type.ty {
69
+ Type::Reference(r) => {
70
+ assert_is_address(&r.elem, param_name);
71
+ true
72
+ }
73
+ ty => {
74
+ assert_is_address(ty, param_name);
75
+ false
76
+ }
77
+ };
78
+ }
79
+ panic!("Parameter `{param_name}` not found in function signature");
80
+ }
81
+
82
+ /// Asserts that the type path resolves to `Address`, panicking otherwise.
83
+ fn assert_is_address(ty: &Type, param_name: &Ident) {
84
+ let Type::Path(tp) = ty else {
85
+ panic!("Parameter `{param_name}` must be of type `Address` or `&Address`");
86
+ };
87
+ if tp.path.segments.last().is_none_or(|s| s.ident != "Address") {
88
+ panic!("Parameter `{param_name}` must be of type `Address` or `&Address`");
89
+ }
90
+ }
package/src/storage.rs ADDED
@@ -0,0 +1,522 @@
1
+ //! Storage macro implementation for Stellar smart contracts.
2
+ //!
3
+ //! Generates strongly-typed storage API from enum variants with automatic TTL management.
4
+
5
+ use heck::ToSnakeCase;
6
+ use itertools::Itertools;
7
+ use proc_macro2::{Ident, TokenStream};
8
+ use quote::{format_ident, quote};
9
+ use syn::{Attribute, Expr, Fields, FieldsNamed, Meta, Type, Variant};
10
+
11
+ // ============================================================================
12
+ // Public API
13
+ // ============================================================================
14
+
15
+ /// Generates the storage API from the `#[storage]` attribute macro.
16
+ pub fn generate_storage(input: TokenStream) -> TokenStream {
17
+ let item_enum: syn::ItemEnum = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
18
+ let enum_name = &item_enum.ident;
19
+ let vis = &item_enum.vis;
20
+
21
+ let variants: Vec<_> = item_enum.variants.iter().map(gen_enum_variant).collect();
22
+ let methods: Vec<_> = item_enum.variants.iter().map(|v| gen_accessor_methods(enum_name, v)).collect();
23
+
24
+ quote! {
25
+ #[soroban_sdk::contracttype]
26
+ #vis enum #enum_name { #(#variants,)* }
27
+
28
+ impl #enum_name { #(#methods)* }
29
+ }
30
+ }
31
+
32
+ // ============================================================================
33
+ // Types
34
+ // ============================================================================
35
+
36
+ /// Storage kind: instance, persistent, or temporary.
37
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
38
+ enum StorageKind {
39
+ Instance,
40
+ Persistent,
41
+ Temporary,
42
+ }
43
+
44
+ impl StorageKind {
45
+ fn name(self) -> &'static str {
46
+ match self {
47
+ Self::Instance => "instance",
48
+ Self::Persistent => "persistent",
49
+ Self::Temporary => "temporary",
50
+ }
51
+ }
52
+
53
+ /// Generates `env.storage().{kind}()`.
54
+ fn accessor(self) -> TokenStream {
55
+ let method = format_ident!("{}", self.name());
56
+ quote! { env.storage().#method() }
57
+ }
58
+ }
59
+
60
+ /// Parsed configuration for a storage enum variant.
61
+ #[derive(Debug, Clone)]
62
+ struct VariantConfig {
63
+ name: String,
64
+ kind: StorageKind,
65
+ value_type: Type,
66
+ default_value: Option<Expr>,
67
+ auto_ttl: bool,
68
+ }
69
+
70
+ impl VariantConfig {
71
+ /// Returns (getter, setter, remover, set_or_remove, has, ttl_extender) function names.
72
+ fn method_names(&self) -> (Ident, Ident, Ident, Ident, Ident, Ident) {
73
+ let base = &self.name;
74
+ (
75
+ format_ident!("{}", base),
76
+ format_ident!("set_{}", base),
77
+ format_ident!("remove_{}", base),
78
+ format_ident!("set_or_remove_{}", base),
79
+ format_ident!("has_{}", base),
80
+ format_ident!("extend_{}_ttl", base),
81
+ )
82
+ }
83
+ }
84
+
85
+ // ============================================================================
86
+ // Enum Variant Code Generation
87
+ // ============================================================================
88
+
89
+ /// Generates contracttype enum variant: `Variant` or `Variant(Type1, Type2, ...)`.
90
+ fn gen_enum_variant(variant: &Variant) -> TokenStream {
91
+ let name = &variant.ident;
92
+ match &variant.fields {
93
+ Fields::Unit => quote! { #name },
94
+ Fields::Named(FieldsNamed { named, .. }) => {
95
+ let types = named.iter().map(|f| &f.ty);
96
+ quote! { #name(#(#types),*) }
97
+ }
98
+ _ => panic!("only unit variants or named fields are supported in storage enums"),
99
+ }
100
+ }
101
+
102
+ /// Generates all storage accessor methods for a variant.
103
+ fn gen_accessor_methods(enum_name: &Ident, variant: &Variant) -> TokenStream {
104
+ let config = VariantConfig::try_from(variant)
105
+ .unwrap_or_else(|e| panic!("failed to parse storage variant for {}: {}", variant.ident, e));
106
+
107
+ let (getter, setter, remover, set_or_remove, has, ttl_extender) = config.method_names();
108
+ let params = gen_params(variant);
109
+ let args = gen_args(variant);
110
+ let key = gen_key(enum_name, variant);
111
+ let accessor = config.kind.accessor();
112
+ let value_type = &config.value_type;
113
+
114
+ // Auto TTL extension call — emitted after reads/writes for persistent storage.
115
+ // `Option<TokenStream>` integrates directly with `quote!` (None emits nothing).
116
+ let extend_ttl = config.auto_ttl.then(|| {
117
+ quote! { utils::ttl_configurable::extend_persistent_ttl(env, &key); }
118
+ });
119
+
120
+ // Getter: returns the value directly (with default) or wrapped in Option.
121
+ let (ret_type, ret_expr) = match &config.default_value {
122
+ Some(default) => (quote! { #value_type }, quote! { value.unwrap_or_else(|| #default) }),
123
+ None => (quote! { Option<#value_type> }, quote! { value }),
124
+ };
125
+ let ttl_on_get = extend_ttl.as_ref().map(|call| quote! { if value.is_some() { #call } });
126
+
127
+ // Has: conditionally extend TTL when key exists.
128
+ let has_body = match &extend_ttl {
129
+ Some(call) => quote! { let exists = #accessor.has(&key); if exists { #call } exists },
130
+ None => quote! { #accessor.has(&key) },
131
+ };
132
+
133
+ // TTL extender method — only for persistent/temporary storage (instance has no per-key TTL).
134
+ let ttl_extender_method = (config.kind != StorageKind::Instance).then(|| {
135
+ quote! {
136
+ pub fn #ttl_extender(#params, threshold: u32, extend_to: u32) {
137
+ let key = #key;
138
+ #accessor.extend_ttl(&key, threshold, extend_to);
139
+ }
140
+ }
141
+ });
142
+
143
+ quote! {
144
+ pub fn #getter(#params) -> #ret_type {
145
+ let key = #key;
146
+ let value = #accessor.get::<_, #value_type>(&key);
147
+ #ttl_on_get
148
+ #ret_expr
149
+ }
150
+
151
+ pub fn #setter(#params, value: &#value_type) {
152
+ let key = #key;
153
+ #accessor.set(&key, value);
154
+ #extend_ttl
155
+ }
156
+
157
+ pub fn #remover(#params) {
158
+ let key = #key;
159
+ #accessor.remove(&key);
160
+ }
161
+
162
+ pub fn #set_or_remove(#params, value: &Option<#value_type>) {
163
+ match value.as_ref() {
164
+ Some(v) => Self::#setter(#args, v),
165
+ None => Self::#remover(#args),
166
+ }
167
+ }
168
+
169
+ pub fn #has(#params) -> bool {
170
+ let key = #key;
171
+ #has_body
172
+ }
173
+
174
+ #ttl_extender_method
175
+ }
176
+ }
177
+
178
+ // ============================================================================
179
+ // Parameter & Key Generation Helpers
180
+ // ============================================================================
181
+
182
+ /// Extracts (name, type) pairs from variant fields.
183
+ fn extract_fields(variant: &Variant) -> Vec<(&Ident, &Type)> {
184
+ match &variant.fields {
185
+ Fields::Unit => vec![],
186
+ Fields::Named(named) => named.named.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect(),
187
+ _ => panic!("only unit variants or named fields are supported in storage enums"),
188
+ }
189
+ }
190
+
191
+ /// Generates function parameters: `env: &Env` or `env: &Env, field1: Type1, ...`.
192
+ fn gen_params(variant: &Variant) -> TokenStream {
193
+ let fields = extract_fields(variant);
194
+ if fields.is_empty() {
195
+ quote! { env: &soroban_sdk::Env }
196
+ } else {
197
+ let params = fields.iter().map(|(name, ty)| {
198
+ if is_primitive_type(ty) {
199
+ quote! { #name: #ty }
200
+ } else {
201
+ quote! { #name: &#ty }
202
+ }
203
+ });
204
+ quote! { env: &soroban_sdk::Env, #(#params),* }
205
+ }
206
+ }
207
+
208
+ /// Generates function arguments: `env` or `env, field1, field2, ...`.
209
+ fn gen_args(variant: &Variant) -> TokenStream {
210
+ let fields = extract_fields(variant);
211
+ if fields.is_empty() {
212
+ quote! { env }
213
+ } else {
214
+ let names = fields.iter().map(|(name, _)| name);
215
+ quote! { env, #(#names),* }
216
+ }
217
+ }
218
+
219
+ /// Generates storage key: `Enum::Variant` or `Enum::Variant(field1.clone(), ...)`.
220
+ fn gen_key(enum_name: &Ident, variant: &Variant) -> TokenStream {
221
+ let variant_ident = &variant.ident;
222
+ let fields = extract_fields(variant);
223
+
224
+ if fields.is_empty() {
225
+ quote! { #enum_name::#variant_ident }
226
+ } else {
227
+ let args = fields.iter().map(|(name, ty)| {
228
+ if is_primitive_type(ty) {
229
+ quote! { #name }
230
+ } else {
231
+ quote! { #name.clone() }
232
+ }
233
+ });
234
+ quote! { #enum_name::#variant_ident(#(#args),*) }
235
+ }
236
+ }
237
+
238
+ /// Checks if a type is a primitive (pass-by-value) type.
239
+ fn is_primitive_type(ty: &Type) -> bool {
240
+ // https://developers.stellar.org/docs/learn/fundamentals/contract-development/types/built-in-types#primitive-types
241
+ const PRIMITIVES: &[&str] = &["u32", "i32", "u64", "i64", "u128", "i128", "bool"];
242
+ matches!(ty, Type::Path(p) if p.path.segments.len() == 1
243
+ && PRIMITIVES.contains(&p.path.segments[0].ident.to_string().as_str()))
244
+ }
245
+
246
+ // ============================================================================
247
+ // Attribute Parsing
248
+ // ============================================================================
249
+
250
+ /// Known attributes for storage variants ("doc" allows /// comments).
251
+ const KNOWN_ATTRS: &[&str] = &["doc", "instance", "persistent", "temporary", "default", "name", "no_ttl_extension"];
252
+
253
+ impl TryFrom<&Variant> for VariantConfig {
254
+ type Error = String;
255
+
256
+ fn try_from(variant: &Variant) -> Result<Self, Self::Error> {
257
+ let attrs = &variant.attrs;
258
+ validate_attrs(attrs, &variant.ident)?;
259
+
260
+ let (kind, value_type) = parse_storage_type(attrs)?;
261
+ let default_value = parse_default(attrs)?;
262
+ let name = parse_name(attrs)?.unwrap_or_else(|| variant.ident.to_string().to_snake_case());
263
+ let no_ttl_extension = parse_no_ttl_extension(attrs)?;
264
+
265
+ if no_ttl_extension && kind != StorageKind::Persistent {
266
+ return Err("#[no_ttl_extension] can only be used with #[persistent(...)] storage".to_string());
267
+ }
268
+
269
+ Ok(Self {
270
+ name,
271
+ kind,
272
+ value_type,
273
+ default_value,
274
+ auto_ttl: kind == StorageKind::Persistent && !no_ttl_extension,
275
+ })
276
+ }
277
+ }
278
+
279
+ fn validate_attrs(attrs: &[Attribute], variant_ident: &Ident) -> Result<(), String> {
280
+ for attr in attrs {
281
+ let path = attr.path();
282
+ let name = path.get_ident().map(|i| i.to_string()).unwrap_or_else(|| quote!(#path).to_string());
283
+
284
+ if !KNOWN_ATTRS.contains(&name.as_str()) {
285
+ return Err(format!(
286
+ "unknown attribute '{}' on variant '{}'. Supported attributes are: {}",
287
+ name,
288
+ variant_ident,
289
+ KNOWN_ATTRS.join(", ")
290
+ ));
291
+ }
292
+ }
293
+ Ok(())
294
+ }
295
+
296
+ fn parse_storage_type(attrs: &[Attribute]) -> Result<(StorageKind, Type), String> {
297
+ attrs
298
+ .iter()
299
+ .filter_map(|attr| {
300
+ let ident = attr.path().get_ident()?;
301
+ let kind = match ident.to_string().as_str() {
302
+ "instance" => StorageKind::Instance,
303
+ "persistent" => StorageKind::Persistent,
304
+ "temporary" => StorageKind::Temporary,
305
+ _ => return None,
306
+ };
307
+ let value_type = attr
308
+ .parse_args::<Type>()
309
+ .unwrap_or_else(|e| panic!("failed to parse storage type for #[{}(...)] : {}", ident, e));
310
+ Some((kind, value_type))
311
+ })
312
+ .exactly_one()
313
+ .map_err(|e| {
314
+ format!(
315
+ "storage type must be specified exactly once as \
316
+ '#[instance(Type)]', '#[persistent(Type)]', or '#[temporary(Type)]': {}",
317
+ e
318
+ )
319
+ })
320
+ }
321
+
322
+ fn parse_default(attrs: &[Attribute]) -> Result<Option<Expr>, String> {
323
+ attrs
324
+ .iter()
325
+ .filter(|attr| attr.path().is_ident("default"))
326
+ .at_most_one()
327
+ .map_err(|e| format!("multiple default values specified: {}", e))?
328
+ .map(|attr| attr.parse_args::<Expr>())
329
+ .transpose()
330
+ .map_err(|e| format!("failed to parse default value: {}", e))
331
+ }
332
+
333
+ fn parse_name(attrs: &[Attribute]) -> Result<Option<String>, String> {
334
+ attrs
335
+ .iter()
336
+ .filter(|attr| attr.path().is_ident("name"))
337
+ .at_most_one()
338
+ .map_err(|e| format!("multiple name attributes specified: {}", e))?
339
+ .map(|attr| attr.parse_args::<syn::LitStr>().map(|lit| lit.value()))
340
+ .transpose()
341
+ .map_err(|e| format!("failed to parse name attribute: {}", e))
342
+ }
343
+
344
+ fn parse_no_ttl_extension(attrs: &[Attribute]) -> Result<bool, String> {
345
+ let attr = attrs
346
+ .iter()
347
+ .filter(|attr| attr.path().is_ident("no_ttl_extension"))
348
+ .at_most_one()
349
+ .map_err(|e| format!("multiple #[no_ttl_extension] attributes specified: {}", e))?;
350
+
351
+ // Reject `#[no_ttl_extension(...)]` / `#[no_ttl_extension = ...]`
352
+ match attr {
353
+ None => Ok(false),
354
+ Some(attr) if matches!(attr.meta, Meta::Path(_)) => Ok(true),
355
+ Some(_) => Err("#[no_ttl_extension] does not accept arguments".to_string()),
356
+ }
357
+ }
358
+
359
+ // ============================================================================
360
+ // Test-only Functions
361
+ // ============================================================================
362
+
363
+ #[cfg(test)]
364
+ pub(crate) mod test {
365
+ use super::*;
366
+
367
+ // ========================================================================
368
+ // VariantConfigInfo - wrapper struct for test access
369
+ // ========================================================================
370
+
371
+ /// Test-only wrapper struct exposing VariantConfig data without exposing the type.
372
+ #[derive(Debug, Clone)]
373
+ pub struct VariantConfigInfo {
374
+ pub name: String,
375
+ pub kind_name: String,
376
+ pub value_type: String,
377
+ pub has_default: bool,
378
+ pub auto_ttl: bool,
379
+ }
380
+
381
+ // ========================================================================
382
+ // StorageKind wrapper functions
383
+ // ========================================================================
384
+
385
+ /// Returns the name for Instance storage kind.
386
+ pub fn storage_kind_instance_name() -> &'static str {
387
+ StorageKind::Instance.name()
388
+ }
389
+
390
+ /// Returns the name for Persistent storage kind.
391
+ pub fn storage_kind_persistent_name() -> &'static str {
392
+ StorageKind::Persistent.name()
393
+ }
394
+
395
+ /// Returns the name for Temporary storage kind.
396
+ pub fn storage_kind_temporary_name() -> &'static str {
397
+ StorageKind::Temporary.name()
398
+ }
399
+
400
+ /// Returns the accessor TokenStream for Instance storage kind.
401
+ pub fn storage_kind_instance_accessor() -> TokenStream {
402
+ StorageKind::Instance.accessor()
403
+ }
404
+
405
+ /// Returns the accessor TokenStream for Persistent storage kind.
406
+ pub fn storage_kind_persistent_accessor() -> TokenStream {
407
+ StorageKind::Persistent.accessor()
408
+ }
409
+
410
+ /// Returns the accessor TokenStream for Temporary storage kind.
411
+ pub fn storage_kind_temporary_accessor() -> TokenStream {
412
+ StorageKind::Temporary.accessor()
413
+ }
414
+
415
+ // ========================================================================
416
+ // VariantConfig wrapper functions
417
+ // ========================================================================
418
+
419
+ /// Gets VariantConfig info from a Variant.
420
+ pub fn get_variant_config_for_test(variant: &Variant) -> Result<VariantConfigInfo, String> {
421
+ VariantConfig::try_from(variant).map(|c| {
422
+ let value_type = c.value_type;
423
+ VariantConfigInfo {
424
+ name: c.name,
425
+ kind_name: c.kind.name().to_string(),
426
+ value_type: quote!(#value_type).to_string(),
427
+ has_default: c.default_value.is_some(),
428
+ auto_ttl: c.auto_ttl,
429
+ }
430
+ })
431
+ }
432
+
433
+ /// Gets method names from a Variant as strings.
434
+ pub fn get_variant_method_names_for_test(
435
+ variant: &Variant,
436
+ ) -> Result<(String, String, String, String, String, String), String> {
437
+ VariantConfig::try_from(variant).map(|c| {
438
+ let (getter, setter, remover, set_or_remove, has, ttl_extender) = c.method_names();
439
+ (
440
+ getter.to_string(),
441
+ setter.to_string(),
442
+ remover.to_string(),
443
+ set_or_remove.to_string(),
444
+ has.to_string(),
445
+ ttl_extender.to_string(),
446
+ )
447
+ })
448
+ }
449
+
450
+ // ========================================================================
451
+ // parse_storage_type wrapper - returns kind name instead of enum
452
+ // ========================================================================
453
+
454
+ /// Test-only wrapper for parse_storage_type that returns kind name as string.
455
+ pub fn parse_storage_type_for_test(attrs: &[Attribute]) -> Result<(String, Type), String> {
456
+ parse_storage_type(attrs).map(|(kind, ty)| (kind.name().to_string(), ty))
457
+ }
458
+
459
+ // ========================================================================
460
+ // Other wrapper functions
461
+ // ========================================================================
462
+
463
+ /// Test-only wrapper for is_primitive_type.
464
+ pub fn is_primitive_type_for_test(ty: &Type) -> bool {
465
+ is_primitive_type(ty)
466
+ }
467
+
468
+ /// Test-only wrapper for extract_fields.
469
+ pub fn extract_fields_for_test(variant: &Variant) -> Vec<(&Ident, &Type)> {
470
+ extract_fields(variant)
471
+ }
472
+
473
+ /// Test-only wrapper for gen_params.
474
+ pub fn gen_params_for_test(variant: &Variant) -> TokenStream {
475
+ gen_params(variant)
476
+ }
477
+
478
+ /// Test-only wrapper for gen_args.
479
+ pub fn gen_args_for_test(variant: &Variant) -> TokenStream {
480
+ gen_args(variant)
481
+ }
482
+
483
+ /// Test-only wrapper for gen_key.
484
+ pub fn gen_key_for_test(enum_name: &Ident, variant: &Variant) -> TokenStream {
485
+ gen_key(enum_name, variant)
486
+ }
487
+
488
+ /// Test-only wrapper for gen_enum_variant.
489
+ pub fn gen_enum_variant_for_test(variant: &Variant) -> TokenStream {
490
+ gen_enum_variant(variant)
491
+ }
492
+
493
+ /// Test-only wrapper for gen_accessor_methods.
494
+ pub fn gen_accessor_methods_for_test(enum_name: &Ident, variant: &Variant) -> TokenStream {
495
+ gen_accessor_methods(enum_name, variant)
496
+ }
497
+
498
+ /// Test-only wrapper for validate_attrs.
499
+ pub fn validate_attrs_for_test(attrs: &[Attribute], variant_ident: &Ident) -> Result<(), String> {
500
+ validate_attrs(attrs, variant_ident)
501
+ }
502
+
503
+ /// Test-only wrapper for parse_default.
504
+ pub fn parse_default_for_test(attrs: &[Attribute]) -> Result<Option<Expr>, String> {
505
+ parse_default(attrs)
506
+ }
507
+
508
+ /// Test-only wrapper for parse_name.
509
+ pub fn parse_name_for_test(attrs: &[Attribute]) -> Result<Option<String>, String> {
510
+ parse_name(attrs)
511
+ }
512
+
513
+ /// Test-only wrapper for parse_no_ttl_extension.
514
+ pub fn parse_no_ttl_extension_for_test(attrs: &[Attribute]) -> Result<bool, String> {
515
+ parse_no_ttl_extension(attrs)
516
+ }
517
+
518
+ /// Returns the list of known attributes for testing.
519
+ pub fn known_attrs_for_test() -> &'static [&'static str] {
520
+ KNOWN_ATTRS
521
+ }
522
+ }