midds_types_codegen/
lib.rs

1//! # MIDDS Types Code Generation
2//!
3//! This crate provides procedural macros for generating bounded string and collection types
4//! that are compatible with Substrate runtime and WebAssembly bindings.
5//!
6//! The main purpose is to solve the limitation where `wasm_bindgen` doesn't support
7//! generic parameters, preventing the use of `BoundedVec<T, Bound>` directly in
8//! JavaScript-exposed APIs.
9//!
10//! ## Features
11//!
12//! - **`#[midds_string(bound)]`**: Generates bounded string types with UTF-8 validation
13//! - **`#[midds_collection(type, bound)]`**: Generates bounded collection types
14//! - **Substrate compatibility**: Implements all required traits for runtime usage
15//! - **JavaScript bindings**: Provides `wasm_bindgen` integration when the `js` feature is enabled
16//! - **Type safety**: Compile-time bounds checking and UTF-8 validation
17//! - **Serde support**: Automatic Serialize/Deserialize implementation when the `js` feature is enabled
18//!
19//! ## Usage
20//!
21//! ```rust
22//! use midds_types_codegen::{midds_string, midds_collection};
23//!
24//! // Generate a bounded string type with 256-byte limit
25//! #[midds_string(256)]
26//! pub struct TrackTitle;
27//!
28//! // Generate a bounded collection type for 64 u64 values
29//! #[midds_collection(u64, 64)]
30//! pub struct ProducerIds;
31//! ```
32//!
33//! ## Generated API
34//!
35//! Each macro generates a complete API including:
36//! - Standard trait implementations (`Clone`, `PartialEq`, `Encode`, `Decode`, etc.)
37//! - String/collection manipulation methods
38//! - Error handling with type-specific error enums
39//! - JavaScript bindings (when `js` feature is enabled)
40//!
41//! ## Architecture
42//!
43//! The generated types wrap `sp_runtime::BoundedVec` to provide:
44//! - Compile-time capacity limits
45//! - Runtime bounds checking
46//! - Memory-efficient storage
47//! - Substrate runtime compatibility
48
49use proc_macro::TokenStream;
50use quote::quote;
51use syn::{parse_macro_input, DeriveInput, Type};
52
53/// Procedural macro that generates a bounded string type with compile-time capacity limits.
54///
55/// This macro creates a newtype wrapper around `BoundedVec<u8>` that:
56/// - Enforces UTF-8 validity at all times
57/// - Provides string-like methods (`push_str`, `pop`, `clear`, etc.)
58/// - Implements all required Substrate traits
59/// - Exposes JavaScript bindings when the `js` feature is enabled
60/// - Optional regex validation (std feature only)
61///
62/// # Arguments
63///
64/// * `bound` - Maximum number of bytes the string can contain (default: 128)
65/// * `regex = "pattern"` - Optional regex pattern for validation (requires std feature)
66///
67/// # Example
68///
69/// ```rust
70/// use midds_types_codegen::midds_string;
71///
72/// #[midds_string(256)]
73/// pub struct TrackTitle;
74///
75/// // With regex validation
76/// #[midds_string(15, regex = r"^[A-Z]{2}[A-Z0-9]{3}[0-9]{2}[0-9]{5}$")]
77/// pub struct Isrc;
78///
79/// // Usage
80/// let mut title = TrackTitle::from_str("My Song").unwrap();
81/// title.push_str(" - Extended Mix").unwrap();
82/// assert_eq!(title.as_str(), "My Song - Extended Mix");
83/// ```
84///
85/// # Generated Methods
86///
87/// The macro generates the following public methods:
88/// - `new()` - Create empty string
89/// - `from_str(s: &str)` - Create from string slice
90/// - `as_str()` - Get string slice
91/// - `push_str(s: &str)` - Append string
92/// - `push(ch: char)` - Append character
93/// - `pop()` - Remove last character
94/// - `clear()` - Remove all content
95/// - `len()`, `is_empty()`, `capacity()` - Size information
96///
97/// # JavaScript Bindings
98///
99/// When the `js` feature is enabled, additional JavaScript-compatible methods are generated:
100/// - `new()` - Constructor
101/// - `value` getter/setter - Access string content
102/// - `fromString(s)` - Static constructor
103/// - `toString()` - Convert to string
104/// - `pushStr(s)` - Append string
105/// - `length`, `capacity`, `isEmpty` - Properties
106///
107/// # Serde Support
108///
109/// When the `js` feature is enabled, the type automatically implements:
110/// - `Serialize` trait for JSON serialization
111/// - `Deserialize` trait for JSON deserialization
112///
113/// # Error Handling
114///
115/// The macro generates a type-specific error enum that handles:
116/// - `InvalidUtf8` - Invalid UTF-8 sequences
117/// - `TooLong` - Content exceeds bound limit
118///
119/// # Panics
120///
121/// This macro will panic if:
122/// - Invalid regex format is provided (must be quoted)
123/// - Arguments are malformed (expected: `bound` or `bound, regex = "pattern"`)
124#[proc_macro_attribute]
125pub fn midds_string(args: TokenStream, input: TokenStream) -> TokenStream {
126    let input = parse_macro_input!(input as DeriveInput);
127    let struct_name = &input.ident;
128    let vis = &input.vis;
129
130    // Parse arguments (bound, optional regex)
131    let (bound, regex_pattern) = if args.is_empty() {
132        (128, None)
133    } else {
134        // Parse as AttributeArgs-like structure
135        let args_str = args.to_string();
136        
137        // Simple parsing for "bound" or "bound, regex = \"pattern\""
138        if args_str.contains("regex") {
139            // Parse complex format: bound, regex = "pattern"
140            let parts: Vec<&str> = args_str.split(',').collect();
141            assert!(parts.len() == 2, "Expected format: bound, regex = \"pattern\"");
142            
143            let bound_str = parts[0].trim();
144            let bound: u32 = bound_str.parse().expect("Invalid bound");
145            
146            let regex_part = parts[1].trim();
147            assert!(regex_part.starts_with("regex"), "Second argument must be regex = \"pattern\"");
148            
149            // Extract the pattern from regex = "pattern"
150            let eq_pos = regex_part.find('=').expect("Expected = after regex");
151            let pattern_part = regex_part[eq_pos + 1..].trim();
152            
153            // Remove quotes - handle raw strings properly
154            let pattern = if pattern_part.starts_with("r#\"") && pattern_part.ends_with("\"#") {
155                &pattern_part[3..pattern_part.len() - 2]
156            } else if pattern_part.starts_with("r\"") && pattern_part.ends_with('"') {
157                &pattern_part[2..pattern_part.len() - 1]
158            } else if pattern_part.starts_with('"') && pattern_part.ends_with('"') {
159                &pattern_part[1..pattern_part.len() - 1]
160            } else {
161                panic!("Regex pattern must be quoted (use \"pattern\" or r\"pattern\")");
162            };
163            
164            (bound, Some(pattern.to_string()))
165        } else {
166            // Simple bound only
167            let bound: u32 = args_str.parse().expect("Invalid bound");
168            (bound, None)
169        }
170    };
171
172    // Preserve original attributes (except midds_string)
173    let attrs: Vec<_> = input
174        .attrs
175        .iter()
176        .filter(|attr| !attr.path().is_ident("midds_string"))
177        .collect();
178
179    // Generate unique error name based on struct name
180    let error_name = quote::format_ident!("{}Error", struct_name);
181
182    // Generate validation-related code
183    let validation_error_variant = if regex_pattern.is_some() {
184        quote! { InvalidFormat, }
185    } else {
186        quote! {}
187    };
188
189    let validation_error_display = if let Some(ref pattern) = regex_pattern {
190        quote! {
191            #error_name::InvalidFormat => write!(f, "String does not match required format: {}", #pattern),
192        }
193    } else {
194        quote! {}
195    };
196
197    let validation_pattern_method = if let Some(ref pattern) = regex_pattern {
198        quote! {
199            #[cfg(feature = "std")]
200            pub fn validation_pattern() -> Option<&'static str> {
201                Some(#pattern)
202            }
203
204            #[cfg(not(feature = "std"))]
205            pub fn validation_pattern() -> Option<&'static str> {
206                None
207            }
208        }
209    } else {
210        quote! {
211            pub fn validation_pattern() -> Option<&'static str> {
212                None
213            }
214        }
215    };
216
217
218
219    let validation_method = if regex_pattern.is_some() {
220        quote! {
221            /// Validate the current content against the regex pattern (std only)
222            #[cfg(feature = "std")]
223            pub fn validate(&self) -> Result<(), #error_name> {
224                if let Some(pattern) = Self::validation_pattern() {
225                    let regex = ::regex::Regex::new(pattern)
226                        .expect("Invalid regex pattern in midds_string macro");
227                    if !regex.is_match(self.as_str()) {
228                        return Err(#error_name::InvalidFormat);
229                    }
230                }
231                Ok(())
232            }
233        }
234    } else {
235        quote! {}
236    };
237
238    let regex_validation_in_from_str = if regex_pattern.is_some() {
239        quote! {
240            // Validate normalized string
241            #[cfg(feature = "std")]
242            {
243                if let Some(pattern) = Self::validation_pattern() {
244                    let regex = ::regex::Regex::new(pattern)
245                        .expect("Invalid regex pattern in midds_string macro");
246                    if !regex.is_match(&normalized) {
247                        return Err(#error_name::InvalidFormat);
248                    }
249                }
250            }
251        }
252    } else {
253        quote! {}
254    };
255
256    let expanded = quote! {
257            // Type-specific error definition
258            #[derive(Debug, Clone)]
259            #vis enum #error_name {
260                InvalidUtf8,
261                TooLong,
262                #validation_error_variant
263            }
264
265            impl core::fmt::Display for #error_name {
266                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267                    match self {
268                        #error_name::InvalidUtf8 => write!(f, "Invalid UTF-8 sequence"),
269                        #error_name::TooLong => write!(f, "String too long, maximum {} bytes", #bound),
270                        #validation_error_display
271                    }
272                }
273            }
274
275            #[cfg(feature = "std")]
276            impl std::error::Error for #error_name {}
277
278            #(#attrs)*
279            #[cfg_attr(feature = "js", ::wasm_bindgen::prelude::wasm_bindgen)]
280            #[derive(
281                Clone,
282                PartialEq,
283                Eq,
284                ::parity_scale_codec::Encode,
285                ::parity_scale_codec::Decode,
286                ::parity_scale_codec::DecodeWithMemTracking,
287                ::scale_info::TypeInfo,
288                ::parity_scale_codec::MaxEncodedLen,
289                ::sp_runtime::RuntimeDebug
290            )]
291            #[cfg_attr(feature = "js", derive(::serde::Serialize, ::serde::Deserialize))]
292            #vis struct #struct_name(::sp_runtime::BoundedVec<u8, ::frame_support::traits::ConstU32<#bound>>);
293
294            impl ::core::str::FromStr for #struct_name {
295                type Err = #error_name;
296
297                fn from_str(s: &str) -> Result<Self, Self::Err> {
298                    // Always normalize the input first (std only)
299                    #[cfg(feature = "std")]
300                    let normalized = s.replace([' ', '-', '_'], "");
301                    
302                    #[cfg(not(feature = "std"))]
303                    let normalized = s.to_string();
304                    
305                    let bytes = normalized.as_bytes();
306                    if bytes.len() > #bound as usize {
307                        return Err(#error_name::TooLong);
308                    }
309
310                    #regex_validation_in_from_str
311
312                    ::sp_runtime::BoundedVec::try_from(bytes.to_vec())
313                        .map(#struct_name)
314                        .map_err(|_| #error_name::TooLong)
315                }
316            }
317
318            impl #struct_name {
319                pub fn new() -> Self {
320                    Self(::sp_runtime::BoundedVec::new())
321                }
322
323                // Validation pattern method
324                #validation_pattern_method
325
326                #validation_method
327
328
329                pub fn from_utf8(bytes: Vec<u8>) -> Result<Self, #error_name> {
330                    let s = core::str::from_utf8(&bytes).map_err(|_| #error_name::InvalidUtf8)?;
331                    
332                    // Use from_str to ensure normalization is applied
333                    <Self as ::core::str::FromStr>::from_str(s)
334                }
335
336                pub fn as_str(&self) -> &str {
337                    core::str::from_utf8(&self.0).expect("Valid UTF-8 checked on construction")
338                }
339
340                pub fn as_bytes(&self) -> &[u8] {
341                    &self.0
342                }
343
344                pub fn len(&self) -> usize {
345                    self.0.len()
346                }
347
348                pub fn is_empty(&self) -> bool {
349                    self.0.is_empty()
350                }
351
352                pub fn capacity(&self) -> usize {
353                    #bound as usize
354                }
355
356                pub fn max_capacity(&self) -> usize {
357                    #bound as usize
358                }
359
360                pub fn remaining_capacity(&self) -> usize {
361                    self.capacity() - self.len()
362                }
363
364                pub fn push_str(&mut self, s: &str) -> Result<(), #error_name> {
365                    // Create the result string and normalize it
366                    let current = self.as_str();
367                    let combined = format!("{}{}", current, s);
368                    
369                    // Use from_str to create a new normalized instance
370                    let normalized_instance = <Self as ::core::str::FromStr>::from_str(&combined)?;
371                    
372                    // Replace current content with normalized content
373                    *self = normalized_instance;
374                    
375                    Ok(())
376                }
377
378                pub fn push(&mut self, ch: char) -> Result<(), #error_name> {
379                    let mut buf = [0; 4];
380                    let s = ch.encode_utf8(&mut buf);
381                    self.push_str(s)
382                }
383
384                pub fn pop(&mut self) -> Option<char> {
385                    if self.is_empty() {
386                        return None;
387                    }
388
389                    let s = self.as_str();
390                    let mut chars = s.chars();
391                    let last_char = chars.next_back()?;
392                    let new_len = s.len() - last_char.len_utf8();
393                    self.0.truncate(new_len);
394                    Some(last_char)
395                }
396
397                pub fn clear(&mut self) {
398                    self.0.clear();
399                }
400
401                pub fn truncate(&mut self, new_len: usize) {
402                    if new_len <= self.len() {
403                        let s = self.as_str();
404                        if let Some((byte_index, _)) = s.char_indices().nth(new_len) {
405                            self.0.truncate(byte_index);
406                        } else {
407                            self.0.truncate(new_len.min(s.len()));
408                        }
409                    }
410                }
411
412                pub fn into_string(self) -> String {
413                    String::from_utf8(self.0.into_inner()).expect("Invalid UTF-8")
414                }
415
416
417                pub fn into_inner(self) -> ::sp_runtime::BoundedVec<u8, ::frame_support::traits::ConstU32<#bound>> {
418                    self.0
419                }
420
421                pub fn get(&self, range: core::ops::Range<usize>) -> Option<&str> {
422                    self.as_str().get(range)
423                }
424            }
425
426            impl Default for #struct_name {
427                fn default() -> Self {
428                    Self::new()
429                }
430            }
431
432            impl core::fmt::Display for #struct_name {
433                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
434                    write!(f, "{}", self.as_str())
435                }
436            }
437
438            impl core::ops::Deref for #struct_name {
439                type Target = str;
440
441                fn deref(&self) -> &Self::Target {
442                    self.as_str()
443                }
444            }
445
446            impl AsRef<str> for #struct_name {
447                fn as_ref(&self) -> &str {
448                    self.as_str()
449                }
450            }
451
452            impl AsRef<[u8]> for #struct_name {
453                fn as_ref(&self) -> &[u8] {
454                    self.as_bytes()
455                }
456            }
457
458            impl From<#struct_name> for String {
459                fn from(ms: #struct_name) -> String {
460                    ms.into_string()
461                }
462            }
463
464            impl TryFrom<String> for #struct_name {
465                type Error = #error_name;
466
467                fn try_from(s: String) -> Result<Self, Self::Error> {
468                    Self::from_utf8(s.into_bytes())
469                }
470            }
471
472            impl TryFrom<&str> for #struct_name {
473                type Error = #error_name;
474
475                fn try_from(s: &str) -> Result<Self, Self::Error> {
476                    <#struct_name as ::core::str::FromStr>::from_str(s)
477                }
478            }
479
480            impl PartialEq<str> for #struct_name {
481                fn eq(&self, other: &str) -> bool {
482                    self.as_str() == other
483                }
484            }
485
486            impl PartialEq<&str> for #struct_name {
487                fn eq(&self, other: &&str) -> bool {
488                    self.as_str() == *other
489                }
490            }
491
492            impl PartialEq<String> for #struct_name {
493                fn eq(&self, other: &String) -> bool {
494                    self.as_str() == other.as_str()
495                }
496            }
497
498            #[cfg(feature = "js")]
499            #[::wasm_bindgen::prelude::wasm_bindgen]
500            impl #struct_name {
501                #[::wasm_bindgen::prelude::wasm_bindgen(constructor)]
502                pub fn js_new() -> #struct_name {
503                    Self::new()
504                }
505
506    #[::wasm_bindgen::prelude::wasm_bindgen(getter)]
507          pub fn value(&self) -> String {
508              self.as_str().to_string()
509          }
510
511          #[::wasm_bindgen::prelude::wasm_bindgen(setter)]
512          pub fn set_value(&mut self, value: &str) -> Result<(), ::wasm_bindgen::JsError> {
513              *self = <#struct_name as ::core::str::FromStr>::from_str(value).map_err(|e| ::wasm_bindgen::JsError::new(&e.to_string()))?;
514              Ok(())
515          }
516
517                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "fromString")]
518                pub fn js_from_string(s: &str) -> Result<#struct_name, ::wasm_bindgen::JsError> {
519                    <#struct_name as ::core::str::FromStr>::from_str(s).map_err(|e| ::wasm_bindgen::JsError::new(&e.to_string()))
520                }
521
522                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "toString")]
523                pub fn js_to_string(&self) -> String {
524                    self.as_str().to_string()
525                }
526
527                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "pushStr")]
528                pub fn js_push_str(&mut self, s: &str) -> Result<(), ::wasm_bindgen::JsError> {
529                    self.push_str(s).map_err(|e| ::wasm_bindgen::JsError::new(&e.to_string()))
530                }
531
532                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "length")]
533                pub fn js_length(&self) -> usize {
534                    self.len()
535                }
536
537                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "capacity")]
538                pub fn js_capacity(&self) -> usize {
539                    self.capacity()
540                }
541
542                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "maxCapacity")]
543                pub fn js_max_capacity(&self) -> usize {
544                    self.max_capacity()
545                }
546
547                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "remainingCapacity")]
548                pub fn js_remaining_capacity(&self) -> usize {
549                    self.remaining_capacity()
550                }
551
552                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "isEmpty")]
553                pub fn js_is_empty(&self) -> bool {
554                    self.is_empty()
555                }
556
557                #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "clear")]
558                pub fn js_clear(&mut self) {
559                    self.clear()
560                }
561
562            }
563        };
564
565    TokenStream::from(expanded)
566}
567
568/// Procedural macro that generates a bounded collection type with compile-time capacity limits.
569///
570/// This macro creates a newtype wrapper around `BoundedVec<T>` that:
571/// - Enforces capacity limits at compile time
572/// - Provides collection methods (push, pop, insert, remove, etc.)
573/// - Implements all required Substrate traits
574/// - Exposes JavaScript bindings when the `js` feature is enabled
575///
576/// # Arguments
577///
578/// * `type` - The inner type that the collection will contain
579/// * `bound` - Maximum number of items the collection can hold
580///
581/// # Example
582///
583/// ```rust
584/// use midds_types_codegen::midds_collection;
585///
586/// #[midds_collection(u64, 64)]
587/// pub struct ProducerIds;
588///
589/// // Usage
590/// let mut producers = ProducerIds::new();
591/// producers.push(12345).unwrap();
592/// producers.push(67890).unwrap();
593/// assert_eq!(producers.len(), 2);
594/// ```
595///
596/// # Generated Methods
597///
598/// The macro generates the following public methods:
599/// - `new()` - Create empty collection
600/// - `push(item)` - Add item to end
601/// - `pop()` - Remove last item
602/// - `insert(index, item)` - Insert at position
603/// - `remove(index)` - Remove at position
604/// - `get(index)`, `get_mut(index)` - Access items
605/// - `clear()` - Remove all items
606/// - `len()`, `is_empty()`, `capacity()` - Size information
607/// - `iter()`, `iter_mut()` - Iterators
608/// - `as_slice()`, `as_mut_slice()` - Slice access
609///
610/// # JavaScript Bindings
611///
612/// When the `js` feature is enabled, additional JavaScript-compatible methods are generated:
613/// - `new()` - Constructor
614/// - `pushItem(item)` - Add item
615/// - `popItem()` - Remove last item
616/// - `getItem(index)` - Get item by index
617/// - `insertItem(index, item)` - Insert at position
618/// - `removeItem(index)` - Remove at position
619/// - `length`, `capacity`, `isEmpty` - Properties
620///
621/// Note: Generic array conversion requires type-specific implementations
622/// due to `wasm_bindgen` limitations with generics.
623///
624/// # Serde Support
625///
626/// When the `js` feature is enabled, the type automatically implements:
627/// - `Serialize` trait for JSON serialization (requires inner type to be Serialize)
628/// - `Deserialize` trait for JSON deserialization (requires inner type to be `DeserializeOwned`)
629///
630/// # Error Handling
631///
632/// The macro generates a type-specific error enum that handles:
633/// - `TooManyItems` - Collection exceeds capacity
634/// - `InvalidItem` - Item validation failed
635///
636/// # Panics
637///
638/// This macro will panic if:
639/// - Wrong number of arguments provided (expected exactly 2: type and bound)
640/// - Invalid inner type format
641#[proc_macro_attribute]
642pub fn midds_collection(args: TokenStream, input: TokenStream) -> TokenStream {
643    let input = parse_macro_input!(input as DeriveInput);
644    let struct_name = &input.ident;
645    let vis = &input.vis;
646
647    // Parse arguments (type, bound)
648    let args_str = args.to_string();
649    let args_parts: Vec<&str> = args_str.split(',').map(str::trim).collect();
650
651    assert!(args_parts.len() == 2, "midds_collection expects exactly 2 arguments: type and bound");
652
653    let inner_type = args_parts[0];
654    let bound_str = args_parts[1];
655    let bound: u32 = bound_str.parse().expect("Invalid bound, expected number");
656
657    // Parse inner type
658    let inner_type_ident: Type = syn::parse_str(inner_type).expect("Invalid inner type");
659
660    // Preserve original attributes
661    let attrs: Vec<_> = input
662        .attrs
663        .iter()
664        .filter(|attr| !attr.path().is_ident("midds_collection"))
665        .collect();
666
667    // Generate unique error name based on struct name
668    let error_name = quote::format_ident!("{}Error", struct_name);
669
670    let expanded = quote! {
671        // Type-specific error definition
672        #[derive(Debug, Clone)]
673        #vis enum #error_name {
674            TooManyItems,
675            InvalidItem,
676        }
677
678        impl core::fmt::Display for #error_name {
679            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
680                match self {
681                    #error_name::TooManyItems => write!(f, "Too many items, maximum {} items", #bound),
682                    #error_name::InvalidItem => write!(f, "Invalid item in collection"),
683                }
684            }
685        }
686
687        #[cfg(feature = "std")]
688        impl std::error::Error for #error_name {}
689
690        #(#attrs)*
691        #[cfg_attr(feature = "js", ::wasm_bindgen::prelude::wasm_bindgen)]
692        #[derive(
693            Clone,
694            PartialEq,
695            Eq,
696            ::parity_scale_codec::Encode,
697            ::parity_scale_codec::Decode,
698            ::parity_scale_codec::DecodeWithMemTracking,
699            ::scale_info::TypeInfo,
700            ::parity_scale_codec::MaxEncodedLen,
701            ::sp_runtime::RuntimeDebug
702        )]
703        #[cfg_attr(feature = "js", derive(::serde::Serialize, ::serde::Deserialize))]
704        #vis struct #struct_name(::sp_runtime::BoundedVec<#inner_type_ident, ::frame_support::traits::ConstU32<#bound>>);
705
706        impl #struct_name {
707            pub fn new() -> Self {
708                Self(::sp_runtime::BoundedVec::new())
709            }
710
711            pub fn with_capacity(_capacity: usize) -> Self {
712                // BoundedVec doesn't have with_capacity, use new() instead
713                Self(::sp_runtime::BoundedVec::new())
714            }
715
716            pub fn try_from_vec(vec: Vec<#inner_type_ident>) -> Result<Self, #error_name> {
717                if vec.len() > #bound as usize {
718                    return Err(#error_name::TooManyItems);
719                }
720
721                ::sp_runtime::BoundedVec::try_from(vec)
722                    .map(#struct_name)
723                    .map_err(|_| #error_name::TooManyItems)
724            }
725
726            pub fn len(&self) -> usize {
727                self.0.len()
728            }
729
730            pub fn is_empty(&self) -> bool {
731                self.0.is_empty()
732            }
733
734            pub fn capacity(&self) -> usize {
735                #bound as usize
736            }
737
738            pub fn max_capacity(&self) -> usize {
739                #bound as usize
740            }
741
742            pub fn remaining_capacity(&self) -> usize {
743                self.capacity() - self.len()
744            }
745
746            pub fn push(&mut self, item: #inner_type_ident) -> Result<(), #error_name> {
747                self.0.try_push(item).map_err(|_| #error_name::TooManyItems)
748            }
749
750            pub fn pop(&mut self) -> Option<#inner_type_ident> {
751                self.0.pop()
752            }
753
754            pub fn insert(&mut self, index: usize, item: #inner_type_ident) -> Result<(), #error_name> {
755                if self.len() >= #bound as usize {
756                    return Err(#error_name::TooManyItems);
757                }
758                self.0.try_insert(index, item).map_err(|_| #error_name::TooManyItems)
759            }
760
761            pub fn remove(&mut self, index: usize) -> #inner_type_ident {
762                self.0.remove(index)
763            }
764
765            pub fn clear(&mut self) {
766                self.0.clear();
767            }
768
769            pub fn get(&self, index: usize) -> Option<&#inner_type_ident> {
770                self.0.get(index)
771            }
772
773            pub fn get_mut(&mut self, index: usize) -> Option<&mut #inner_type_ident> {
774                self.0.get_mut(index)
775            }
776
777            pub fn iter(&self) -> impl Iterator<Item = &#inner_type_ident> {
778                self.0.iter()
779            }
780
781            pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut #inner_type_ident> {
782                self.0.iter_mut()
783            }
784
785            pub fn as_slice(&self) -> &[#inner_type_ident] {
786                &self.0
787            }
788
789            pub fn as_mut_slice(&mut self) -> &mut [#inner_type_ident] {
790                self.0.as_mut()
791            }
792
793            pub fn into_vec(self) -> Vec<#inner_type_ident> {
794                self.0.into_inner()
795            }
796
797            pub fn into_inner(self) -> ::sp_runtime::BoundedVec<#inner_type_ident, ::frame_support::traits::ConstU32<#bound>> {
798                self.0
799            }
800
801            pub fn extend_from_slice(&mut self, slice: &[#inner_type_ident]) -> Result<(), #error_name>
802            where
803                #inner_type_ident: Clone
804            {
805                if self.len() + slice.len() > #bound as usize {
806                    return Err(#error_name::TooManyItems);
807                }
808
809                for item in slice {
810                    self.0.try_push(item.clone()).map_err(|_| #error_name::TooManyItems)?;
811                }
812                Ok(())
813            }
814
815        }
816
817        impl Default for #struct_name {
818            fn default() -> Self {
819                Self::new()
820            }
821        }
822
823        impl core::ops::Deref for #struct_name {
824            type Target = [#inner_type_ident];
825
826            fn deref(&self) -> &Self::Target {
827                &self.0
828            }
829        }
830
831        impl AsRef<[#inner_type_ident]> for #struct_name {
832            fn as_ref(&self) -> &[#inner_type_ident] {
833                &self.0
834            }
835        }
836
837        impl AsMut<[#inner_type_ident]> for #struct_name {
838            fn as_mut(&mut self) -> &mut [#inner_type_ident] {
839                self.0.as_mut()
840            }
841        }
842
843        impl From<#struct_name> for Vec<#inner_type_ident> {
844            fn from(collection: #struct_name) -> Vec<#inner_type_ident> {
845                collection.into_vec()
846            }
847        }
848
849        impl TryFrom<Vec<#inner_type_ident>> for #struct_name {
850            type Error = #error_name;
851
852            fn try_from(vec: Vec<#inner_type_ident>) -> Result<Self, Self::Error> {
853                Self::try_from_vec(vec)
854            }
855        }
856
857        impl<'a> IntoIterator for &'a #struct_name {
858            type Item = &'a #inner_type_ident;
859            type IntoIter = core::slice::Iter<'a, #inner_type_ident>;
860
861            fn into_iter(self) -> Self::IntoIter {
862                self.0.iter()
863            }
864        }
865
866        impl<'a> IntoIterator for &'a mut #struct_name {
867            type Item = &'a mut #inner_type_ident;
868            type IntoIter = core::slice::IterMut<'a, #inner_type_ident>;
869
870            fn into_iter(self) -> Self::IntoIter {
871                self.0.iter_mut()
872            }
873        }
874
875        impl IntoIterator for #struct_name {
876            type Item = #inner_type_ident;
877            type IntoIter = alloc::vec::IntoIter<#inner_type_ident>;
878
879            fn into_iter(self) -> Self::IntoIter {
880                self.0.into_inner().into_iter()
881            }
882        }
883
884        #[cfg(feature = "js")]
885        #[::wasm_bindgen::prelude::wasm_bindgen]
886        impl #struct_name {
887            #[::wasm_bindgen::prelude::wasm_bindgen(constructor)]
888            pub fn js_new() -> #struct_name {
889                Self::new()
890            }
891
892            // Getter/Setter for JS property access
893            #[::wasm_bindgen::prelude::wasm_bindgen(getter)]
894            pub fn items(&self) -> ::wasm_bindgen::JsValue {
895                // Note: Generic conversion not available, use toArray() instead
896                ::wasm_bindgen::JsValue::NULL
897            }
898
899            #[::wasm_bindgen::prelude::wasm_bindgen(setter)]
900            pub fn set_items(&mut self, items: ::wasm_bindgen::JsValue) -> Result<(), ::wasm_bindgen::JsError> {
901                // Note: Generic serialization not available, use fromArray() instead
902                Err(::wasm_bindgen::JsError::new("Use fromArray() method instead"))
903            }
904
905            // Element manipulation methods
906            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "pushItem")]
907            pub fn js_push_item(&mut self, item: #inner_type_ident) -> Result<(), ::wasm_bindgen::JsError> {
908                self.push(item).map_err(|e| ::wasm_bindgen::JsError::new(&e.to_string()))
909            }
910
911            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "popItem")]
912            pub fn js_pop_item(&mut self) -> Option<#inner_type_ident> {
913                self.pop()
914            }
915
916            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "getItem")]
917            pub fn js_get_item(&self, index: usize) -> Option<#inner_type_ident>
918            where
919                #inner_type_ident: Clone
920            {
921                self.get(index).cloned()
922            }
923
924            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "insertItem")]
925            pub fn js_insert_item(&mut self, index: usize, item: #inner_type_ident) -> Result<(), ::wasm_bindgen::JsError> {
926                self.insert(index, item).map_err(|e| ::wasm_bindgen::JsError::new(&e.to_string()))
927            }
928
929            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "removeItem")]
930            pub fn js_remove_item(&mut self, index: usize) -> Result<#inner_type_ident, ::wasm_bindgen::JsError> {
931                if index >= self.len() {
932                    return Err(::wasm_bindgen::JsError::new("Index out of bounds"));
933                }
934                Ok(self.remove(index))
935            }
936
937            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "length")]
938            pub fn js_length(&self) -> usize {
939                self.len()
940            }
941
942            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "capacity")]
943            pub fn js_capacity(&self) -> usize {
944                self.capacity()
945            }
946
947            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "maxCapacity")]
948            pub fn js_max_capacity(&self) -> usize {
949                self.max_capacity()
950            }
951
952            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "remainingCapacity")]
953            pub fn js_remaining_capacity(&self) -> usize {
954                self.remaining_capacity()
955            }
956
957            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "isEmpty")]
958            pub fn js_is_empty(&self) -> bool {
959                self.is_empty()
960            }
961
962            #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "clear")]
963            pub fn js_clear(&mut self) {
964                self.clear()
965            }
966
967
968            // Note: Conversion to/from JS Array requires type-specific implementation
969            // for each type because wasm_bindgen doesn't support generics.
970        }
971    };
972
973    TokenStream::from(expanded)
974}