use core::str::{Utf8Error, from_utf8, from_utf8_unchecked};
use const_fn::const_fn;
use crate::{
fmt::const_debug_assert,
types::{MqttBinary, TooLargeToEncode},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MqttStringError {
Utf8Error(Utf8Error),
NullCharacter,
TooLargeToEncode,
}
#[cfg(feature = "defmt")]
impl defmt::Format for MqttStringError {
fn format(&self, fmt: defmt::Formatter) {
match self {
Self::Utf8Error(e) => defmt::write!(
fmt,
"Utf8Error(Utf8Error {{ valid_up_to: {:?}, error_len: {:?} }})",
e.valid_up_to(),
e.error_len()
),
Self::NullCharacter => defmt::write!(fmt, "NullCharacter"),
Self::TooLargeToEncode => defmt::write!(fmt, "TooLargeToEncode"),
}
}
}
impl From<Utf8Error> for MqttStringError {
fn from(e: Utf8Error) -> Self {
Self::Utf8Error(e)
}
}
impl From<TooLargeToEncode> for MqttStringError {
fn from(_: TooLargeToEncode) -> Self {
Self::TooLargeToEncode
}
}
#[derive(Default, Clone, PartialEq, Eq)]
pub struct MqttString<'s>(pub(crate) MqttBinary<'s>);
impl core::fmt::Debug for MqttString<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("MqttString").field(&self.as_ref()).finish()
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for MqttString<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "MqttString({:?})", self.as_ref());
}
}
impl<'s> TryFrom<MqttBinary<'s>> for MqttString<'s> {
type Error = MqttStringError;
fn try_from(value: MqttBinary<'s>) -> Result<Self, Self::Error> {
Self::from_utf8_binary(value)
}
}
impl<'s> TryFrom<&'s str> for MqttString<'s> {
type Error = MqttStringError;
fn try_from(value: &'s str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
impl AsRef<str> for MqttString<'_> {
fn as_ref(&self) -> &str {
unsafe { from_utf8_unchecked(self.0.as_ref()) }
}
}
impl<'s> MqttString<'s> {
pub const MAX_LENGTH: usize = MqttBinary::MAX_LENGTH;
#[const_fn(cfg(not(feature = "alloc")))]
pub const fn from_utf8_binary(b: MqttBinary<'s>) -> Result<Self, MqttStringError> {
let mut i = 0;
while i < b.as_bytes().len() {
if b.as_bytes()[i] == 0 {
return Err(MqttStringError::NullCharacter);
}
i += 1;
}
match from_utf8(b.as_bytes()) {
Ok(_) => Ok(Self(b)),
Err(e) => Err(MqttStringError::Utf8Error(e)),
}
}
#[must_use]
pub const unsafe fn from_utf8_binary_unchecked(b: MqttBinary<'s>) -> Self {
if cfg!(debug_assertions) {
let mut i = 0;
while i < b.as_bytes().len() {
const_debug_assert!(b.as_bytes()[i] != 0);
i += 1;
}
}
const_debug_assert!(from_utf8(b.as_bytes()).is_ok());
Self(b)
}
pub const fn from_str(s: &'s str) -> Result<Self, MqttStringError> {
let mut i = 0;
while i < s.len() {
if s.as_bytes()[i] == 0 {
return Err(MqttStringError::NullCharacter);
}
i += 1;
}
match s.len() {
..=Self::MAX_LENGTH => Ok(Self(MqttBinary::from_slice_unchecked(s.as_bytes()))),
_ => Err(MqttStringError::TooLargeToEncode),
}
}
#[must_use]
pub const fn from_str_unchecked(s: &'s str) -> Self {
if cfg!(debug_assertions) {
let mut i = 0;
while i < s.len() {
const_debug_assert!(s.as_bytes()[i] != 0);
i += 1;
}
}
Self(MqttBinary::from_slice_unchecked(s.as_bytes()))
}
#[inline]
#[must_use]
pub const fn len(&self) -> u16 {
self.0.len()
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
#[must_use]
pub const fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(self.0.as_bytes()) }
}
#[inline]
#[must_use]
pub const fn as_borrowed(&'s self) -> Self {
Self(self.0.as_borrowed())
}
}