@layerzerolabs/protocol-stellar-v2 0.2.20 → 0.2.22
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.
- package/.turbo/turbo-build.log +783 -802
- package/.turbo/turbo-lint.log +320 -157
- package/.turbo/turbo-test.log +1414 -1457
- package/Cargo.lock +109 -108
- package/Cargo.toml +32 -18
- package/contracts/common-macros/Cargo.toml +7 -7
- package/contracts/common-macros/src/auth.rs +18 -37
- package/contracts/common-macros/src/contract_ttl.rs +2 -2
- package/contracts/common-macros/src/lib.rs +27 -10
- package/contracts/common-macros/src/lz_contract.rs +38 -7
- package/contracts/common-macros/src/storage.rs +251 -292
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +6 -12
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +12 -17
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +2 -7
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +20 -9
- package/contracts/common-macros/src/tests/upgradeable.rs +26 -4
- package/contracts/common-macros/src/ttl_configurable.rs +2 -10
- package/contracts/common-macros/src/ttl_extendable.rs +2 -10
- package/contracts/common-macros/src/upgradeable.rs +56 -15
- package/contracts/common-macros/src/utils.rs +0 -9
- package/contracts/endpoint-v2/src/lib.rs +3 -2
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +2 -2
- package/contracts/endpoint-v2/src/tests/endpoint_v2/lz_receive_alert.rs +3 -3
- package/contracts/endpoint-v2/src/tests/endpoint_v2/send.rs +4 -4
- package/contracts/endpoint-v2/src/tests/endpoint_v2/set_delegate.rs +17 -5
- package/contracts/endpoint-v2/src/tests/endpoint_v2/set_zro.rs +4 -4
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +2 -2
- package/contracts/endpoint-v2/src/tests/message_lib_manager/register_library.rs +2 -2
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +6 -6
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +67 -37
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_send_library.rs +5 -5
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library.rs +44 -54
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library_timeout.rs +7 -7
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_send_library.rs +8 -8
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +3 -3
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +4 -4
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +3 -3
- package/contracts/endpoint-v2/src/tests/messaging_composer/clear_compose.rs +2 -2
- package/contracts/endpoint-v2/src/tests/messaging_composer/lz_compose_alert.rs +3 -3
- package/contracts/endpoint-v2/src/tests/messaging_composer/send_compose.rs +2 -2
- package/contracts/layerzero-views/Cargo.toml +0 -1
- package/contracts/layerzero-views/src/layerzero_view.rs +1 -13
- package/contracts/macro-integration-tests/Cargo.toml +5 -15
- package/contracts/macro-integration-tests/tests/runtime/oapp/mod.rs +48 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/oapp_core.rs +170 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +154 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +338 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +435 -0
- package/contracts/macro-integration-tests/tests/runtime.rs +1 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.rs +8 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.stderr +5 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.rs +8 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.stderr +71 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.rs +10 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.stderr +5 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.rs +8 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.stderr +5 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.rs +8 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.stderr +5 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +38 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +96 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/minimal_contract.rs +64 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/struct_with_fields.rs +46 -0
- package/contracts/macro-integration-tests/tests/ui/ownable/fail/only_auth_missing_env.stderr +8 -0
- package/contracts/macro-integration-tests/tests/ui/ownable/pass/namespacing_and_imports.rs +1 -1
- package/contracts/macro-integration-tests/tests/ui/ownable/pass/only_auth_env_param_variants.rs +1 -1
- package/contracts/macro-integration-tests/tests/ui_oapp.rs +11 -0
- package/contracts/message-libs/message-lib-common/Cargo.toml +0 -1
- package/contracts/message-libs/message-lib-common/src/errors.rs +1 -1
- package/contracts/message-libs/treasury/Cargo.toml +0 -2
- package/contracts/message-libs/treasury/src/tests/treasury_tests.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/verify.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +7 -27
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_executor_configs.rs +2 -2
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +2 -2
- package/contracts/oapps/counter/Cargo.toml +4 -6
- package/contracts/oapps/counter/integration_tests/utils.rs +19 -12
- package/contracts/oapps/oapp/src/errors.rs +1 -1
- package/contracts/oapps/oapp/src/interfaces/mod.rs +3 -0
- package/contracts/oapps/oapp/src/interfaces/oapp_msg_inspector.rs +47 -0
- package/contracts/oapps/oapp/src/lib.rs +1 -0
- package/contracts/oapps/oapp/src/macro_tests/test_macros.rs +4 -4
- package/contracts/oapps/oapp/src/oapp_core.rs +5 -5
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +12 -4
- package/contracts/oapps/oapp/src/oapp_receiver.rs +14 -9
- package/contracts/oapps/oapp/src/tests/mod.rs +4 -4
- package/contracts/oapps/oapp/src/tests/{test_oapp_core.rs → oapp_core.rs} +4 -4
- package/contracts/oapps/oapp/src/tests/{test_oapp_options_type3.rs → oapp_options_type3.rs} +3 -4
- package/contracts/oapps/oapp-macros/Cargo.toml +8 -4
- package/contracts/oapps/oapp-macros/src/generators.rs +9 -34
- package/contracts/oapps/oapp-macros/src/lib.rs +3 -0
- package/contracts/oapps/oapp-macros/src/tests/mod.rs +2 -0
- package/contracts/oapps/oapp-macros/src/tests/oapp.rs +88 -0
- package/contracts/oapps/oapp-macros/src/tests/parse_custom_impls.rs +86 -0
- package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +103 -0
- package/contracts/oapps/oft/integration-tests/utils.rs +28 -8
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +136 -74
- package/contracts/oapps/oft/src/extensions/pausable.rs +44 -10
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +170 -130
- package/contracts/oapps/oft/src/oft.rs +19 -12
- package/contracts/oapps/oft/src/oft_types/lock_unlock.rs +1 -1
- package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
- package/contracts/oapps/oft-core/Cargo.toml +1 -4
- package/contracts/oapps/oft-core/integration-tests/setup.rs +2 -2
- package/contracts/oapps/oft-core/integration-tests/utils.rs +21 -3
- package/contracts/oapps/oft-core/src/errors.rs +3 -2
- package/contracts/oapps/oft-core/src/events.rs +6 -0
- package/contracts/oapps/oft-core/src/lib.rs +1 -1
- package/contracts/oapps/oft-core/src/oft_core.rs +115 -60
- package/contracts/oapps/oft-core/src/storage.rs +7 -3
- package/contracts/oapps/oft-core/src/tests/mod.rs +1 -0
- package/contracts/oapps/oft-core/src/tests/test_decimals.rs +37 -2
- package/contracts/oapps/oft-core/src/tests/test_lz_receive.rs +2 -2
- package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +323 -0
- package/contracts/oapps/oft-core/src/tests/test_send.rs +2 -2
- package/contracts/oapps/oft-core/src/tests/test_utils.rs +59 -14
- package/contracts/utils/Cargo.toml +0 -1
- package/contracts/utils/src/errors.rs +1 -1
- package/contracts/utils/src/multisig.rs +17 -8
- package/contracts/utils/src/ownable.rs +6 -6
- package/contracts/utils/src/testing_utils.rs +124 -54
- package/contracts/utils/src/tests/multisig.rs +12 -12
- package/contracts/utils/src/tests/ownable.rs +6 -6
- package/contracts/utils/src/tests/testing_utils.rs +50 -167
- package/contracts/utils/src/tests/ttl_configurable.rs +5 -5
- package/contracts/utils/src/tests/upgradeable.rs +1 -1
- package/contracts/utils/src/ttl_configurable.rs +10 -4
- package/contracts/utils/src/upgradeable.rs +5 -5
- package/contracts/workers/dvn/Cargo.toml +5 -6
- package/contracts/workers/dvn/src/dvn.rs +2 -12
- package/contracts/workers/dvn-fee-lib/Cargo.toml +1 -1
- package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +37 -19
- package/contracts/workers/dvn-fee-lib/src/lib.rs +12 -2
- package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +15 -13
- package/contracts/workers/executor/Cargo.toml +3 -0
- package/contracts/workers/executor/src/executor.rs +2 -12
- package/contracts/workers/executor/src/lib.rs +2 -2
- package/contracts/workers/executor/src/tests/auth.rs +394 -0
- package/contracts/workers/executor/src/tests/executor.rs +410 -0
- package/contracts/workers/executor/src/tests/mod.rs +3 -0
- package/contracts/workers/executor/src/tests/setup.rs +250 -0
- package/contracts/workers/executor-fee-lib/Cargo.toml +5 -0
- package/contracts/workers/executor-fee-lib/src/executor_fee_lib.rs +1 -12
- package/contracts/workers/executor-fee-lib/src/lib.rs +8 -2
- package/contracts/workers/executor-helper/Cargo.toml +0 -1
- package/contracts/workers/price-feed/Cargo.toml +5 -0
- package/contracts/workers/price-feed/src/lib.rs +9 -4
- package/contracts/workers/price-feed/src/price_feed.rs +1 -11
- package/contracts/workers/worker/src/errors.rs +1 -1
- package/contracts/workers/worker/src/tests/setup.rs +1 -1
- package/contracts/workers/worker/src/tests/worker.rs +55 -41
- package/contracts/workers/worker/src/worker.rs +34 -25
- package/docs/error-spec.md +55 -0
- package/docs/layerzero-v2-on-stellar.md +447 -0
- package/docs/oapp-guide.md +212 -0
- package/docs/oft-guide.md +314 -0
- package/package.json +3 -3
- package/sdk/.turbo/turbo-test.log +260 -257
- package/sdk/dist/generated/bml.d.ts +3 -3
- package/sdk/dist/generated/bml.js +4 -4
- package/sdk/dist/generated/counter.d.ts +295 -295
- package/sdk/dist/generated/counter.js +43 -43
- package/sdk/dist/generated/dvn.d.ts +91 -91
- package/sdk/dist/generated/dvn.js +24 -24
- package/sdk/dist/generated/dvn_fee_lib.d.ts +92 -92
- package/sdk/dist/generated/dvn_fee_lib.js +25 -25
- package/sdk/dist/generated/endpoint.d.ts +99 -99
- package/sdk/dist/generated/endpoint.js +16 -16
- package/sdk/dist/generated/executor.d.ts +91 -91
- package/sdk/dist/generated/executor.js +24 -24
- package/sdk/dist/generated/executor_fee_lib.d.ts +92 -92
- package/sdk/dist/generated/executor_fee_lib.js +25 -25
- package/sdk/dist/generated/executor_helper.d.ts +3 -3
- package/sdk/dist/generated/executor_helper.js +4 -4
- package/sdk/dist/generated/layerzero_view.d.ts +186 -186
- package/sdk/dist/generated/layerzero_view.js +35 -35
- package/sdk/dist/generated/oft.d.ts +366 -352
- package/sdk/dist/generated/oft.js +74 -79
- package/sdk/dist/generated/price_feed.d.ts +198 -198
- package/sdk/dist/generated/price_feed.js +39 -39
- package/sdk/dist/generated/sml.d.ts +99 -99
- package/sdk/dist/generated/sml.js +16 -16
- package/sdk/dist/generated/treasury.d.ts +99 -99
- package/sdk/dist/generated/treasury.js +16 -16
- package/sdk/dist/generated/uln302.d.ts +99 -99
- package/sdk/dist/generated/uln302.js +16 -16
- package/sdk/dist/generated/upgrader.d.ts +3 -3
- package/sdk/dist/generated/upgrader.js +3 -3
- package/sdk/package.json +1 -1
- package/sdk/test/suites/localnet.ts +84 -20
- package/contracts/ERROR_SPEC.md +0 -51
- package/contracts/endpoint-v2/ARCHITECTURE.md +0 -233
- /package/contracts/oapps/oapp/src/tests/{test_oapp_receiver.rs → oapp_receiver.rs} +0 -0
- /package/contracts/oapps/oapp/src/tests/{test_oapp_sender.rs → oapp_sender.rs} +0 -0
|
@@ -6,198 +6,125 @@ use heck::ToSnakeCase;
|
|
|
6
6
|
use itertools::Itertools;
|
|
7
7
|
use proc_macro2::{Ident, TokenStream};
|
|
8
8
|
use quote::{format_ident, quote};
|
|
9
|
-
use syn::{Expr, Fields, FieldsNamed, Type, Variant};
|
|
9
|
+
use syn::{Attribute, Expr, Fields, FieldsNamed, Type, Variant};
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
|
-
//
|
|
12
|
+
// Public API
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
|
-
///
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Persistent(Type),
|
|
20
|
-
Temporary(Type),
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
impl StorageType {
|
|
24
|
-
fn value_type(&self) -> &Type {
|
|
25
|
-
match self {
|
|
26
|
-
Self::Instance(t) | Self::Persistent(t) | Self::Temporary(t) => t,
|
|
27
|
-
}
|
|
15
|
+
/// Generates the storage API from the `#[storage]` attribute macro.
|
|
16
|
+
pub fn generate_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
17
|
+
if !attr.is_empty() {
|
|
18
|
+
panic!("the #[storage] attribute does not accept arguments");
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
let item_enum: syn::ItemEnum = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
|
|
22
|
+
let enum_name = &item_enum.ident;
|
|
23
|
+
let vis = &item_enum.vis;
|
|
24
|
+
|
|
25
|
+
let variants: Vec<_> = item_enum.variants.iter().map(gen_enum_variant).collect();
|
|
26
|
+
let methods: Vec<_> = item_enum.variants.iter().map(|v| gen_accessor_methods(enum_name, v)).collect();
|
|
27
|
+
|
|
28
|
+
quote! {
|
|
29
|
+
#[soroban_sdk::contracttype]
|
|
30
|
+
#vis enum #enum_name { #(#variants,)* }
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
matches!(self, Self::Persistent(_))
|
|
32
|
+
impl #enum_name { #(#methods)* }
|
|
36
33
|
}
|
|
34
|
+
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
/// Checks if a type is a primitive (pass-by-value) type.
|
|
37
|
+
pub(crate) fn is_primitive_type(ty: &Type) -> bool {
|
|
38
|
+
const PRIMITIVES: &[&str] = &["u32", "i32", "u64", "i64", "u128", "i128", "bool"];
|
|
39
|
+
matches!(ty, Type::Path(p) if p.path.segments.len() == 1
|
|
40
|
+
&& PRIMITIVES.contains(&p.path.segments[0].ident.to_string().as_str()))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Types
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/// Storage kind: instance, persistent, or temporary.
|
|
48
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
49
|
+
enum StorageKind {
|
|
50
|
+
Instance,
|
|
51
|
+
Persistent,
|
|
52
|
+
Temporary,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl StorageKind {
|
|
56
|
+
fn name(self) -> &'static str {
|
|
40
57
|
match self {
|
|
41
|
-
Self::Instance
|
|
42
|
-
Self::Persistent
|
|
43
|
-
Self::Temporary
|
|
58
|
+
Self::Instance => "instance",
|
|
59
|
+
Self::Persistent => "persistent",
|
|
60
|
+
Self::Temporary => "temporary",
|
|
44
61
|
}
|
|
45
62
|
}
|
|
46
|
-
}
|
|
47
63
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let value_type = attr.parse_args::<Type>().map_err(|e| format!("failed to parse value type: {}", e))?;
|
|
64
|
+
/// Generates `env.storage().{kind}()`.
|
|
65
|
+
fn accessor(self) -> TokenStream {
|
|
66
|
+
let method = format_ident!("{}", self.name());
|
|
67
|
+
quote! { env.storage().#method() }
|
|
68
|
+
}
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
/// Generates TTL extension code.
|
|
71
|
+
///
|
|
72
|
+
/// - `use_stored_config=true`: reads TTL config from storage (for auto-extension)
|
|
73
|
+
/// - `use_stored_config=false`: uses local `threshold`/`extend_to` variables (for manual)
|
|
74
|
+
fn gen_ttl_extension(self, use_stored_config: bool) -> TokenStream {
|
|
75
|
+
let accessor = self.accessor();
|
|
76
|
+
let extend_call = if self == Self::Instance {
|
|
77
|
+
quote! { #accessor.extend_ttl(threshold, extend_to); }
|
|
78
|
+
} else {
|
|
79
|
+
quote! { #accessor.extend_ttl(&key, threshold, extend_to); }
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if use_stored_config {
|
|
83
|
+
let method = format_ident!("{}", self.name());
|
|
84
|
+
quote! {
|
|
85
|
+
if let Some(utils::ttl_configurable::TtlConfig { threshold, extend_to }) =
|
|
86
|
+
utils::ttl_configurable::TtlConfigStorage::#method(env)
|
|
87
|
+
{
|
|
88
|
+
#extend_call
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
extend_call
|
|
60
93
|
}
|
|
61
94
|
}
|
|
62
95
|
}
|
|
63
96
|
|
|
64
|
-
/// Parsed configuration for a
|
|
97
|
+
/// Parsed configuration for a storage enum variant.
|
|
65
98
|
#[derive(Debug, Clone)]
|
|
66
|
-
|
|
99
|
+
struct VariantConfig {
|
|
67
100
|
name: String,
|
|
68
|
-
|
|
101
|
+
kind: StorageKind,
|
|
102
|
+
value_type: Type,
|
|
69
103
|
default_value: Option<Expr>,
|
|
70
|
-
|
|
104
|
+
auto_ttl: bool,
|
|
71
105
|
}
|
|
72
106
|
|
|
73
|
-
impl
|
|
74
|
-
/// Returns
|
|
75
|
-
fn
|
|
107
|
+
impl VariantConfig {
|
|
108
|
+
/// Returns (getter, setter, remover, set_or_remove, has, ttl_extender) function names.
|
|
109
|
+
fn method_names(&self) -> (Ident, Ident, Ident, Ident, Ident, Ident) {
|
|
76
110
|
let base = &self.name;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
111
|
+
(
|
|
112
|
+
format_ident!("{}", base),
|
|
113
|
+
format_ident!("set_{}", base),
|
|
114
|
+
format_ident!("remove_{}", base),
|
|
115
|
+
format_ident!("set_or_remove_{}", base),
|
|
116
|
+
format_ident!("has_{}", base),
|
|
117
|
+
format_ident!("extend_{}_ttl", base),
|
|
118
|
+
)
|
|
85
119
|
}
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
impl TryFrom<&Variant> for StorageVariantConfig {
|
|
89
|
-
type Error = String;
|
|
90
|
-
|
|
91
|
-
fn try_from(variant: &Variant) -> std::result::Result<Self, Self::Error> {
|
|
92
|
-
let attrs = &variant.attrs;
|
|
93
|
-
|
|
94
|
-
// Known attribute identifiers for this macro ("doc" allows /// comments on variants)
|
|
95
|
-
const KNOWN_ATTRS: &[&str] =
|
|
96
|
-
&["doc", "instance", "persistent", "temporary", "default", "name", "no_ttl_extension"];
|
|
97
|
-
|
|
98
|
-
// Check for unsupported attributes
|
|
99
|
-
if let Some(unknown) = attrs
|
|
100
|
-
.iter()
|
|
101
|
-
.filter_map(|attr| attr.path().get_ident())
|
|
102
|
-
.find(|ident| !KNOWN_ATTRS.contains(&ident.to_string().as_str()))
|
|
103
|
-
{
|
|
104
|
-
return Err(format!(
|
|
105
|
-
"unknown attribute '{}' on variant '{}'. Supported attributes are: {}",
|
|
106
|
-
unknown,
|
|
107
|
-
variant.ident,
|
|
108
|
-
KNOWN_ATTRS.join(", ")
|
|
109
|
-
));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Parse storage type (exactly one required)
|
|
113
|
-
let storage_type =
|
|
114
|
-
attrs.iter().filter_map(|attr| StorageType::try_from(attr).ok()).exactly_one().map_err(|e| {
|
|
115
|
-
format!(
|
|
116
|
-
"storage type must be specified exactly once as \
|
|
117
|
-
'#[instance(Type)]', '#[persistent(Type)]', or '#[temporary(Type)]': {}",
|
|
118
|
-
e
|
|
119
|
-
)
|
|
120
|
-
})?;
|
|
121
|
-
|
|
122
|
-
// Parse optional default value
|
|
123
|
-
let default_value = attrs
|
|
124
|
-
.iter()
|
|
125
|
-
.filter(|attr| attr.path().is_ident("default"))
|
|
126
|
-
.at_most_one()
|
|
127
|
-
.map_err(|_| "multiple default values specified")?
|
|
128
|
-
.map(|attr| attr.parse_args::<Expr>())
|
|
129
|
-
.transpose()
|
|
130
|
-
.map_err(|e| format!("failed to parse default value: {}", e))?;
|
|
131
|
-
|
|
132
|
-
// Parse optional name override for storage key generation
|
|
133
|
-
// If no #[name("custom_name")] attribute is provided, defaults to snake_case of variant name
|
|
134
|
-
let name = attrs
|
|
135
|
-
.iter()
|
|
136
|
-
.filter(|attr| attr.path().is_ident("name"))
|
|
137
|
-
.at_most_one()
|
|
138
|
-
.map_err(|_| "multiple name attributes specified")?
|
|
139
|
-
.map(|attr| attr.parse_args::<syn::LitStr>().map(|lit| lit.value()))
|
|
140
|
-
.transpose()
|
|
141
|
-
.map_err(|e| format!("failed to parse name attribute: {}", e))?
|
|
142
|
-
.unwrap_or_else(|| variant.ident.to_string().to_snake_case());
|
|
143
|
-
|
|
144
|
-
// Parse optional #[no_ttl_extension] to skip automatic TTL extension (persistent only)
|
|
145
|
-
let no_ttl_extension = attrs
|
|
146
|
-
.iter()
|
|
147
|
-
.filter(|attr| attr.path().is_ident("no_ttl_extension"))
|
|
148
|
-
.at_most_one()
|
|
149
|
-
.map_err(|_| "multiple #[no_ttl_extension] attributes specified")?
|
|
150
|
-
.is_some();
|
|
151
|
-
if no_ttl_extension && !storage_type.is_persistent() {
|
|
152
|
-
return Err("#[no_ttl_extension] can only be used with #[persistent(...)] storage".to_string());
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
Ok(Self { storage_type, default_value, name, no_ttl_extension })
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/// Generated function names for a storage variant.
|
|
160
|
-
struct FunctionNames {
|
|
161
|
-
getter: Ident,
|
|
162
|
-
setter: Ident,
|
|
163
|
-
remover: Ident,
|
|
164
|
-
set_or_remove: Ident,
|
|
165
|
-
ttl_extender: Ident,
|
|
166
|
-
has: Ident,
|
|
167
|
-
}
|
|
168
|
-
|
|
169
122
|
// ============================================================================
|
|
170
|
-
// Code Generation
|
|
123
|
+
// Enum Variant Code Generation
|
|
171
124
|
// ============================================================================
|
|
172
125
|
|
|
173
|
-
/// Generates the storage API from the `#[storage]` attribute macro.
|
|
174
|
-
pub(crate) fn generate_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
175
|
-
let item_enum: syn::ItemEnum = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
|
|
176
|
-
|
|
177
|
-
// Ensure no arguments are passed to #[storage]
|
|
178
|
-
if !attr.is_empty() {
|
|
179
|
-
panic!("the #[storage] attribute does not accept arguments");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
let enum_name = &item_enum.ident;
|
|
183
|
-
let contracttype_variants: Vec<_> = item_enum.variants.iter().map(gen_contracttype_variant).collect();
|
|
184
|
-
let inherent_impls: Vec<_> = item_enum.variants.iter().map(|v| gen_storage_functions(enum_name, v)).collect();
|
|
185
|
-
|
|
186
|
-
let visibility = &item_enum.vis;
|
|
187
|
-
quote! {
|
|
188
|
-
#[soroban_sdk::contracttype]
|
|
189
|
-
#visibility enum #enum_name {
|
|
190
|
-
#(#contracttype_variants,)*
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
impl #enum_name {
|
|
194
|
-
#(#inherent_impls)*
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
126
|
/// Generates contracttype enum variant: `Variant` or `Variant(Type1, Type2, ...)`.
|
|
200
|
-
fn
|
|
127
|
+
fn gen_enum_variant(variant: &Variant) -> TokenStream {
|
|
201
128
|
let name = &variant.ident;
|
|
202
129
|
match &variant.fields {
|
|
203
130
|
Fields::Unit => quote! { #name },
|
|
@@ -209,125 +136,94 @@ fn gen_contracttype_variant(variant: &Variant) -> TokenStream {
|
|
|
209
136
|
}
|
|
210
137
|
}
|
|
211
138
|
|
|
212
|
-
/// Generates all storage accessor
|
|
213
|
-
fn
|
|
214
|
-
let config =
|
|
139
|
+
/// Generates all storage accessor methods for a variant.
|
|
140
|
+
fn gen_accessor_methods(enum_name: &Ident, variant: &Variant) -> TokenStream {
|
|
141
|
+
let config = VariantConfig::try_from(variant)
|
|
215
142
|
.unwrap_or_else(|e| panic!("failed to parse storage variant for {}: {}", variant.ident, e));
|
|
216
143
|
|
|
217
|
-
let
|
|
144
|
+
let (getter, setter, remover, set_or_remove, has, ttl_extender) = config.method_names();
|
|
145
|
+
let params = gen_params(variant);
|
|
146
|
+
let args = gen_args(variant);
|
|
147
|
+
let key = gen_key(enum_name, variant);
|
|
148
|
+
let accessor = config.kind.accessor();
|
|
149
|
+
let value_type = &config.value_type;
|
|
218
150
|
|
|
219
|
-
|
|
220
|
-
let
|
|
221
|
-
let
|
|
222
|
-
let storage_acc = gen_storage_accessor(&config.storage_type);
|
|
223
|
-
let value_type = config.storage_type.value_type();
|
|
151
|
+
// TTL extension tokens
|
|
152
|
+
let auto_ttl = if config.auto_ttl { config.kind.gen_ttl_extension(true) } else { TokenStream::new() };
|
|
153
|
+
let manual_ttl = config.kind.gen_ttl_extension(false);
|
|
224
154
|
|
|
225
|
-
//
|
|
226
|
-
let
|
|
227
|
-
|
|
155
|
+
// Getter return type and expression
|
|
156
|
+
let (ret_type, ret_expr) = match &config.default_value {
|
|
157
|
+
Some(default) => (quote! { #value_type }, quote! { value.unwrap_or(#default) }),
|
|
158
|
+
None => (quote! { Option<#value_type> }, quote! { value }),
|
|
159
|
+
};
|
|
160
|
+
let ttl_on_get = if config.auto_ttl {
|
|
161
|
+
quote! { if value.is_some() { #auto_ttl } }
|
|
228
162
|
} else {
|
|
229
163
|
TokenStream::new()
|
|
230
164
|
};
|
|
231
165
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
&
|
|
235
|
-
&storage_key,
|
|
236
|
-
&storage_acc,
|
|
237
|
-
value_type,
|
|
238
|
-
&config.default_value,
|
|
239
|
-
&auto_ttl_extend,
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Manual TTL extension (user provides threshold/extend_to)
|
|
243
|
-
let manual_ttl_extend = gen_ttl_extension(&config.storage_type, false);
|
|
244
|
-
|
|
245
|
-
// Has implementation (conditionally includes TTL extension)
|
|
246
|
-
let has_impl = if auto_ttl_extend.is_empty() {
|
|
247
|
-
quote! { #storage_acc.has(&key) }
|
|
166
|
+
// Has check body
|
|
167
|
+
let has_body = if config.auto_ttl {
|
|
168
|
+
quote! { let exists = #accessor.has(&key); if exists { #auto_ttl } exists }
|
|
248
169
|
} else {
|
|
249
|
-
quote! {
|
|
250
|
-
let exists = #storage_acc.has(&key);
|
|
251
|
-
if exists { #auto_ttl_extend }
|
|
252
|
-
exists
|
|
253
|
-
}
|
|
170
|
+
quote! { #accessor.has(&key) }
|
|
254
171
|
};
|
|
255
172
|
|
|
256
173
|
quote! {
|
|
257
|
-
#
|
|
174
|
+
pub fn #getter(#params) -> #ret_type {
|
|
175
|
+
let key = #key;
|
|
176
|
+
let value = #accessor.get::<_, #value_type>(&key);
|
|
177
|
+
#ttl_on_get
|
|
178
|
+
#ret_expr
|
|
179
|
+
}
|
|
258
180
|
|
|
259
|
-
pub fn #setter(#
|
|
260
|
-
let key = #
|
|
261
|
-
#
|
|
262
|
-
#
|
|
181
|
+
pub fn #setter(#params, value: &#value_type) {
|
|
182
|
+
let key = #key;
|
|
183
|
+
#accessor.set(&key, value);
|
|
184
|
+
#auto_ttl
|
|
263
185
|
}
|
|
264
186
|
|
|
265
|
-
pub fn #remover(#
|
|
266
|
-
let key = #
|
|
267
|
-
#
|
|
187
|
+
pub fn #remover(#params) {
|
|
188
|
+
let key = #key;
|
|
189
|
+
#accessor.remove(&key);
|
|
268
190
|
}
|
|
269
191
|
|
|
270
|
-
pub fn #set_or_remove(#
|
|
192
|
+
pub fn #set_or_remove(#params, value: &Option<#value_type>) {
|
|
271
193
|
match value.as_ref() {
|
|
272
|
-
Some(v) => Self::#setter(#
|
|
273
|
-
None => Self::#remover(#
|
|
194
|
+
Some(v) => Self::#setter(#args, v),
|
|
195
|
+
None => Self::#remover(#args),
|
|
274
196
|
}
|
|
275
197
|
}
|
|
276
198
|
|
|
277
|
-
pub fn #has(#
|
|
278
|
-
let key = #
|
|
279
|
-
#
|
|
199
|
+
pub fn #has(#params) -> bool {
|
|
200
|
+
let key = #key;
|
|
201
|
+
#has_body
|
|
280
202
|
}
|
|
281
203
|
|
|
282
|
-
pub fn #ttl_extender(#
|
|
283
|
-
let key = #
|
|
284
|
-
#
|
|
204
|
+
pub fn #ttl_extender(#params, threshold: u32, extend_to: u32) {
|
|
205
|
+
let key = #key;
|
|
206
|
+
#manual_ttl
|
|
285
207
|
}
|
|
286
208
|
}
|
|
287
209
|
}
|
|
288
210
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
quote! { env.storage().#method() }
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/// Generates TTL extension code.
|
|
296
|
-
/// - use_stored_config=true: fetches TTL config from TtlConfigStorage, only extends if config is set
|
|
297
|
-
/// - use_stored_config=false: uses local variables `threshold` and `extend_to` (for manual extension)
|
|
298
|
-
fn gen_ttl_extension(storage_type: &StorageType, use_stored_config: bool) -> TokenStream {
|
|
299
|
-
let accessor = gen_storage_accessor(storage_type);
|
|
300
|
-
let extend_call = if storage_type.is_instance() {
|
|
301
|
-
quote! { #accessor.extend_ttl(threshold, extend_to); }
|
|
302
|
-
} else {
|
|
303
|
-
quote! { #accessor.extend_ttl(&key, threshold, extend_to); }
|
|
304
|
-
};
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Parameter & Key Generation Helpers
|
|
213
|
+
// ============================================================================
|
|
305
214
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
extend_call
|
|
215
|
+
/// Extracts (name, type) pairs from variant fields.
|
|
216
|
+
fn extract_fields(variant: &Variant) -> Vec<(&Ident, &Type)> {
|
|
217
|
+
match &variant.fields {
|
|
218
|
+
Fields::Unit => vec![],
|
|
219
|
+
Fields::Named(named) => named.named.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect(),
|
|
220
|
+
_ => panic!("only unit variants or named fields are supported in storage enums"),
|
|
315
221
|
}
|
|
316
222
|
}
|
|
317
223
|
|
|
318
|
-
/// Checks if a type is a primitive type
|
|
319
|
-
pub(crate) fn is_primitive_type(ty: &Type) -> bool {
|
|
320
|
-
let Type::Path(type_path) = ty else { return false };
|
|
321
|
-
let Some(segment) = type_path.path.segments.first() else { return false };
|
|
322
|
-
type_path.path.segments.len() == 1
|
|
323
|
-
&& matches!(segment.ident.to_string().as_str(), "u32" | "i32" | "u64" | "i64" | "u128" | "i128" | "bool")
|
|
324
|
-
}
|
|
325
|
-
|
|
326
224
|
/// Generates function parameters: `env: &Env` or `env: &Env, field1: Type1, ...`.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
fn gen_fn_params(variant: &Variant) -> TokenStream {
|
|
330
|
-
let fields = extract_named_fields(&variant.fields);
|
|
225
|
+
fn gen_params(variant: &Variant) -> TokenStream {
|
|
226
|
+
let fields = extract_fields(variant);
|
|
331
227
|
if fields.is_empty() {
|
|
332
228
|
quote! { env: &soroban_sdk::Env }
|
|
333
229
|
} else {
|
|
@@ -342,73 +238,136 @@ fn gen_fn_params(variant: &Variant) -> TokenStream {
|
|
|
342
238
|
}
|
|
343
239
|
}
|
|
344
240
|
|
|
345
|
-
/// Generates function
|
|
346
|
-
fn
|
|
347
|
-
let fields =
|
|
241
|
+
/// Generates function arguments: `env` or `env, field1, field2, ...`.
|
|
242
|
+
fn gen_args(variant: &Variant) -> TokenStream {
|
|
243
|
+
let fields = extract_fields(variant);
|
|
348
244
|
if fields.is_empty() {
|
|
349
245
|
quote! { env }
|
|
350
246
|
} else {
|
|
351
|
-
let
|
|
352
|
-
quote! { env, #(#
|
|
247
|
+
let names = fields.iter().map(|(name, _)| *name);
|
|
248
|
+
quote! { env, #(#names),* }
|
|
353
249
|
}
|
|
354
250
|
}
|
|
355
251
|
|
|
356
|
-
/// Generates key
|
|
357
|
-
|
|
358
|
-
fn gen_storage_key(variant: &Variant, enum_name: &Ident) -> TokenStream {
|
|
359
|
-
let fields = extract_named_fields(&variant.fields);
|
|
252
|
+
/// Generates storage key: `Enum::Variant` or `Enum::Variant(field1.clone(), ...)`.
|
|
253
|
+
fn gen_key(enum_name: &Ident, variant: &Variant) -> TokenStream {
|
|
360
254
|
let variant_ident = &variant.ident;
|
|
255
|
+
let fields = extract_fields(variant);
|
|
256
|
+
|
|
361
257
|
if fields.is_empty() {
|
|
362
258
|
quote! { #enum_name::#variant_ident }
|
|
363
259
|
} else {
|
|
364
|
-
let
|
|
260
|
+
let args = fields.iter().map(|(name, ty)| {
|
|
365
261
|
if is_primitive_type(ty) {
|
|
366
262
|
quote! { #name }
|
|
367
263
|
} else {
|
|
368
264
|
quote! { #name.clone() }
|
|
369
265
|
}
|
|
370
266
|
});
|
|
371
|
-
quote! { #enum_name::#variant_ident(#(#
|
|
267
|
+
quote! { #enum_name::#variant_ident(#(#args),*) }
|
|
372
268
|
}
|
|
373
269
|
}
|
|
374
270
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Attribute Parsing
|
|
273
|
+
// ============================================================================
|
|
274
|
+
|
|
275
|
+
/// Known attributes for storage variants ("doc" allows /// comments).
|
|
276
|
+
const KNOWN_ATTRS: &[&str] = &["doc", "instance", "persistent", "temporary", "default", "name", "no_ttl_extension"];
|
|
277
|
+
|
|
278
|
+
impl TryFrom<&Variant> for VariantConfig {
|
|
279
|
+
type Error = String;
|
|
280
|
+
|
|
281
|
+
fn try_from(variant: &Variant) -> Result<Self, Self::Error> {
|
|
282
|
+
let attrs = &variant.attrs;
|
|
283
|
+
validate_attrs(attrs, &variant.ident)?;
|
|
284
|
+
|
|
285
|
+
let (kind, value_type) = parse_storage_type(attrs)?;
|
|
286
|
+
let default_value = parse_default(attrs)?;
|
|
287
|
+
let name = parse_name(attrs)?.unwrap_or_else(|| variant.ident.to_string().to_snake_case());
|
|
288
|
+
let no_ttl_extension = parse_no_ttl_extension(attrs)?;
|
|
289
|
+
|
|
290
|
+
if no_ttl_extension && kind != StorageKind::Persistent {
|
|
291
|
+
return Err("#[no_ttl_extension] can only be used with #[persistent(...)] storage".to_string());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Ok(Self {
|
|
295
|
+
name,
|
|
296
|
+
kind,
|
|
297
|
+
value_type,
|
|
298
|
+
default_value,
|
|
299
|
+
auto_ttl: kind == StorageKind::Persistent && !no_ttl_extension,
|
|
300
|
+
})
|
|
381
301
|
}
|
|
382
302
|
}
|
|
383
303
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
key: &TokenStream,
|
|
389
|
-
accessor: &TokenStream,
|
|
390
|
-
value_type: &Type,
|
|
391
|
-
default_value: &Option<Expr>,
|
|
392
|
-
auto_ttl_extend: &TokenStream,
|
|
393
|
-
) -> TokenStream {
|
|
394
|
-
let (return_type, return_expr) = match default_value {
|
|
395
|
-
Some(expr) => (quote! { #value_type }, quote! { value.unwrap_or(#expr) }),
|
|
396
|
-
None => (quote! { Option<#value_type> }, quote! { value }),
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// If auto TTL extension is enabled, check if the value is not None and extend the TTL
|
|
400
|
-
let ttl_check = if auto_ttl_extend.is_empty() {
|
|
401
|
-
TokenStream::new()
|
|
402
|
-
} else {
|
|
403
|
-
quote! { if value.is_some() { #auto_ttl_extend } }
|
|
404
|
-
};
|
|
304
|
+
fn validate_attrs(attrs: &[Attribute], variant_ident: &Ident) -> Result<(), String> {
|
|
305
|
+
for attr in attrs {
|
|
306
|
+
let path = attr.path();
|
|
307
|
+
let name = path.get_ident().map(|i| i.to_string()).unwrap_or_else(|| quote!(#path).to_string());
|
|
405
308
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
309
|
+
if !KNOWN_ATTRS.contains(&name.as_str()) {
|
|
310
|
+
return Err(format!(
|
|
311
|
+
"unknown attribute '{}' on variant '{}'. Supported attributes are: {}",
|
|
312
|
+
name,
|
|
313
|
+
variant_ident,
|
|
314
|
+
KNOWN_ATTRS.join(", ")
|
|
315
|
+
));
|
|
412
316
|
}
|
|
413
317
|
}
|
|
318
|
+
Ok(())
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fn parse_storage_type(attrs: &[Attribute]) -> Result<(StorageKind, Type), String> {
|
|
322
|
+
attrs
|
|
323
|
+
.iter()
|
|
324
|
+
.filter_map(|attr| {
|
|
325
|
+
let ident = attr.path().get_ident()?;
|
|
326
|
+
let kind = match ident.to_string().as_str() {
|
|
327
|
+
"instance" => StorageKind::Instance,
|
|
328
|
+
"persistent" => StorageKind::Persistent,
|
|
329
|
+
"temporary" => StorageKind::Temporary,
|
|
330
|
+
_ => return None,
|
|
331
|
+
};
|
|
332
|
+
Some((kind, attr.parse_args::<Type>().ok()?))
|
|
333
|
+
})
|
|
334
|
+
.exactly_one()
|
|
335
|
+
.map_err(|e| {
|
|
336
|
+
format!(
|
|
337
|
+
"storage type must be specified exactly once as \
|
|
338
|
+
'#[instance(Type)]', '#[persistent(Type)]', or '#[temporary(Type)]': {}",
|
|
339
|
+
e
|
|
340
|
+
)
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fn parse_default(attrs: &[Attribute]) -> Result<Option<Expr>, String> {
|
|
345
|
+
attrs
|
|
346
|
+
.iter()
|
|
347
|
+
.filter(|attr| attr.path().is_ident("default"))
|
|
348
|
+
.at_most_one()
|
|
349
|
+
.map_err(|_| "multiple default values specified")?
|
|
350
|
+
.map(|attr| attr.parse_args::<Expr>())
|
|
351
|
+
.transpose()
|
|
352
|
+
.map_err(|e| format!("failed to parse default value: {}", e))
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fn parse_name(attrs: &[Attribute]) -> Result<Option<String>, String> {
|
|
356
|
+
attrs
|
|
357
|
+
.iter()
|
|
358
|
+
.filter(|attr| attr.path().is_ident("name"))
|
|
359
|
+
.at_most_one()
|
|
360
|
+
.map_err(|_| "multiple name attributes specified")?
|
|
361
|
+
.map(|attr| attr.parse_args::<syn::LitStr>().map(|lit| lit.value()))
|
|
362
|
+
.transpose()
|
|
363
|
+
.map_err(|e| format!("failed to parse name attribute: {}", e))
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
fn parse_no_ttl_extension(attrs: &[Attribute]) -> Result<bool, String> {
|
|
367
|
+
Ok(attrs
|
|
368
|
+
.iter()
|
|
369
|
+
.filter(|attr| attr.path().is_ident("no_ttl_extension"))
|
|
370
|
+
.at_most_one()
|
|
371
|
+
.map_err(|_| "multiple #[no_ttl_extension] attributes specified")?
|
|
372
|
+
.is_some())
|
|
414
373
|
}
|