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)]
52pub struct BytesM<const MAX: u32 = { u32::MAX }>(pub Vec<u8>);
53
54#[cfg(all(feature = "serde", feature = "alloc"))]
55#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
56impl<'de, const MAX: u32> Deserialize<'de> for BytesM<MAX> {
57 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 let s = String::deserialize(deserializer)?;
62 s.parse().map_err(DeError::custom)
63 }
64}
65
66#[cfg(all(feature = "serde", feature = "alloc"))]
67#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
68impl<const MAX: u32> Serialize for BytesM<MAX> {
69 fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
70 where
71 S: Serializer,
72 {
73 if self.0.len() > Self::MAX_LEN {
74 return Err(<S::Error as SerError>::custom(Error::LengthExceedsMax));
75 }
76 ser.collect_str(self)
77 }
78}
79
80impl<const MAX: u32> core::fmt::Display for BytesM<MAX> {
81 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82 #[cfg(feature = "alloc")]
83 let v = &self.0;
84 #[cfg(not(feature = "alloc"))]
85 let v = self.0;
86 for b in v {
87 write!(f, "{b:02x}")?;
88 }
89 Ok(())
90 }
91}
92
93#[cfg(feature = "alloc")]
94#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
95impl<const MAX: u32> core::str::FromStr for BytesM<MAX> {
96 type Err = Error;
97 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
98 if !s.len().is_multiple_of(2) {
99 return Err(Error::InvalidHex);
100 }
101 if s.len() / 2 > MAX as usize {
102 return Err(Error::LengthExceedsMax);
103 }
104 let mut out = Vec::with_capacity(s.len() / 2);
105 let (pairs, rest) = s.as_bytes().as_chunks::<2>();
106 debug_assert!(rest.is_empty()); for pair in pairs {
108 out.push((hex_digit(pair[0])? << 4) | hex_digit(pair[1])?);
109 }
110 out.try_into()
111 }
112}
113
114#[cfg(feature = "alloc")]
116pub(crate) fn hex_digit(b: u8) -> core::result::Result<u8, Error> {
117 match b {
118 b'0'..=b'9' => Ok(b - b'0'),
119 b'a'..=b'f' => Ok(b - b'a' + 10),
120 b'A'..=b'F' => Ok(b - b'A' + 10),
121 _ => Err(Error::InvalidHex),
122 }
123}
124
125impl<const MAX: u32> core::fmt::Debug for BytesM<MAX> {
126 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
127 #[cfg(feature = "alloc")]
128 let v = &self.0;
129 #[cfg(not(feature = "alloc"))]
130 let v = self.0;
131 write!(f, "BytesM(")?;
132 for b in v {
133 write!(f, "{b:02x}")?;
134 }
135 write!(f, ")")?;
136 Ok(())
137 }
138}
139
140#[cfg(feature = "schemars")]
141#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
142impl<const MAX: u32> schemars::JsonSchema for BytesM<MAX> {
143 fn schema_name() -> alloc::borrow::Cow<'static, str> {
144 alloc::format!("BytesM<{}>", MAX).into()
145 }
146
147 fn schema_id() -> alloc::borrow::Cow<'static, str> {
148 alloc::format!("xdr_common::BytesM<{}>", MAX).into()
149 }
150
151 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
152 schemars::json_schema!({
153 "type": "string",
154 "maxLength": u64::from(MAX) * 2,
155 "contentEncoding": "hex",
156 "contentMediaType": "application/binary",
157 "pattern": "^([0-9a-fA-F]{2})*$",
158 })
159 }
160}
161
162impl<const MAX: u32> Deref for BytesM<MAX> {
163 type Target = Vec<u8>;
164
165 fn deref(&self) -> &Self::Target {
166 &self.0
167 }
168}
169
170impl<const MAX: u32> Default for BytesM<MAX> {
171 fn default() -> Self {
172 Self(Vec::default())
173 }
174}
175
176impl<const MAX: u32> BytesM<MAX> {
177 pub const MAX_LEN: usize = { MAX as usize };
179
180 #[must_use]
182 #[allow(clippy::unused_self)]
183 pub fn max_len(&self) -> usize {
184 Self::MAX_LEN
185 }
186
187 #[must_use]
189 pub fn as_vec(&self) -> &Vec<u8> {
190 self.as_ref()
191 }
192
193 #[must_use]
197 pub const fn as_slice(&self) -> &[u8] {
198 #[cfg(feature = "alloc")]
199 let s: &[u8] = self.0.as_slice();
200 #[cfg(not(feature = "alloc"))]
201 let s: &[u8] = self.0;
202 s
203 }
204}
205
206impl<const MAX: u32> BytesM<MAX> {
207 #[must_use]
211 #[cfg(feature = "alloc")]
212 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
213 pub fn to_vec(&self) -> Vec<u8> {
214 self.into()
215 }
216
217 #[must_use]
219 pub fn into_vec(self) -> Vec<u8> {
220 self.into()
221 }
222}
223
224impl<const MAX: u32> TryFrom<Vec<u8>> for BytesM<MAX> {
225 type Error = Error;
226
227 fn try_from(v: Vec<u8>) -> Result<Self, Error> {
228 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
229 if len <= MAX {
230 Ok(BytesM(v))
231 } else {
232 Err(Error::LengthExceedsMax)
233 }
234 }
235}
236
237impl<const MAX: u32> From<BytesM<MAX>> for Vec<u8> {
238 fn from(v: BytesM<MAX>) -> Self {
239 v.0
240 }
241}
242
243#[cfg(feature = "alloc")]
244#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
245impl<const MAX: u32> From<&BytesM<MAX>> for Vec<u8> {
246 fn from(v: &BytesM<MAX>) -> Self {
247 v.0.clone()
248 }
249}
250
251impl<const MAX: u32> AsRef<Vec<u8>> for BytesM<MAX> {
252 fn as_ref(&self) -> &Vec<u8> {
253 &self.0
254 }
255}
256
257#[cfg(feature = "alloc")]
258#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
259impl<const MAX: u32> TryFrom<&Vec<u8>> for BytesM<MAX> {
260 type Error = Error;
261
262 fn try_from(v: &Vec<u8>) -> Result<Self, Error> {
263 v.as_slice().try_into()
264 }
265}
266
267#[cfg(feature = "alloc")]
268#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
269impl<const MAX: u32> TryFrom<&[u8]> for BytesM<MAX> {
270 type Error = Error;
271
272 fn try_from(v: &[u8]) -> Result<Self, Error> {
273 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
274 if len <= MAX {
275 Ok(BytesM(v.to_vec()))
276 } else {
277 Err(Error::LengthExceedsMax)
278 }
279 }
280}
281
282impl<const MAX: u32> AsRef<[u8]> for BytesM<MAX> {
283 #[cfg(feature = "alloc")]
284 fn as_ref(&self) -> &[u8] {
285 self.0.as_ref()
286 }
287 #[cfg(not(feature = "alloc"))]
288 fn as_ref(&self) -> &[u8] {
289 self.0
290 }
291}
292
293#[cfg(feature = "alloc")]
294#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
295impl<const N: usize, const MAX: u32> TryFrom<[u8; N]> for BytesM<MAX> {
296 type Error = Error;
297
298 fn try_from(v: [u8; N]) -> Result<Self, Error> {
299 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
300 if len <= MAX {
301 Ok(BytesM(v.to_vec()))
302 } else {
303 Err(Error::LengthExceedsMax)
304 }
305 }
306}
307
308#[cfg(feature = "alloc")]
309#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
310impl<const N: usize, const MAX: u32> TryFrom<BytesM<MAX>> for [u8; N] {
311 type Error = BytesM<MAX>;
312
313 fn try_from(v: BytesM<MAX>) -> core::result::Result<Self, Self::Error> {
314 let s: [u8; N] = v.0.try_into().map_err(BytesM::<MAX>)?;
315 Ok(s)
316 }
317}
318
319#[cfg(feature = "alloc")]
320#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
321impl<const N: usize, const MAX: u32> TryFrom<&[u8; N]> for BytesM<MAX> {
322 type Error = Error;
323
324 fn try_from(v: &[u8; N]) -> Result<Self, Error> {
325 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
326 if len <= MAX {
327 Ok(BytesM(v.to_vec()))
328 } else {
329 Err(Error::LengthExceedsMax)
330 }
331 }
332}
333
334#[cfg(not(feature = "alloc"))]
335#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
336impl<const N: usize, const MAX: u32> TryFrom<&'static [u8; N]> for BytesM<MAX> {
337 type Error = Error;
338
339 fn try_from(v: &'static [u8; N]) -> Result<Self, Error> {
340 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
341 if len <= MAX {
342 Ok(BytesM(v))
343 } else {
344 Err(Error::LengthExceedsMax)
345 }
346 }
347}
348
349#[cfg(feature = "alloc")]
350#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
351impl<const MAX: u32> TryFrom<&String> for BytesM<MAX> {
352 type Error = Error;
353
354 fn try_from(v: &String) -> Result<Self, Error> {
355 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
356 if len <= MAX {
357 Ok(BytesM(v.as_bytes().to_vec()))
358 } else {
359 Err(Error::LengthExceedsMax)
360 }
361 }
362}
363
364#[cfg(feature = "alloc")]
365#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
366impl<const MAX: u32> TryFrom<String> for BytesM<MAX> {
367 type Error = Error;
368
369 fn try_from(v: String) -> Result<Self, Error> {
370 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
371 if len <= MAX {
372 Ok(BytesM(v.into()))
373 } else {
374 Err(Error::LengthExceedsMax)
375 }
376 }
377}
378
379#[cfg(feature = "alloc")]
380#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
381impl<const MAX: u32> TryFrom<&str> for BytesM<MAX> {
382 type Error = Error;
383
384 fn try_from(v: &str) -> Result<Self, Error> {
385 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
386 if len <= MAX {
387 Ok(BytesM(v.into()))
388 } else {
389 Err(Error::LengthExceedsMax)
390 }
391 }
392}
393
394#[cfg(not(feature = "alloc"))]
395#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
396impl<const MAX: u32> TryFrom<&'static str> for BytesM<MAX> {
397 type Error = Error;
398
399 fn try_from(v: &'static str) -> Result<Self, Error> {
400 let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
401 if len <= MAX {
402 Ok(BytesM(v.as_bytes()))
403 } else {
404 Err(Error::LengthExceedsMax)
405 }
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[cfg(feature = "alloc")]
414 extern crate alloc;
415 #[cfg(feature = "alloc")]
416 use alloc::{string::ToString, vec};
417
418 #[test]
419 #[cfg(feature = "alloc")]
420 fn test_as_slice_const() {
421 const fn as_slice_in_const<const M: u32>(b: &BytesM<M>) -> &[u8] {
425 b.as_slice()
426 }
427 let b: BytesM<3> = vec![0xde, 0xad].try_into().unwrap();
428 assert_eq!(as_slice_in_const(&b), &[0xde, 0xad]);
429 }
430
431 #[test]
432 #[cfg(not(feature = "alloc"))]
433 fn test_as_slice_const() {
434 const B: BytesM<3> = BytesM(&[0xde, 0xad]);
435 const SLICE: &[u8] = B.as_slice();
436 assert_eq!(SLICE, &[0xde, 0xad]);
437 }
438
439 #[test]
440 #[cfg(feature = "alloc")]
441 fn test_bytesm_alloc_basic() {
442 let b: BytesM<100> = vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap();
444 assert_eq!(b.len(), 4);
445 assert_eq!(b.max_len(), 100);
446 assert_eq!(b.to_string(), "deadbeef");
447
448 let too_long: Result<BytesM<3>, _> = vec![1, 2, 3, 4].try_into();
450 assert!(too_long.is_err());
451 }
452
453 #[test]
454 #[cfg(feature = "alloc")]
455 fn from_str_rejects_odd_length_hex() {
456 let result = "abc".parse::<BytesM<100>>();
457 assert_eq!(result, Err(Error::InvalidHex));
458 }
459
460 #[test]
461 #[cfg(feature = "alloc")]
462 fn from_str_rejects_overlength_before_decode() {
463 let input = "aabbcc";
466 let result = input.parse::<BytesM<2>>();
467 assert_eq!(result, Err(Error::LengthExceedsMax));
468 }
469
470 #[test]
471 #[cfg(feature = "alloc")]
472 fn from_str_accepts_valid_hex_within_bound() {
473 let result = "deadbeef".parse::<BytesM<4>>();
474 assert!(result.is_ok());
475 assert_eq!(
476 AsRef::<[u8]>::as_ref(&result.unwrap()),
477 &[0xde, 0xad, 0xbe, 0xef]
478 );
479 }
480
481 #[test]
482 #[cfg(feature = "alloc")]
483 fn from_str_accepts_exact_max_length() {
484 let result = "aabb".parse::<BytesM<2>>();
486 assert!(result.is_ok());
487 assert_eq!(result.unwrap().len(), 2);
488 }
489
490 #[test]
491 #[cfg(feature = "alloc")]
492 fn from_str_rejects_invalid_hex_chars() {
493 let result = "zzzz".parse::<BytesM<100>>();
494 assert_eq!(result, Err(Error::InvalidHex));
495 }
496
497 #[test]
498 #[cfg(feature = "alloc")]
499 fn from_str_overlength_rejects_before_validating_hex_content() {
500 let result = "aabbccZZ".parse::<BytesM<1>>();
504 assert_eq!(result, Err(Error::LengthExceedsMax));
505 }
506
507 #[test]
508 #[cfg(feature = "schemars")]
509 fn schema_pattern_rejects_odd_length_hex() {
510 let schema = schemars::schema_for!(BytesM<2>);
511 let obj = schema
512 .as_value()
513 .as_object()
514 .expect("schema should be a JSON object");
515 let pattern = obj
516 .get("pattern")
517 .expect("schema missing pattern key")
518 .as_str()
519 .expect("pattern should be a string");
520 let re = regex_lite::Regex::new(pattern).unwrap();
521 assert!(re.is_match("aabb"), "pattern should accept even-length hex");
522 assert!(re.is_match(""), "pattern should accept empty string");
523 assert!(!re.is_match("f"), "pattern must reject odd-length hex");
524 assert!(!re.is_match("abc"), "pattern must reject odd-length hex");
525 }
526
527 #[test]
528 #[cfg(all(feature = "serde", feature = "alloc"))]
529 fn test_bytesm_serde_serialize_rejects_overlimit() {
530 let value = BytesM::<1>(vec![0xaa, 0xbb]);
531 let err = serde_json::to_value(&value).unwrap_err();
532 let msg = alloc::format!("{err}");
533 assert!(msg.contains("max length exceeded"), "{msg}");
534 }
535
536 #[test]
537 #[cfg(all(feature = "serde", feature = "alloc"))]
538 fn test_bytesm_serde_serialize_accepts_at_max() {
539 let value = BytesM::<2>(vec![0xaa, 0xbb]);
540 let json = serde_json::to_value(&value).unwrap();
541 assert_eq!(json, serde_json::json!("aabb"));
542 }
543
544 #[test]
545 #[cfg(all(feature = "serde", feature = "alloc"))]
546 fn test_bytesm_serde_roundtrip() {
547 let value = BytesM::<100>(vec![0xde, 0xad, 0xbe, 0xef]);
548 let json = serde_json::to_value(&value).unwrap();
549 let back: BytesM<100> = serde_json::from_value(json).unwrap();
550 assert_eq!(value, back);
551 }
552
553 #[test]
554 #[cfg(all(feature = "serde", feature = "alloc"))]
555 fn test_bytesm_serde_deserialize_rejects_invalid_hex() {
556 let err = serde_json::from_value::<BytesM<100>>(serde_json::json!("zz"));
557 assert!(err.is_err());
558 }
559
560 #[test]
561 #[cfg(all(feature = "serde", feature = "alloc"))]
562 fn test_bytesm_serde_deserialize_rejects_overlimit() {
563 let err = serde_json::from_value::<BytesM<1>>(serde_json::json!("aabb"));
564 assert!(err.is_err());
565 }
566
567 #[test]
568 #[cfg(not(feature = "alloc"))]
569 fn test_bytesm_noalloc_basic() {
570 static DATA: [u8; 4] = [0xde, 0xad, 0xbe, 0xef];
572 let b: BytesM<100> = (&DATA).try_into().unwrap();
573 assert_eq!(b.len(), 4);
574 assert_eq!(b.max_len(), 100);
575 assert_eq!(AsRef::<[u8]>::as_ref(&b), &[0xde, 0xad, 0xbe, 0xef]);
576
577 static LONG_DATA: [u8; 4] = [1, 2, 3, 4];
579 let too_long: Result<BytesM<3>, _> = (&LONG_DATA).try_into();
580 assert!(too_long.is_err());
581 }
582}