use crate::string::*;
use core::fmt::{Debug, Display};
use core::hash::Hash;
use core::ops::Deref;
use iceoryx2_log::fail;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SemanticStringError {
InvalidContent,
ExceedsMaximumLength,
}
impl From<StringModificationError> for SemanticStringError {
fn from(value: StringModificationError) -> Self {
match value {
StringModificationError::InsertWouldExceedCapacity => {
SemanticStringError::ExceedsMaximumLength
}
StringModificationError::InvalidCharacter => SemanticStringError::InvalidContent,
}
}
}
impl core::fmt::Display for SemanticStringError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::write!(f, "SemanticStringError::{self:?}")
}
}
impl core::error::Error for SemanticStringError {}
#[doc(hidden)]
pub mod internal {
use super::*;
pub trait SemanticStringAccessor<const CAPACITY: usize> {
unsafe fn new_empty() -> Self;
unsafe fn get_mut_string(&mut self) -> &mut StaticString<CAPACITY>;
fn is_invalid_content(string: &[u8]) -> bool;
fn does_contain_invalid_characters(string: &[u8]) -> bool;
}
}
pub trait SemanticString<const CAPACITY: usize>:
internal::SemanticStringAccessor<CAPACITY>
+ Debug
+ Display
+ Sized
+ Deref<Target = [u8]>
+ PartialEq
+ Eq
+ Hash
+ Clone
+ Copy
{
fn as_string(&self) -> &StaticString<CAPACITY>;
fn new(value: &[u8]) -> Result<Self, SemanticStringError> {
let msg = "Unable to create SemanticString";
let origin = "SemanticString::new()";
let mut new_self =
unsafe { <Self as internal::SemanticStringAccessor<CAPACITY>>::new_empty() };
fail!(from origin, when new_self.push_bytes(value),
"{} due to an invalid value \"{}\".", msg, as_escaped_string(value));
Ok(new_self)
}
unsafe fn new_unchecked(bytes: &[u8]) -> Self;
unsafe fn from_c_str(ptr: *const core::ffi::c_char) -> Result<Self, SemanticStringError> {
unsafe {
Self::new(core::slice::from_raw_parts(
ptr.cast(),
strnlen(ptr, CAPACITY + 1),
))
}
}
fn as_bytes(&self) -> &[u8] {
self.as_string().as_bytes()
}
fn as_c_str(&self) -> *const core::ffi::c_char {
self.as_string().as_c_str()
}
fn capacity(&self) -> usize {
CAPACITY
}
fn find(&self, bytes: &[u8]) -> Option<usize> {
self.as_string().find(bytes)
}
fn rfind(&self, bytes: &[u8]) -> Option<usize> {
self.as_string().find(bytes)
}
fn is_full(&self) -> bool {
self.as_string().is_full()
}
fn is_empty(&self) -> bool {
self.as_string().is_empty()
}
fn len(&self) -> usize {
self.as_string().len()
}
fn insert(&mut self, idx: usize, byte: u8) -> Result<(), SemanticStringError> {
self.insert_bytes(idx, &[byte; 1])
}
fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), SemanticStringError> {
let msg = "Unable to insert byte string";
fail!(from self, when unsafe { self.get_mut_string().insert_bytes(idx, bytes) },
with SemanticStringError::ExceedsMaximumLength,
"{} \"{}\" since it would exceed the maximum allowed length of {}.",
msg, as_escaped_string(bytes), CAPACITY);
if Self::is_invalid_content(self.as_bytes()) {
unsafe { self.get_mut_string().remove_range(idx, bytes.len()) };
fail!(from self, with SemanticStringError::InvalidContent,
"{} \"{}\" since it would result in an illegal content.",
msg, as_escaped_string(bytes));
}
Ok(())
}
unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]);
fn normalize(&self) -> Self;
fn pop(&mut self) -> Result<Option<u8>, SemanticStringError> {
if self.len() == 0 {
return Ok(None);
}
self.remove(self.len() - 1)
}
fn push(&mut self, byte: u8) -> Result<(), SemanticStringError> {
self.insert(self.len(), byte)
}
fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), SemanticStringError> {
self.insert_bytes(self.len(), bytes)
}
fn remove(&mut self, idx: usize) -> Result<Option<u8>, SemanticStringError> {
let mut temp = *self.as_string();
let value = temp.remove(idx);
if Self::is_invalid_content(temp.as_bytes()) {
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to remove character at position {} since it would result in an illegal content.",
idx);
}
unsafe { *self.get_mut_string() = temp };
Ok(value)
}
fn remove_range(&mut self, idx: usize, len: usize) -> Result<(), SemanticStringError> {
let mut temp = *self.as_string();
temp.remove_range(idx, len);
if Self::is_invalid_content(temp.as_bytes()) {
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to remove range from {} with lenght {} since it would result in the illegal content \"{}\".",
idx, len, temp);
}
unsafe { self.get_mut_string().remove_range(idx, len) };
Ok(())
}
fn retain<F: FnMut(u8) -> bool>(&mut self, f: F) -> Result<(), SemanticStringError> {
let mut temp = *self.as_string();
temp.retain(f);
if Self::is_invalid_content(temp.as_bytes()) {
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to retain characters from string since it would result in the illegal content \"{}\".",
temp);
}
unsafe { *self.get_mut_string() = temp };
Ok(())
}
fn strip_prefix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
let mut temp = *self.as_string();
if !temp.strip_prefix(bytes) {
return Ok(false);
}
if Self::is_invalid_content(temp.as_bytes()) {
let mut prefix = StaticString::<123>::new();
unsafe { prefix.insert_bytes_unchecked(0, bytes) };
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
prefix, temp);
}
unsafe { self.get_mut_string().strip_prefix(bytes) };
Ok(true)
}
fn strip_suffix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
let mut temp = *self.as_string();
if !temp.strip_suffix(bytes) {
return Ok(false);
}
if Self::is_invalid_content(temp.as_bytes()) {
let mut prefix = StaticString::<123>::new();
unsafe { prefix.insert_bytes_unchecked(0, bytes) };
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
prefix, temp);
}
unsafe { self.get_mut_string().strip_suffix(bytes) };
Ok(true)
}
fn truncate(&mut self, new_len: usize) -> Result<(), SemanticStringError> {
let mut temp = *self.as_string();
temp.truncate(new_len);
if Self::is_invalid_content(temp.as_bytes()) {
fail!(from self, with SemanticStringError::InvalidContent,
"Unable to truncate characters to {} since it would result in the illegal content \"{}\".",
new_len, temp);
}
unsafe { self.get_mut_string().truncate(new_len) };
Ok(())
}
}
#[macro_export(local_inner_macros)]
macro_rules! semantic_string {
{$(#[$documentation:meta])*
name: $string_name:ident,
capacity: $capacity:expr,
invalid_content: $invalid_content:expr,
invalid_characters: $invalid_characters:expr,
normalize: $normalize:expr} => {
$(#[$documentation])*
#[repr(C)]
#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, ZeroCopySend)]
pub struct $string_name {
value: iceoryx2_bb_container::string::StaticString<$capacity>
}
pub(crate) mod VisitorType {
pub(crate) struct $string_name;
}
impl<'de> serde::de::Visitor<'de> for VisitorType::$string_name {
type Value = $string_name;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("a string containing the service name")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match $string_name::new(v.as_bytes()) {
Ok(v) => Ok(v),
Err(v) => Err(E::custom(alloc::format!("invalid {} provided {:?}.", core::stringify!($string_name), v))),
}
}
}
impl<'de> serde::Deserialize<'de> for $string_name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(VisitorType::$string_name)
}
}
impl serde::Serialize for $string_name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(core::str::from_utf8(self.as_bytes()).unwrap())
}
}
impl iceoryx2_bb_container::semantic_string::SemanticString<$capacity> for $string_name {
fn as_string(&self) -> &iceoryx2_bb_container::string::StaticString<$capacity> {
&self.value
}
fn normalize(&self) -> Self {
$normalize(self)
}
unsafe fn new_unchecked(bytes: &[u8]) -> Self {
Self {
value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(bytes),
}
}
unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
use iceoryx2_bb_container::string::String;
self.value.insert_bytes_unchecked(idx, bytes);
}
}
impl $string_name {
pub const unsafe fn new_unchecked_const(value: &[u8]) -> $string_name {
core::debug_assert!(value.len() <= $capacity);
$string_name {
value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(value),
}
}
pub const fn max_len() -> usize {
$capacity
}
pub const fn as_bytes_const(&self) -> &[u8] {
self.value.as_bytes_const()
}
}
impl core::fmt::Display for $string_name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::write!(f, "{}", self.value)
}
}
impl Hash for $string_name {
fn hash<H: Hasher>(&self, state: &mut H) {
self.normalize().as_bytes().hash(state)
}
}
impl From<$string_name> for String {
fn from(value: $string_name) -> String {
unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
}
}
impl From<&$string_name> for String {
fn from(value: &$string_name) -> String {
unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
}
}
impl core::convert::TryFrom<&str> for $string_name {
type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value.as_bytes())
}
}
impl PartialEq<$string_name> for $string_name {
fn eq(&self, other: &$string_name) -> bool {
*self.normalize().as_bytes() == *other.normalize().as_bytes()
}
}
impl PartialEq<&[u8]> for $string_name {
fn eq(&self, other: &&[u8]) -> bool {
let other = match $string_name::new(other) {
Ok(other) => other,
Err(_) => return false,
};
*self == other
}
}
impl PartialEq<&[u8]> for &$string_name {
fn eq(&self, other: &&[u8]) -> bool {
let other = match $string_name::new(other) {
Ok(other) => other,
Err(_) => return false,
};
**self == other
}
}
impl<const CAPACITY: usize> PartialEq<[u8; CAPACITY]> for $string_name {
fn eq(&self, other: &[u8; CAPACITY]) -> bool {
let other = match $string_name::new(other) {
Ok(other) => other,
Err(_) => return false,
};
*self == other
}
}
impl<const CAPACITY: usize> PartialEq<&[u8; CAPACITY]> for $string_name {
fn eq(&self, other: &&[u8; CAPACITY]) -> bool {
let other = match $string_name::new(*other) {
Ok(other) => other,
Err(_) => return false,
};
*self == other
}
}
impl PartialEq<&str> for &$string_name {
fn eq(&self, other: &&str) -> bool {
let other = match $string_name::new(other.as_bytes()) {
Ok(other) => other,
Err(_) => return false,
};
**self == other
}
}
impl core::ops::Deref for $string_name {
type Target = [u8];
fn deref(&self) -> &Self::Target {
use iceoryx2_bb_container::string::String;
self.value.as_bytes()
}
}
impl iceoryx2_bb_container::semantic_string::internal::SemanticStringAccessor<$capacity> for $string_name {
unsafe fn new_empty() -> Self {
Self {
value: iceoryx2_bb_container::string::StaticString::new(),
}
}
unsafe fn get_mut_string(&mut self) -> &mut iceoryx2_bb_container::string::StaticString<$capacity> {
&mut self.value
}
fn is_invalid_content(string: &[u8]) -> bool {
if Self::does_contain_invalid_characters(string) {
return true;
}
$invalid_content(string)
}
fn does_contain_invalid_characters(string: &[u8]) -> bool {
if core::str::from_utf8(string).is_err() {
return true;
}
$invalid_characters(string)
}
}
};
}