iceoryx2_bb_container/
semantic_string.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! The [`SemanticString`](crate::semantic_string::SemanticString) is a trait for
14//! [`StaticString`](crate::string::StaticString) to create
15//! strong string types with semantic content contracts. They can be created
16//! with the help of the [`semantic_string`](crate::semantic_string!) macro.
17//!
18//! # Example, create a string that can contain a posix group name
19//!
20//! ```
21//! extern crate alloc;
22//!
23//! pub use iceoryx2_bb_container::semantic_string::SemanticString;
24//! use iceoryx2_bb_derive_macros::ZeroCopySend;
25//! use iceoryx2_bb_elementary_traits::zero_copy_send::ZeroCopySend;
26//!
27//! use core::hash::{Hash, Hasher};
28//! use iceoryx2_bb_container::semantic_string;
29//!
30//! const GROUP_NAME_LENGTH: usize = 31;
31//! semantic_string! {
32//!   // Name of the type
33//!   name: GroupName,
34//!   // The underlying capacity of the StaticString
35//!   capacity: GROUP_NAME_LENGTH,
36//!   // Callable that shall return true when the provided string contains invalid content
37//!   invalid_content: |string: &[u8]| {
38//!     if string.is_empty() {
39//!         // group names are not allowed to be empty
40//!         return true;
41//!     }
42//!
43//!     // group names are not allowed to start with a number or -
44//!     matches!(string[0], b'-' | b'0'..=b'9')
45//!   },
46//!   // Callable that shall return true when the provided string contains invalid characters
47//!   invalid_characters: |string: &[u8]| {
48//!     for value in string {
49//!         match value {
50//!             // only non-capital letters, numbers and - is allowed
51//!             b'a'..=b'z' | b'0'..=b'9' | b'-' => (),
52//!             _ => return true,
53//!         }
54//!     }
55//!
56//!     false
57//!   },
58//!   // When a SemanticString has multiple representations of the same semantic content, this
59//!   // callable shall convert the content to a uniform representation.
60//!   // Example: The path to `/tmp` can be also expressed as `/tmp/` or `////tmp////`
61//!   normalize: |this: &GroupName| {
62//!       this.clone()
63//!   }
64//! }
65//! ```
66
67use crate::string::*;
68use core::fmt::{Debug, Display};
69use core::hash::Hash;
70use core::ops::Deref;
71use iceoryx2_log::fail;
72
73/// Failures that can occur when a [`SemanticString`] is created or modified
74#[derive(Debug, Clone, Copy, Eq, PartialEq)]
75pub enum SemanticStringError {
76    /// The modification would lead to a [`SemanticString`] with invalid content.
77    InvalidContent,
78    /// The added content would exceed the maximum capacity of the [`SemanticString`]
79    ExceedsMaximumLength,
80}
81
82impl From<StringModificationError> for SemanticStringError {
83    fn from(value: StringModificationError) -> Self {
84        match value {
85            StringModificationError::InsertWouldExceedCapacity => {
86                SemanticStringError::ExceedsMaximumLength
87            }
88            StringModificationError::InvalidCharacter => SemanticStringError::InvalidContent,
89        }
90    }
91}
92
93impl core::fmt::Display for SemanticStringError {
94    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95        core::write!(f, "SemanticStringError::{self:?}")
96    }
97}
98
99impl core::error::Error for SemanticStringError {}
100
101#[doc(hidden)]
102pub mod internal {
103    use super::*;
104
105    pub trait SemanticStringAccessor<const CAPACITY: usize> {
106        unsafe fn new_empty() -> Self;
107        unsafe fn get_mut_string(&mut self) -> &mut StaticString<CAPACITY>;
108        fn is_invalid_content(string: &[u8]) -> bool;
109        fn does_contain_invalid_characters(string: &[u8]) -> bool;
110    }
111}
112
113/// Trait that defines the methods a [`StaticString`] with context semantics, a
114/// [`SemanticString`] shares. A new [`SemanticString`] can be created with the [`crate::semantic_string!`]
115/// macro. For the usage, see [`mod@crate::semantic_string`].
116pub trait SemanticString<const CAPACITY: usize>:
117    internal::SemanticStringAccessor<CAPACITY>
118    + Debug
119    + Display
120    + Sized
121    + Deref<Target = [u8]>
122    + PartialEq
123    + Eq
124    + Hash
125    + Clone
126    + Copy
127{
128    /// Returns a reference to the underlying [`StaticString`]
129    fn as_string(&self) -> &StaticString<CAPACITY>;
130
131    /// Creates a new content. If it contains invalid characters or exceeds the maximum supported
132    /// length of the system or contains illegal strings it fails.
133    fn new(value: &[u8]) -> Result<Self, SemanticStringError> {
134        let msg = "Unable to create SemanticString";
135        let origin = "SemanticString::new()";
136
137        let mut new_self =
138            unsafe { <Self as internal::SemanticStringAccessor<CAPACITY>>::new_empty() };
139        fail!(from origin, when new_self.push_bytes(value),
140            "{} due to an invalid value \"{}\".", msg, as_escaped_string(value));
141
142        Ok(new_self)
143    }
144
145    /// Creates a new content but does not verify that it does not contain invalid characters.
146    ///
147    /// # Safety
148    ///
149    ///   * The slice must contain only valid characters.
150    ///   * The slice must have a length that is less or equal CAPACITY
151    ///   * The slice must not contain invalid UTF-8 characters
152    ///
153    unsafe fn new_unchecked(bytes: &[u8]) -> Self;
154
155    /// Creates a new content from a given ptr. The user has to ensure that it is null-terminated.
156    ///
157    /// # Safety
158    ///
159    ///   * The pointer must be '\0' (null) terminated
160    ///   * The pointer must be valid and non-null
161    ///   * The contents must have a length that is less or equal CAPACITY
162    ///   * The contents must not contain invalid UTF-8 characters
163    ///
164    unsafe fn from_c_str(ptr: *const core::ffi::c_char) -> Result<Self, SemanticStringError> {
165        Self::new(core::slice::from_raw_parts(
166            ptr.cast(),
167            strnlen(ptr, CAPACITY + 1),
168        ))
169    }
170
171    /// Returns the contents as a slice
172    fn as_bytes(&self) -> &[u8] {
173        self.as_string().as_bytes()
174    }
175
176    /// Returns a zero terminated slice of the underlying bytes
177    fn as_c_str(&self) -> *const core::ffi::c_char {
178        self.as_string().as_c_str()
179    }
180
181    /// Returns the capacity of the file system type
182    fn capacity(&self) -> usize {
183        CAPACITY
184    }
185
186    /// Finds the first occurrence of a  byte string in the given string. If the byte string was
187    /// found the start position of the byte string is returned, otherwise [`None`].
188    fn find(&self, bytes: &[u8]) -> Option<usize> {
189        self.as_string().find(bytes)
190    }
191
192    /// Finds the last occurrence of a byte string in the given string. If the byte string was
193    /// found the start position of the byte string is returned, otherwise [`None`].
194    fn rfind(&self, bytes: &[u8]) -> Option<usize> {
195        self.as_string().find(bytes)
196    }
197
198    /// Returns true when the string is full, otherwise false
199    fn is_full(&self) -> bool {
200        self.as_string().is_full()
201    }
202
203    /// Returns true when the string is empty, otherwise false
204    fn is_empty(&self) -> bool {
205        self.as_string().is_empty()
206    }
207
208    /// Returns the length of the string
209    fn len(&self) -> usize {
210        self.as_string().len()
211    }
212
213    /// Inserts a single byte at a specific position. When the capacity is exceeded, the byte is an
214    /// illegal character or the content would result in an illegal content it fails.
215    fn insert(&mut self, idx: usize, byte: u8) -> Result<(), SemanticStringError> {
216        self.insert_bytes(idx, &[byte; 1])
217    }
218
219    /// Inserts a byte slice at a specific position. When the capacity is exceeded, the byte slice contains
220    /// illegal characters or the content would result in an illegal content it fails.
221    fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), SemanticStringError> {
222        let msg = "Unable to insert byte string";
223        fail!(from self, when unsafe { self.get_mut_string().insert_bytes(idx, bytes) },
224                with SemanticStringError::ExceedsMaximumLength,
225                    "{} \"{}\" since it would exceed the maximum allowed length of {}.",
226                        msg, as_escaped_string(bytes), CAPACITY);
227
228        if Self::is_invalid_content(self.as_bytes()) {
229            unsafe { self.get_mut_string().remove_range(idx, bytes.len()) };
230            fail!(from self, with SemanticStringError::InvalidContent,
231                "{} \"{}\" since it would result in an illegal content.",
232                msg, as_escaped_string(bytes));
233        }
234
235        Ok(())
236    }
237
238    /// Adds bytes to the string without checking if they only contain valid characters or
239    /// would result in a valid result.
240    ///
241    /// # Safety
242    ///
243    ///   * The user must ensure that the bytes contain only valid characters.
244    ///   * The user must ensure that the result, after the bytes were added, is valid.
245    ///   * The slice must have a length that is less or equal CAPACITY
246    ///   * The slice is not contain invalid UTF-8 characters
247    ///
248    unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]);
249
250    /// Normalizes the string. This function is used as basis for [`core::hash::Hash`] and
251    /// [`PartialEq`]. Normalizing a [`SemanticString`] means to bring it to some format so that it
252    /// contains still the same semantic content but in an uniform way so that strings, with the
253    /// same semantic content but different representation compare as equal.
254    fn normalize(&self) -> Self;
255
256    /// Removes the last character. If the string is empty it returns [`None`].
257    /// If the removal would create an illegal content it fails.
258    fn pop(&mut self) -> Result<Option<u8>, SemanticStringError> {
259        if self.len() == 0 {
260            return Ok(None);
261        }
262
263        self.remove(self.len() - 1)
264    }
265
266    /// Adds a single byte at the end. When the capacity is exceeded, the byte is an
267    /// illegal character or the content would result in an illegal content it fails.
268    fn push(&mut self, byte: u8) -> Result<(), SemanticStringError> {
269        self.insert(self.len(), byte)
270    }
271
272    /// Adds a byte slice at the end. When the capacity is exceeded, the byte slice contains
273    /// illegal characters or the content would result in an illegal content it fails.
274    fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), SemanticStringError> {
275        self.insert_bytes(self.len(), bytes)
276    }
277
278    /// Removes a byte at a specific position and returns it.
279    /// If the removal would create an illegal content it fails.
280    fn remove(&mut self, idx: usize) -> Result<Option<u8>, SemanticStringError> {
281        let mut temp = *self.as_string();
282        let value = temp.remove(idx);
283
284        if Self::is_invalid_content(temp.as_bytes()) {
285            fail!(from self, with SemanticStringError::InvalidContent,
286                "Unable to remove character at position {} since it would result in an illegal content.",
287                idx);
288        }
289
290        unsafe { *self.get_mut_string() = temp };
291        Ok(value)
292    }
293
294    /// Removes a range.
295    /// If the removal would create an illegal content it fails.
296    fn remove_range(&mut self, idx: usize, len: usize) -> Result<(), SemanticStringError> {
297        let mut temp = *self.as_string();
298        temp.remove_range(idx, len);
299        if Self::is_invalid_content(temp.as_bytes()) {
300            fail!(from self, with SemanticStringError::InvalidContent,
301                "Unable to remove range from {} with lenght {} since it would result in the illegal content \"{}\".",
302                    idx, len, temp);
303        }
304
305        unsafe { self.get_mut_string().remove_range(idx, len) };
306        Ok(())
307    }
308
309    /// Removes all bytes which satisfy the provided clojure f.
310    /// If the removal would create an illegal content it fails.
311    fn retain<F: FnMut(u8) -> bool>(&mut self, f: F) -> Result<(), SemanticStringError> {
312        let mut temp = *self.as_string();
313        temp.retain(f);
314
315        if Self::is_invalid_content(temp.as_bytes()) {
316            fail!(from self, with SemanticStringError::InvalidContent,
317                "Unable to retain characters from string since it would result in the illegal content \"{}\".",
318                temp);
319        }
320
321        unsafe { *self.get_mut_string() = temp };
322
323        Ok(())
324    }
325
326    /// Removes a prefix. If the prefix does not exist it returns false. If the removal would lead
327    /// to an invalid string content it fails and returns [`SemanticStringError::InvalidContent`].
328    /// After a successful removal it returns true.
329    fn strip_prefix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
330        let mut temp = *self.as_string();
331        if !temp.strip_prefix(bytes) {
332            return Ok(false);
333        }
334
335        if Self::is_invalid_content(temp.as_bytes()) {
336            let mut prefix = StaticString::<123>::new();
337            unsafe { prefix.insert_bytes_unchecked(0, bytes) };
338            fail!(from self, with SemanticStringError::InvalidContent,
339                "Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
340                prefix, temp);
341        }
342
343        unsafe { self.get_mut_string().strip_prefix(bytes) };
344
345        Ok(true)
346    }
347
348    /// Removes a suffix. If the suffix does not exist it returns false. If the removal would lead
349    /// to an invalid string content it fails and returns [`SemanticStringError::InvalidContent`].
350    /// After a successful removal it returns true.
351    fn strip_suffix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
352        let mut temp = *self.as_string();
353        if !temp.strip_suffix(bytes) {
354            return Ok(false);
355        }
356
357        if Self::is_invalid_content(temp.as_bytes()) {
358            let mut prefix = StaticString::<123>::new();
359            unsafe { prefix.insert_bytes_unchecked(0, bytes) };
360            fail!(from self, with SemanticStringError::InvalidContent,
361                "Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
362                prefix, temp);
363        }
364
365        unsafe { self.get_mut_string().strip_suffix(bytes) };
366
367        Ok(true)
368    }
369
370    /// Truncates the string to new_len.
371    fn truncate(&mut self, new_len: usize) -> Result<(), SemanticStringError> {
372        let mut temp = *self.as_string();
373        temp.truncate(new_len);
374
375        if Self::is_invalid_content(temp.as_bytes()) {
376            fail!(from self, with SemanticStringError::InvalidContent,
377                "Unable to truncate characters to {} since it would result in the illegal content \"{}\".",
378                new_len, temp);
379        }
380
381        unsafe { self.get_mut_string().truncate(new_len) };
382        Ok(())
383    }
384}
385
386/// Helper macro to create a new [`SemanticString`]. Usage example can be found here:
387/// [`mod@crate::semantic_string`].
388#[macro_export(local_inner_macros)]
389macro_rules! semantic_string {
390    {$(#[$documentation:meta])*
391     /// Name of the struct
392     name: $string_name:ident,
393     /// Capacity of the underlying StaticString
394     capacity: $capacity:expr,
395     /// Callable that gets a [`&[u8]`] as input and shall return true when the slice contains
396     /// invalid content.
397     invalid_content: $invalid_content:expr,
398     /// Callable that gets a [`&[u8]`] as input and shall return true when the slice contains
399     /// invalid characters.
400     invalid_characters: $invalid_characters:expr,
401     /// Normalizes the content. Required when the same semantical content has multiple
402     /// representations like paths for instance (`/tmp` == `/tmp/`)
403     normalize: $normalize:expr} => {
404        $(#[$documentation])*
405        #[repr(C)]
406        #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, ZeroCopySend)]
407        pub struct $string_name {
408            value: iceoryx2_bb_container::string::StaticString<$capacity>
409        }
410
411        // BEGIN: serde
412        pub(crate) mod VisitorType {
413            pub(crate) struct $string_name;
414        }
415
416        impl<'de> serde::de::Visitor<'de> for VisitorType::$string_name {
417            type Value = $string_name;
418
419            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
420                formatter.write_str("a string containing the service name")
421            }
422
423            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
424            where
425                E: serde::de::Error,
426            {
427                match $string_name::new(v.as_bytes()) {
428                    Ok(v) => Ok(v),
429                    Err(v) => Err(E::custom(alloc::format!("invalid {} provided {:?}.", core::stringify!($string_name), v))),
430                }
431            }
432        }
433
434        impl<'de> serde::Deserialize<'de> for $string_name {
435            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
436            where
437                D: serde::Deserializer<'de>,
438            {
439                deserializer.deserialize_str(VisitorType::$string_name)
440            }
441        }
442
443        impl serde::Serialize for $string_name {
444            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
445            where
446                S: serde::Serializer,
447            {
448                serializer.serialize_str(core::str::from_utf8(self.as_bytes()).unwrap())
449            }
450        }
451        // END: serde
452
453        impl iceoryx2_bb_container::semantic_string::SemanticString<$capacity> for $string_name {
454            fn as_string(&self) -> &iceoryx2_bb_container::string::StaticString<$capacity> {
455                &self.value
456            }
457
458            fn normalize(&self) -> Self {
459                $normalize(self)
460            }
461
462            unsafe fn new_unchecked(bytes: &[u8]) -> Self {
463                Self {
464                    value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(bytes),
465                }
466            }
467
468            unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
469                use iceoryx2_bb_container::string::String;
470                self.value.insert_bytes_unchecked(idx, bytes);
471            }
472        }
473
474        impl $string_name {
475            /// Creates a new instance.
476            ///
477            /// # Safety
478            ///
479            /// * The provided slice must have a length smaller or equal to the capacity. `value.len() < Self::max_len()`
480            /// * The contents of the slice must follow the content contract
481            ///
482            pub const unsafe fn new_unchecked_const(value: &[u8]) -> $string_name {
483                core::debug_assert!(value.len() <= $capacity);
484                $string_name {
485                    value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(value),
486                }
487            }
488
489            /// Returns the maximum supported length
490            pub const fn max_len() -> usize {
491                $capacity
492            }
493
494            pub const fn as_bytes_const(&self) -> &[u8] {
495                self.value.as_bytes_const()
496            }
497        }
498
499        impl core::fmt::Display for $string_name {
500            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
501                core::write!(f, "{}", self.value)
502            }
503        }
504
505        impl Hash for $string_name {
506            fn hash<H: Hasher>(&self, state: &mut H) {
507                self.normalize().as_bytes().hash(state)
508            }
509        }
510
511        impl From<$string_name> for String {
512            fn from(value: $string_name) -> String {
513                // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
514                unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
515            }
516        }
517
518        impl From<&$string_name> for String {
519            fn from(value: &$string_name) -> String {
520                // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
521                unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
522            }
523        }
524
525        impl core::convert::TryFrom<&str> for $string_name {
526            type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
527
528            fn try_from(value: &str) -> Result<Self, Self::Error> {
529                Self::new(value.as_bytes())
530            }
531        }
532
533        impl PartialEq<$string_name> for $string_name {
534            fn eq(&self, other: &$string_name) -> bool {
535                *self.normalize().as_bytes() == *other.normalize().as_bytes()
536            }
537        }
538
539        impl PartialEq<&[u8]> for $string_name {
540            fn eq(&self, other: &&[u8]) -> bool {
541                let other = match $string_name::new(other) {
542                    Ok(other) => other,
543                    Err(_) => return false,
544                };
545
546                *self == other
547            }
548        }
549
550        impl PartialEq<&[u8]> for &$string_name {
551            fn eq(&self, other: &&[u8]) -> bool {
552                let other = match $string_name::new(other) {
553                    Ok(other) => other,
554                    Err(_) => return false,
555                };
556
557                **self == other
558            }
559        }
560
561        impl<const CAPACITY: usize> PartialEq<[u8; CAPACITY]> for $string_name {
562            fn eq(&self, other: &[u8; CAPACITY]) -> bool {
563                let other = match $string_name::new(other) {
564                    Ok(other) => other,
565                    Err(_) => return false,
566                };
567
568                *self == other
569            }
570        }
571
572        impl<const CAPACITY: usize> PartialEq<&[u8; CAPACITY]> for $string_name {
573            fn eq(&self, other: &&[u8; CAPACITY]) -> bool {
574                let other = match $string_name::new(*other) {
575                    Ok(other) => other,
576                    Err(_) => return false,
577                };
578
579                *self == other
580            }
581        }
582
583        impl PartialEq<&str> for &$string_name {
584            fn eq(&self, other: &&str) -> bool {
585                let other = match $string_name::new(other.as_bytes()) {
586                    Ok(other) => other,
587                    Err(_) => return false,
588                };
589
590                **self == other
591            }
592        }
593
594        impl core::ops::Deref for $string_name {
595            type Target = [u8];
596
597            fn deref(&self) -> &Self::Target {
598                use iceoryx2_bb_container::string::String;
599                self.value.as_bytes()
600            }
601        }
602
603        impl iceoryx2_bb_container::semantic_string::internal::SemanticStringAccessor<$capacity> for $string_name {
604            unsafe fn new_empty() -> Self {
605                Self {
606                    value: iceoryx2_bb_container::string::StaticString::new(),
607                }
608            }
609
610            unsafe fn get_mut_string(&mut self) -> &mut iceoryx2_bb_container::string::StaticString<$capacity> {
611                &mut self.value
612            }
613
614            fn is_invalid_content(string: &[u8]) -> bool {
615                if Self::does_contain_invalid_characters(string) {
616                    return true;
617                }
618
619                $invalid_content(string)
620            }
621
622            fn does_contain_invalid_characters(string: &[u8]) -> bool {
623                if core::str::from_utf8(string).is_err() {
624                    return true;
625                }
626
627                $invalid_characters(string)
628            }
629        }
630
631    };
632}