#![allow(unused_imports)]
#![allow(dead_code)]
use crate::cfg::*;
pub use crate::error::*;
use core::fmt;
use core::marker::PhantomData;
use core::ops::Deref;
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct StringM<const MAX: u32 = { u32::MAX }>(pub Vec<u8>);
#[cfg(all(feature = "serde", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
impl<'de, const MAX: u32> Deserialize<'de> for StringM<MAX> {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(DeError::custom)
}
}
#[cfg(all(feature = "serde", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
impl<const MAX: u32> Serialize for StringM<MAX> {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.0.len() > Self::MAX_LEN {
return Err(<S::Error as SerError>::custom(Error::LengthExceedsMax));
}
ser.collect_str(self)
}
}
impl<const MAX: u32> core::fmt::Display for StringM<MAX> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "alloc")]
let v = &self.0;
#[cfg(not(feature = "alloc"))]
let v = self.0;
for b in escape_bytes::Escape::new(v) {
write!(f, "{}", b as char)?;
}
Ok(())
}
}
impl<const MAX: u32> core::fmt::Debug for StringM<MAX> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "alloc")]
let v = &self.0;
#[cfg(not(feature = "alloc"))]
let v = self.0;
write!(f, "StringM(")?;
for b in escape_bytes::Escape::new(v) {
write!(f, "{}", b as char)?;
}
write!(f, ")")?;
Ok(())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> core::str::FromStr for StringM<MAX> {
type Err = Error;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
if s.len() > Self::MAX_LEN.saturating_mul(4) {
return Err(Error::LengthExceedsMax);
}
escape_bytes::unescape(s.as_bytes())
.map_err(|_| Error::Invalid)?
.try_into()
}
}
#[cfg(feature = "schemars")]
#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
impl<const MAX: u32> schemars::JsonSchema for StringM<MAX> {
fn schema_name() -> alloc::borrow::Cow<'static, str> {
alloc::format!("StringM<{}>", MAX).into()
}
fn schema_id() -> alloc::borrow::Cow<'static, str> {
alloc::format!("xdr_common::StringM<{}>", MAX).into()
}
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"x-max-unescaped-bytes": MAX,
"description": "XDR string with escape_bytes encoding. x-max-unescaped-bytes is the byte limit of the unescaped XDR payload, not the character count of the JSON value.",
})
}
}
impl<const MAX: u32> Deref for StringM<MAX> {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const MAX: u32> Default for StringM<MAX> {
fn default() -> Self {
Self(Vec::default())
}
}
impl<const MAX: u32> StringM<MAX> {
pub const MAX_LEN: usize = { MAX as usize };
#[must_use]
#[allow(clippy::unused_self)]
pub fn max_len(&self) -> usize {
Self::MAX_LEN
}
#[must_use]
pub fn as_vec(&self) -> &Vec<u8> {
self.as_ref()
}
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
#[cfg(feature = "alloc")]
let s: &[u8] = self.0.as_slice();
#[cfg(not(feature = "alloc"))]
let s: &[u8] = self.0;
s
}
}
impl<const MAX: u32> StringM<MAX> {
#[must_use]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
#[must_use]
pub fn into_vec(self) -> Vec<u8> {
self.into()
}
}
impl<const MAX: u32> StringM<MAX> {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_utf8_string(&self) -> Result<String, Error> {
self.try_into()
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn into_utf8_string(self) -> Result<String, Error> {
self.try_into()
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[must_use]
pub fn to_utf8_string_lossy(&self) -> String {
String::from_utf8_lossy(&self.0).into_owned()
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[must_use]
pub fn into_utf8_string_lossy(self) -> String {
String::from_utf8_lossy(&self.0).into_owned()
}
}
impl<const MAX: u32> TryFrom<Vec<u8>> for StringM<MAX> {
type Error = Error;
fn try_from(v: Vec<u8>) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v))
} else {
Err(Error::LengthExceedsMax)
}
}
}
impl<const MAX: u32> From<StringM<MAX>> for Vec<u8> {
fn from(v: StringM<MAX>) -> Self {
v.0
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> From<&StringM<MAX>> for Vec<u8> {
fn from(v: &StringM<MAX>) -> Self {
v.0.clone()
}
}
impl<const MAX: u32> AsRef<Vec<u8>> for StringM<MAX> {
fn as_ref(&self) -> &Vec<u8> {
&self.0
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<&Vec<u8>> for StringM<MAX> {
type Error = Error;
fn try_from(v: &Vec<u8>) -> Result<Self, Error> {
v.as_slice().try_into()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<&[u8]> for StringM<MAX> {
type Error = Error;
fn try_from(v: &[u8]) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.to_vec()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
impl<const MAX: u32> AsRef<[u8]> for StringM<MAX> {
#[cfg(feature = "alloc")]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
#[cfg(not(feature = "alloc"))]
fn as_ref(&self) -> &[u8] {
self.0
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const N: usize, const MAX: u32> TryFrom<[u8; N]> for StringM<MAX> {
type Error = Error;
fn try_from(v: [u8; N]) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.to_vec()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const N: usize, const MAX: u32> TryFrom<StringM<MAX>> for [u8; N] {
type Error = StringM<MAX>;
fn try_from(v: StringM<MAX>) -> core::result::Result<Self, Self::Error> {
let s: [u8; N] = v.0.try_into().map_err(StringM::<MAX>)?;
Ok(s)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const N: usize, const MAX: u32> TryFrom<&[u8; N]> for StringM<MAX> {
type Error = Error;
fn try_from(v: &[u8; N]) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.to_vec()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(not(feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
impl<const N: usize, const MAX: u32> TryFrom<&'static [u8; N]> for StringM<MAX> {
type Error = Error;
fn try_from(v: &'static [u8; N]) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<&String> for StringM<MAX> {
type Error = Error;
fn try_from(v: &String) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.as_bytes().to_vec()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<String> for StringM<MAX> {
type Error = Error;
fn try_from(v: String) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.into()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<StringM<MAX>> for String {
type Error = Error;
fn try_from(v: StringM<MAX>) -> Result<Self, Error> {
Ok(String::from_utf8(v.0)?)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<&StringM<MAX>> for String {
type Error = Error;
fn try_from(v: &StringM<MAX>) -> Result<Self, Error> {
Ok(core::str::from_utf8(v.as_ref())?.to_owned())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<const MAX: u32> TryFrom<&str> for StringM<MAX> {
type Error = Error;
fn try_from(v: &str) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.into()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
#[cfg(not(feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
impl<const MAX: u32> TryFrom<&'static str> for StringM<MAX> {
type Error = Error;
fn try_from(v: &'static str) -> Result<Self, Error> {
let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
if len <= MAX {
Ok(StringM(v.as_bytes()))
} else {
Err(Error::LengthExceedsMax)
}
}
}
impl<'a, const MAX: u32> TryFrom<&'a StringM<MAX>> for &'a str {
type Error = Error;
fn try_from(v: &'a StringM<MAX>) -> Result<Self, Error> {
Ok(core::str::from_utf8(v.as_ref())?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::string::ToString;
#[cfg(feature = "alloc")]
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[test]
#[cfg(feature = "alloc")]
fn test_as_slice_const() {
const fn as_slice_in_const<const M: u32>(s: &StringM<M>) -> &[u8] {
s.as_slice()
}
let s: StringM<100> = "xdr".try_into().unwrap();
assert_eq!(as_slice_in_const(&s), b"xdr");
}
#[test]
#[cfg(not(feature = "alloc"))]
fn test_as_slice_const() {
const S: StringM<8> = StringM(b"xdr");
const SLICE: &[u8] = S.as_slice();
assert_eq!(SLICE, b"xdr");
}
#[test]
#[cfg(feature = "alloc")]
fn test_to_utf8_string_lossy_valid_utf8() {
let s: StringM<100> = "Hello, World!".try_into().unwrap();
assert_eq!(s.to_utf8_string_lossy(), "Hello, World!");
}
#[test]
#[cfg(feature = "alloc")]
fn test_to_utf8_string_lossy_invalid_utf8() {
let bytes = vec![0xFF, 0xFE, 0xFD];
let s: StringM<100> = bytes.try_into().unwrap();
let result = s.to_utf8_string_lossy();
assert_eq!(result, "\u{FFFD}\u{FFFD}\u{FFFD}");
}
#[test]
#[cfg(feature = "alloc")]
fn test_to_utf8_string_lossy_mixed() {
let mut bytes = Vec::new();
bytes.extend_from_slice(b"Hello");
bytes.push(0xFF); bytes.extend_from_slice(b"World");
let s: StringM<100> = bytes.try_into().unwrap();
let result = s.to_utf8_string_lossy();
assert_eq!(result, "Hello\u{FFFD}World");
}
#[test]
#[cfg(feature = "alloc")]
fn test_into_utf8_string_lossy_valid_utf8() {
let s: StringM<100> = "Test String".try_into().unwrap();
assert_eq!(s.into_utf8_string_lossy(), "Test String");
}
#[test]
#[cfg(feature = "alloc")]
fn test_into_utf8_string_lossy_invalid_utf8() {
let bytes = vec![0xC0, 0xC1, 0xF5];
let s: StringM<100> = bytes.try_into().unwrap();
let result = s.into_utf8_string_lossy();
assert!(result.contains('\u{FFFD}'));
}
#[test]
#[cfg(feature = "alloc")]
fn test_to_utf8_string_success() {
let s: StringM<100> = "Valid UTF-8".try_into().unwrap();
assert_eq!(s.to_utf8_string().unwrap(), "Valid UTF-8");
}
#[test]
#[cfg(feature = "alloc")]
fn test_to_utf8_string_failure() {
let bytes = vec![0xFF, 0xFE];
let s: StringM<100> = bytes.try_into().unwrap();
assert!(s.to_utf8_string().is_err());
}
#[test]
#[cfg(feature = "alloc")]
fn test_into_utf8_string_success() {
let s: StringM<100> = "Another test".try_into().unwrap();
assert_eq!(s.into_utf8_string().unwrap(), "Another test");
}
#[test]
#[cfg(feature = "alloc")]
fn test_into_utf8_string_failure() {
let bytes = vec![0x80, 0x81];
let s: StringM<100> = bytes.try_into().unwrap();
assert!(s.into_utf8_string().is_err());
}
#[test]
fn test_max_len_constraint() {
let short: Result<StringM<5>, _> = "hello".try_into();
assert!(short.is_ok());
let too_long: Result<StringM<5>, _> = "hello world".try_into();
assert!(too_long.is_err());
}
#[test]
#[cfg(feature = "alloc")]
fn test_empty_string() {
let s: StringM<100> = "".try_into().unwrap();
assert_eq!(s.to_utf8_string_lossy(), "");
assert_eq!(s.to_utf8_string().unwrap(), "");
}
#[test]
#[cfg(feature = "alloc")]
fn test_unicode_characters() {
let s: StringM<100> = "Hello 世界 🌍".try_into().unwrap();
assert_eq!(s.to_utf8_string_lossy(), "Hello 世界 🌍");
assert_eq!(s.to_utf8_string().unwrap(), "Hello 世界 🌍");
}
#[test]
#[cfg(feature = "alloc")]
fn test_fromstr_rejects_overlength() {
let result = "abcd".parse::<StringM<3>>();
assert!(matches!(result, Err(Error::LengthExceedsMax)));
}
#[test]
#[cfg(feature = "alloc")]
fn test_fromstr_accepts_at_max() {
let s = "abc".parse::<StringM<3>>().unwrap();
assert_eq!(s.as_ref() as &[u8], b"abc");
}
#[test]
#[cfg(feature = "alloc")]
fn test_fromstr_rejects_overlength_escaped() {
let result = "\\x61\\x62\\x63\\x64".parse::<StringM<3>>();
assert!(matches!(result, Err(Error::LengthExceedsMax)));
}
#[test]
#[cfg(feature = "alloc")]
fn test_fromstr_roundtrip() {
let original: StringM<10> = vec![0xFF, b'a', b'b'].try_into().unwrap();
let displayed = original.to_string();
let parsed: StringM<10> = displayed.parse().unwrap();
assert_eq!(original, parsed);
}
#[test]
#[cfg(all(feature = "serde", feature = "alloc"))]
fn test_stringm_serde_serialize_rejects_overlimit() {
let value = StringM::<1>(b"ab".to_vec());
let err = serde_json::to_value(&value).unwrap_err();
let msg = alloc::format!("{err}");
assert!(msg.contains("max length exceeded"), "{msg}");
}
#[test]
#[cfg(all(feature = "serde", feature = "alloc"))]
fn test_stringm_serde_serialize_accepts_at_max() {
let value = StringM::<2>(b"ab".to_vec());
let json = serde_json::to_value(&value).unwrap();
assert_eq!(json, serde_json::json!("ab"));
}
#[test]
#[cfg(all(feature = "serde", feature = "alloc"))]
fn test_stringm_serde_roundtrip() {
let value = StringM::<10>(vec![0xc3, 0xa9, b'a']);
let json = serde_json::to_value(&value).unwrap();
let back: StringM<10> = serde_json::from_value(json).unwrap();
assert_eq!(value, back);
}
#[test]
#[cfg(all(feature = "serde", feature = "alloc"))]
fn test_stringm_serde_deserialize_rejects_overlimit() {
let err = serde_json::from_value::<StringM<1>>(serde_json::json!("abc"));
assert!(err.is_err());
}
}