1#![allow(unused_imports)]
2#![allow(dead_code)]
3
4use crate::cfg::*;
5pub use crate::error::*;
6
7use core::fmt;
8use core::marker::PhantomData;
9use core::ops::Deref;
10
11#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
72pub struct StringM<const MAX: u32 = { u32::MAX }>(pub Vec<u8>);
73
74#[cfg(all(feature = "serde", feature = "alloc"))]
75#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
76impl<'de, const MAX: u32> Deserialize<'de> for StringM<MAX> {
77 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 let s = String::deserialize(deserializer)?;
82 s.parse().map_err(DeError::custom)
83 }
84}
85
86#[cfg(all(feature = "serde", feature = "alloc"))]
87#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
88impl<const MAX: u32> Serialize for StringM<MAX> {
89 fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
90 where
91 S: Serializer,
92 {
93 if self.0.len() > Self::MAX_LEN {
94 return Err(<S::Error as SerError>::custom(Error::LengthExceedsMax));
95 }
96 ser.collect_str(self)
97 }
98}
99
100impl<const MAX: u32> core::fmt::Display for StringM<MAX> {
101 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102 #[cfg(feature = "alloc")]
103 let v = &self.0;
104 #[cfg(not(feature = "alloc"))]
105 let v = self.0;
106 for b in escape_bytes::Escape::new(v) {
107 write!(f, "{}", b as char)?;
108 }
109 Ok(())
110 }
111}
112
113impl<const MAX: u32> core::fmt::Debug for StringM<MAX> {
114 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115 #[cfg(feature = "alloc")]
116 let v = &self.0;
117 #[cfg(not(feature = "alloc"))]
118 let v = self.0;
119 write!(f, "StringM(")?;
120 for b in escape_bytes::Escape::new(v) {
121 write!(f, "{}", b as char)?;
122 }
123 write!(f, ")")?;
124 Ok(())
125 }
126}
127
128#[cfg(feature = "alloc")]
129#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
130impl<const MAX: u32> core::str::FromStr for StringM<MAX> {
131 type Err = Error;
132 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
133 if s.len() > Self::MAX_LEN.saturating_mul(4) {
136 return Err(Error::LengthExceedsMax);
137 }
138 escape_bytes::unescape(s.as_bytes())
139 .map_err(|_| Error::Invalid)?
140 .try_into()
141 }
142}
143
144#[cfg(feature = "schemars")]
145#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
146impl<const MAX: u32> schemars::JsonSchema for StringM<MAX> {
147 fn schema_name() -> alloc::borrow::Cow<'static, str> {
148 alloc::format!("StringM<{}>", MAX).into()
149 }
150
151 fn schema_id() -> alloc::borrow::Cow<'static, str> {
152 alloc::format!("xdr_common::StringM<{}>", MAX).into()
153 }
154
155 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
156 schemars::json_schema!({
157 "type": "string",
158 "x-max-unescaped-bytes": MAX,
159 "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.",
160 })
161 }
162}
163
164impl<const MAX: u32> Deref for StringM<MAX> {
165 type Target = Vec<u8>;
166
167 fn deref(&self) -> &Self::Target {
168 &self.0
169 }
170}
171
172impl<const MAX: u32> Default for StringM<MAX> {
173 fn default() -> Self {
174 Self(Vec::default())
175 }
176}
177
178impl<const MAX: u32> StringM<MAX> {
179 pub const MAX_LEN: usize = { MAX as usize };
181
182 #[must_use]
184 #[allow(clippy::unused_self)]
185 pub fn max_len(&self) -> usize {
186 Self::MAX_LEN
187 }
188
189 #[must_use]
191 pub fn as_vec(&self) -> &Vec<u8> {
192 self.as_ref()
193 }
194
195 #[must_use]
199 pub const fn as_slice(&self) -> &[u8] {
200 #[cfg(feature = "alloc")]
201 let s: &[u8] = self.0.as_slice();
202 #[cfg(not(feature = "alloc"))]
203 let s: &[u8] = self.0;
204 s
205 }
206}
207
208impl<const MAX: u32> StringM<MAX> {
209 #[must_use]
213 #[cfg(feature = "alloc")]
214 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
215 pub fn to_vec(&self) -> Vec<u8> {
216 self.into()
217 }
218
219 #[must_use]
221 pub fn into_vec(self) -> Vec<u8> {
222 self.into()
223 }
224}
225
226impl<const MAX: u32> StringM<MAX> {
227 #[cfg(feature = "alloc")]
233 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
234 pub fn to_utf8_string(&self) -> Result<String, Error> {
235 self.try_into()
236 }
237
238 #[cfg(feature = "alloc")]
244 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
245 pub fn into_utf8_string(self) -> Result<String, Error> {
246 self.try_into()
247 }
248
249 #[cfg(feature = "alloc")]
255 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
256 #[must_use]
257 pub fn to_utf8_string_lossy(&self) -> String {
258 String::from_utf8_lossy(&self.0).into_owned()
259 }
260
261 #[cfg(feature = "alloc")]
267 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
268 #[must_use]
269 pub fn into_utf8_string_lossy(self) -> String {
270 String::from_utf8_lossy(&self.0).into_owned()
271 }
272}
273
274impl<const MAX: u32> TryFrom<Vec<u8>> for StringM<MAX> {
275 type Error = Error;
276
277 fn try_from(v: Vec<u8>) -> Result<Self, Error> {
278 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
279 if len <= MAX {
280 Ok(StringM(v))
281 } else {
282 Err(Error::LengthExceedsMax)
283 }
284 }
285}
286
287impl<const MAX: u32> From<StringM<MAX>> for Vec<u8> {
288 fn from(v: StringM<MAX>) -> Self {
289 v.0
290 }
291}
292
293#[cfg(feature = "alloc")]
294#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
295impl<const MAX: u32> From<&StringM<MAX>> for Vec<u8> {
296 fn from(v: &StringM<MAX>) -> Self {
297 v.0.clone()
298 }
299}
300
301impl<const MAX: u32> AsRef<Vec<u8>> for StringM<MAX> {
302 fn as_ref(&self) -> &Vec<u8> {
303 &self.0
304 }
305}
306
307#[cfg(feature = "alloc")]
308#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
309impl<const MAX: u32> TryFrom<&Vec<u8>> for StringM<MAX> {
310 type Error = Error;
311
312 fn try_from(v: &Vec<u8>) -> Result<Self, Error> {
313 v.as_slice().try_into()
314 }
315}
316
317#[cfg(feature = "alloc")]
318#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
319impl<const MAX: u32> TryFrom<&[u8]> for StringM<MAX> {
320 type Error = Error;
321
322 fn try_from(v: &[u8]) -> Result<Self, Error> {
323 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
324 if len <= MAX {
325 Ok(StringM(v.to_vec()))
326 } else {
327 Err(Error::LengthExceedsMax)
328 }
329 }
330}
331
332impl<const MAX: u32> AsRef<[u8]> for StringM<MAX> {
333 #[cfg(feature = "alloc")]
334 fn as_ref(&self) -> &[u8] {
335 self.0.as_ref()
336 }
337 #[cfg(not(feature = "alloc"))]
338 fn as_ref(&self) -> &[u8] {
339 self.0
340 }
341}
342
343#[cfg(feature = "alloc")]
344#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
345impl<const N: usize, const MAX: u32> TryFrom<[u8; N]> for StringM<MAX> {
346 type Error = Error;
347
348 fn try_from(v: [u8; N]) -> Result<Self, Error> {
349 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
350 if len <= MAX {
351 Ok(StringM(v.to_vec()))
352 } else {
353 Err(Error::LengthExceedsMax)
354 }
355 }
356}
357
358#[cfg(feature = "alloc")]
359#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
360impl<const N: usize, const MAX: u32> TryFrom<StringM<MAX>> for [u8; N] {
361 type Error = StringM<MAX>;
362
363 fn try_from(v: StringM<MAX>) -> core::result::Result<Self, Self::Error> {
364 let s: [u8; N] = v.0.try_into().map_err(StringM::<MAX>)?;
365 Ok(s)
366 }
367}
368
369#[cfg(feature = "alloc")]
370#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
371impl<const N: usize, const MAX: u32> TryFrom<&[u8; N]> for StringM<MAX> {
372 type Error = Error;
373
374 fn try_from(v: &[u8; N]) -> Result<Self, Error> {
375 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
376 if len <= MAX {
377 Ok(StringM(v.to_vec()))
378 } else {
379 Err(Error::LengthExceedsMax)
380 }
381 }
382}
383
384#[cfg(not(feature = "alloc"))]
385#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
386impl<const N: usize, const MAX: u32> TryFrom<&'static [u8; N]> for StringM<MAX> {
387 type Error = Error;
388
389 fn try_from(v: &'static [u8; N]) -> Result<Self, Error> {
390 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
391 if len <= MAX {
392 Ok(StringM(v))
393 } else {
394 Err(Error::LengthExceedsMax)
395 }
396 }
397}
398
399#[cfg(feature = "alloc")]
400#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
401impl<const MAX: u32> TryFrom<&String> for StringM<MAX> {
402 type Error = Error;
403
404 fn try_from(v: &String) -> Result<Self, Error> {
405 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
406 if len <= MAX {
407 Ok(StringM(v.as_bytes().to_vec()))
408 } else {
409 Err(Error::LengthExceedsMax)
410 }
411 }
412}
413
414#[cfg(feature = "alloc")]
415#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
416impl<const MAX: u32> TryFrom<String> for StringM<MAX> {
417 type Error = Error;
418
419 fn try_from(v: String) -> Result<Self, Error> {
420 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
421 if len <= MAX {
422 Ok(StringM(v.into()))
423 } else {
424 Err(Error::LengthExceedsMax)
425 }
426 }
427}
428
429#[cfg(feature = "alloc")]
430#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
431impl<const MAX: u32> TryFrom<StringM<MAX>> for String {
432 type Error = Error;
433
434 fn try_from(v: StringM<MAX>) -> Result<Self, Error> {
435 Ok(String::from_utf8(v.0)?)
436 }
437}
438
439#[cfg(feature = "alloc")]
440#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
441impl<const MAX: u32> TryFrom<&StringM<MAX>> for String {
442 type Error = Error;
443
444 fn try_from(v: &StringM<MAX>) -> Result<Self, Error> {
445 Ok(core::str::from_utf8(v.as_ref())?.to_owned())
446 }
447}
448
449#[cfg(feature = "alloc")]
450#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
451impl<const MAX: u32> TryFrom<&str> for StringM<MAX> {
452 type Error = Error;
453
454 fn try_from(v: &str) -> Result<Self, Error> {
455 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
456 if len <= MAX {
457 Ok(StringM(v.into()))
458 } else {
459 Err(Error::LengthExceedsMax)
460 }
461 }
462}
463
464#[cfg(not(feature = "alloc"))]
465#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
466impl<const MAX: u32> TryFrom<&'static str> for StringM<MAX> {
467 type Error = Error;
468
469 fn try_from(v: &'static str) -> Result<Self, Error> {
470 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
471 if len <= MAX {
472 Ok(StringM(v.as_bytes()))
473 } else {
474 Err(Error::LengthExceedsMax)
475 }
476 }
477}
478
479impl<'a, const MAX: u32> TryFrom<&'a StringM<MAX>> for &'a str {
480 type Error = Error;
481
482 fn try_from(v: &'a StringM<MAX>) -> Result<Self, Error> {
483 Ok(core::str::from_utf8(v.as_ref())?)
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[cfg(feature = "alloc")]
492 extern crate alloc;
493 #[cfg(feature = "alloc")]
494 use alloc::string::ToString;
495 #[cfg(feature = "alloc")]
496 use alloc::vec;
497 #[cfg(feature = "alloc")]
498 use alloc::vec::Vec;
499
500 #[test]
501 #[cfg(feature = "alloc")]
502 fn test_as_slice_const() {
503 const fn as_slice_in_const<const M: u32>(s: &StringM<M>) -> &[u8] {
507 s.as_slice()
508 }
509 let s: StringM<100> = "xdr".try_into().unwrap();
510 assert_eq!(as_slice_in_const(&s), b"xdr");
511 }
512
513 #[test]
514 #[cfg(not(feature = "alloc"))]
515 fn test_as_slice_const() {
516 const S: StringM<8> = StringM(b"xdr");
517 const SLICE: &[u8] = S.as_slice();
518 assert_eq!(SLICE, b"xdr");
519 }
520
521 #[test]
522 #[cfg(feature = "alloc")]
523 fn test_to_utf8_string_lossy_valid_utf8() {
524 let s: StringM<100> = "Hello, World!".try_into().unwrap();
526 assert_eq!(s.to_utf8_string_lossy(), "Hello, World!");
527 }
528
529 #[test]
530 #[cfg(feature = "alloc")]
531 fn test_to_utf8_string_lossy_invalid_utf8() {
532 let bytes = vec![0xFF, 0xFE, 0xFD];
534 let s: StringM<100> = bytes.try_into().unwrap();
535 let result = s.to_utf8_string_lossy();
536
537 assert_eq!(result, "\u{FFFD}\u{FFFD}\u{FFFD}");
539 }
540
541 #[test]
542 #[cfg(feature = "alloc")]
543 fn test_to_utf8_string_lossy_mixed() {
544 let mut bytes = Vec::new();
546 bytes.extend_from_slice(b"Hello");
547 bytes.push(0xFF); bytes.extend_from_slice(b"World");
549
550 let s: StringM<100> = bytes.try_into().unwrap();
551 let result = s.to_utf8_string_lossy();
552
553 assert_eq!(result, "Hello\u{FFFD}World");
554 }
555
556 #[test]
557 #[cfg(feature = "alloc")]
558 fn test_into_utf8_string_lossy_valid_utf8() {
559 let s: StringM<100> = "Test String".try_into().unwrap();
561 assert_eq!(s.into_utf8_string_lossy(), "Test String");
562 }
563
564 #[test]
565 #[cfg(feature = "alloc")]
566 fn test_into_utf8_string_lossy_invalid_utf8() {
567 let bytes = vec![0xC0, 0xC1, 0xF5];
569 let s: StringM<100> = bytes.try_into().unwrap();
570 let result = s.into_utf8_string_lossy();
571
572 assert!(result.contains('\u{FFFD}'));
574 }
575
576 #[test]
577 #[cfg(feature = "alloc")]
578 fn test_to_utf8_string_success() {
579 let s: StringM<100> = "Valid UTF-8".try_into().unwrap();
581 assert_eq!(s.to_utf8_string().unwrap(), "Valid UTF-8");
582 }
583
584 #[test]
585 #[cfg(feature = "alloc")]
586 fn test_to_utf8_string_failure() {
587 let bytes = vec![0xFF, 0xFE];
589 let s: StringM<100> = bytes.try_into().unwrap();
590 assert!(s.to_utf8_string().is_err());
591 }
592
593 #[test]
594 #[cfg(feature = "alloc")]
595 fn test_into_utf8_string_success() {
596 let s: StringM<100> = "Another test".try_into().unwrap();
598 assert_eq!(s.into_utf8_string().unwrap(), "Another test");
599 }
600
601 #[test]
602 #[cfg(feature = "alloc")]
603 fn test_into_utf8_string_failure() {
604 let bytes = vec![0x80, 0x81];
606 let s: StringM<100> = bytes.try_into().unwrap();
607 assert!(s.into_utf8_string().is_err());
608 }
609
610 #[test]
611 fn test_max_len_constraint() {
612 let short: Result<StringM<5>, _> = "hello".try_into();
614 assert!(short.is_ok());
615
616 let too_long: Result<StringM<5>, _> = "hello world".try_into();
617 assert!(too_long.is_err());
618 }
619
620 #[test]
621 #[cfg(feature = "alloc")]
622 fn test_empty_string() {
623 let s: StringM<100> = "".try_into().unwrap();
624 assert_eq!(s.to_utf8_string_lossy(), "");
625 assert_eq!(s.to_utf8_string().unwrap(), "");
626 }
627
628 #[test]
629 #[cfg(feature = "alloc")]
630 fn test_unicode_characters() {
631 let s: StringM<100> = "Hello δΈη π".try_into().unwrap();
633 assert_eq!(s.to_utf8_string_lossy(), "Hello δΈη π");
634 assert_eq!(s.to_utf8_string().unwrap(), "Hello δΈη π");
635 }
636
637 #[test]
638 #[cfg(feature = "alloc")]
639 fn test_fromstr_rejects_overlength() {
640 let result = "abcd".parse::<StringM<3>>();
641 assert!(matches!(result, Err(Error::LengthExceedsMax)));
642 }
643
644 #[test]
645 #[cfg(feature = "alloc")]
646 fn test_fromstr_accepts_at_max() {
647 let s = "abc".parse::<StringM<3>>().unwrap();
648 assert_eq!(s.as_ref() as &[u8], b"abc");
649 }
650
651 #[test]
652 #[cfg(feature = "alloc")]
653 fn test_fromstr_rejects_overlength_escaped() {
654 let result = "\\x61\\x62\\x63\\x64".parse::<StringM<3>>();
655 assert!(matches!(result, Err(Error::LengthExceedsMax)));
656 }
657
658 #[test]
659 #[cfg(feature = "alloc")]
660 fn test_fromstr_roundtrip() {
661 let original: StringM<10> = vec![0xFF, b'a', b'b'].try_into().unwrap();
662 let displayed = original.to_string();
663 let parsed: StringM<10> = displayed.parse().unwrap();
664 assert_eq!(original, parsed);
665 }
666
667 #[test]
668 #[cfg(all(feature = "serde", feature = "alloc"))]
669 fn test_stringm_serde_serialize_rejects_overlimit() {
670 let value = StringM::<1>(b"ab".to_vec());
671 let err = serde_json::to_value(&value).unwrap_err();
672 let msg = alloc::format!("{err}");
673 assert!(msg.contains("max length exceeded"), "{msg}");
674 }
675
676 #[test]
677 #[cfg(all(feature = "serde", feature = "alloc"))]
678 fn test_stringm_serde_serialize_accepts_at_max() {
679 let value = StringM::<2>(b"ab".to_vec());
680 let json = serde_json::to_value(&value).unwrap();
681 assert_eq!(json, serde_json::json!("ab"));
682 }
683
684 #[test]
685 #[cfg(all(feature = "serde", feature = "alloc"))]
686 fn test_stringm_serde_roundtrip() {
687 let value = StringM::<10>(vec![0xc3, 0xa9, b'a']);
689 let json = serde_json::to_value(&value).unwrap();
690 let back: StringM<10> = serde_json::from_value(json).unwrap();
691 assert_eq!(value, back);
692 }
693
694 #[test]
695 #[cfg(all(feature = "serde", feature = "alloc"))]
696 fn test_stringm_serde_deserialize_rejects_overlimit() {
697 let err = serde_json::from_value::<StringM<1>>(serde_json::json!("abc"));
698 assert!(err.is_err());
699 }
700}