Skip to main content

ab_io_type/
fixed_capacity_string.rs

1#[cfg(test)]
2mod tests;
3
4use crate::alignment::Alignment;
5use crate::fixed_capacity_elements::{FixedCapacityElements, SUPPORTED_CAPACITY};
6use crate::metadata::{IoTypeMetadataKind, MAX_METADATA_CAPACITY, concat_metadata_sources};
7use crate::trivial_type::TrivialType;
8use crate::unaligned::Unaligned;
9use core::ops::{Deref, DerefMut};
10
11/// Container for storing a UTF-8 string limited by the specified fixed bytes capacity.
12///
13/// This is a string only by convention, there is no runtime verification done, contents is
14/// treated as regular bytes.
15///
16/// This is just a wrapper for [`FixedCapacityElements`] of bytes that the type dereferences to
17/// with a different semantic meaning.
18///
19/// `CAPACITY` must not exceed `u32::MAX - 4`.
20#[derive(Debug, Copy, Clone)]
21#[repr(C)]
22pub struct FixedCapacityString<const CAPACITY: usize> {
23    bytes: FixedCapacityElements<u8, CAPACITY>,
24}
25
26impl<const CAPACITY: usize> Default for FixedCapacityString<CAPACITY>
27where
28    [(); SUPPORTED_CAPACITY::<u8, CAPACITY>]:,
29{
30    #[inline(always)]
31    fn default() -> Self {
32        Self {
33            bytes: FixedCapacityElements::default(),
34        }
35    }
36}
37
38impl<const CAPACITY: usize> Deref for FixedCapacityString<CAPACITY> {
39    type Target = FixedCapacityElements<u8, CAPACITY>;
40
41    fn deref(&self) -> &Self::Target {
42        &self.bytes
43    }
44}
45
46impl<const CAPACITY: usize> DerefMut for FixedCapacityString<CAPACITY> {
47    fn deref_mut(&mut self) -> &mut Self::Target {
48        &mut self.bytes
49    }
50}
51
52// SAFETY: The only field is `TrivialType`, so the layout is the same and there is no padding, the
53// parsing step parses the field
54unsafe impl<const CAPACITY: usize> TrivialType for FixedCapacityString<CAPACITY>
55where
56    [(); SUPPORTED_CAPACITY::<u8, CAPACITY>]:,
57{
58    const ALIGNMENT: Alignment = FixedCapacityElements::<u8, CAPACITY>::ALIGNMENT;
59    // Casting `CAPACITY` to `u32` is lossless, which is checked by the `SUPPORTED_CAPACITY` bound
60    const METADATA: &[u8] = {
61        #[inline(always)]
62        const fn metadata(capacity: u32) -> ([u8; MAX_METADATA_CAPACITY], usize) {
63            concat_metadata_sources(&[
64                &[IoTypeMetadataKind::FixedCapacityString as u8],
65                &capacity.to_le_bytes(),
66            ])
67        }
68
69        // The reference extends the lifetime of metadata scratch to that of the constant
70        let (metadata_scratch, metadata_size) = &metadata(CAPACITY as u32);
71        metadata_scratch.split_at(*metadata_size).0
72    };
73
74    #[inline(always)]
75    fn try_from_bytes_unaligned(bytes: &[u8]) -> Option<(&Unaligned<Self>, &[u8])> {
76        let (_value, remainder) =
77            FixedCapacityElements::<u8, CAPACITY>::try_from_bytes_unaligned(bytes)?;
78        // SAFETY: `bytes` start with a valid value of the only field (parsing step contract), which
79        // has the same layout as `Self`, `Unaligned<Self>` has alignment 1 and the size of `Self`
80        let value = unsafe { bytes.as_ptr().cast::<Unaligned<Self>>().as_ref_unchecked() };
81        Some((value, remainder))
82    }
83}
84
85impl<const CAPACITY: usize> FixedCapacityString<CAPACITY>
86where
87    [(); SUPPORTED_CAPACITY::<u8, CAPACITY>]:,
88{
89    /// Try to create an instance from provided string.
90    ///
91    /// Returns `None` if provided string does not fit into the capacity.
92    #[inline(always)]
93    pub fn try_from_str(s: &str) -> Option<Self> {
94        Self::try_from_slice(s.as_bytes())
95    }
96
97    /// Try to create an instance from provided bytes.
98    ///
99    /// Returns `None` if provided bytes do not fit into the capacity.
100    #[inline(always)]
101    pub fn try_from_slice(bytes: &[u8]) -> Option<Self> {
102        Some(Self {
103            bytes: FixedCapacityElements::try_from_slice(bytes)?,
104        })
105    }
106}