1use aws_smithy_types::date_time::Format;
25use aws_smithy_types::primitive::Parse;
26use aws_smithy_types::DateTime;
27use std::borrow::Cow;
28use std::error::Error;
29use std::fmt;
30use std::str::FromStr;
31
32#[derive(Debug)]
34pub struct ParseError {
35 message: Cow<'static, str>,
36 source: Option<Box<dyn Error + Send + Sync + 'static>>,
37}
38
39impl ParseError {
40 pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
42 Self {
43 message: message.into(),
44 source: None,
45 }
46 }
47
48 pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
50 Self {
51 source: Some(source.into()),
52 ..self
53 }
54 }
55}
56
57impl fmt::Display for ParseError {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(f, "output failed to parse in headers: {}", self.message)
60 }
61}
62
63impl Error for ParseError {
64 fn source(&self) -> Option<&(dyn Error + 'static)> {
65 self.source.as_ref().map(|err| err.as_ref() as _)
66 }
67}
68
69pub(crate) const NON_UTF8_HEADER: &str = "header was not valid utf-8";
70
71fn str_from_utf8(bytes: &[u8]) -> Result<&str, ParseError> {
73 std::str::from_utf8(bytes).map_err(|_| ParseError::new(NON_UTF8_HEADER))
74}
75
76pub fn many_dates<'a>(
81 values: impl Iterator<Item = &'a str>,
82 format: Format,
83) -> Result<Vec<DateTime>, ParseError> {
84 many_dates_bytes(values.map(str::as_bytes), format)
85}
86
87pub fn many_dates_bytes<'a>(
93 values: impl Iterator<Item = &'a [u8]>,
94 format: Format,
95) -> Result<Vec<DateTime>, ParseError> {
96 let mut out = vec![];
97 for header in values {
98 let mut header = str_from_utf8(header)?;
99 while !header.is_empty() {
100 let (v, next) = DateTime::read(header, format, ',').map_err(|err| {
101 ParseError::new(format!("header could not be parsed as date: {err}"))
102 })?;
103 out.push(v);
104 header = next;
105 }
106 }
107 Ok(out)
108}
109
110pub fn headers_for_prefix<'a>(
122 header_names: impl Iterator<Item = &'a str>,
123 key: &'a str,
124) -> impl Iterator<Item = (&'a str, &'a str)> {
125 let prefix = key.as_bytes();
126 let has_uppercase = prefix.iter().any(u8::is_ascii_uppercase);
127 header_names
128 .filter(move |k| {
129 let Some(head) = k.as_bytes().get(..prefix.len()) else {
130 return false;
131 };
132 if has_uppercase {
133 head.iter()
134 .zip(prefix)
135 .all(|(h, p)| *h == p.to_ascii_lowercase())
136 } else {
137 head == prefix
138 }
139 })
140 .map(move |k| (&k[key.len()..], k))
143}
144
145pub fn read_many_from_str<'a, T: FromStr>(
147 values: impl Iterator<Item = &'a str>,
148) -> Result<Vec<T>, ParseError>
149where
150 T::Err: Error + Send + Sync + 'static,
151{
152 read_many_from_str_bytes(values.map(str::as_bytes))
153}
154
155pub fn read_many_from_str_bytes<'a, T: FromStr>(
160 values: impl Iterator<Item = &'a [u8]>,
161) -> Result<Vec<T>, ParseError>
162where
163 T::Err: Error + Send + Sync + 'static,
164{
165 read_many_bytes(values, |v: &str| {
166 v.parse().map_err(|err| {
167 ParseError::new("failed during `FromString` conversion").with_source(err)
168 })
169 })
170}
171
172pub fn read_many_primitive<'a, T: Parse>(
174 values: impl Iterator<Item = &'a str>,
175) -> Result<Vec<T>, ParseError> {
176 read_many_primitive_bytes(values.map(str::as_bytes))
177}
178
179pub fn read_many_primitive_bytes<'a, T: Parse>(
185 values: impl Iterator<Item = &'a [u8]>,
186) -> Result<Vec<T>, ParseError> {
187 read_many_bytes(values, |v: &str| {
188 T::parse_smithy_primitive(v)
189 .map_err(|err| ParseError::new("failed reading a list of primitives").with_source(err))
190 })
191}
192
193fn read_many_bytes<'a, T>(
195 values: impl Iterator<Item = &'a [u8]>,
196 f: impl Fn(&str) -> Result<T, ParseError>,
197) -> Result<Vec<T>, ParseError> {
198 let mut out = vec![];
199 for header in values {
200 let mut header = header;
201 while !header.is_empty() {
202 let (v, next) = read_one(header, &f)?;
203 out.push(v);
204 header = next;
205 }
206 }
207 Ok(out)
208}
209
210fn one_of_many_bytes<'a, T>(
221 values: impl Iterator<Item = &'a [u8]>,
222 f: impl Fn(&str) -> Result<T, ParseError>,
223) -> Result<Option<T>, ParseError> {
224 let mut first = None;
225 let mut count = 0usize;
226 for header in values {
227 let mut header = header;
228 while !header.is_empty() {
229 let (value, next) = read_one(header, &f)?;
230 count += 1;
231 if first.is_none() {
232 first = Some(value);
233 }
234 header = next;
235 }
236 }
237 if count > 1 {
238 return Err(ParseError::new(format!(
239 "expected one item but found {count}"
240 )));
241 }
242 Ok(first)
243}
244
245pub fn one_primitive_or_none_bytes<'a, T: Parse>(
252 values: impl Iterator<Item = &'a [u8]>,
253) -> Result<Option<T>, ParseError> {
254 one_of_many_bytes(values, |v: &str| {
255 T::parse_smithy_primitive(v)
256 .map_err(|err| ParseError::new("failed reading a list of primitives").with_source(err))
257 })
258}
259
260pub fn one_from_str_or_none_bytes<'a, T: FromStr>(
265 values: impl Iterator<Item = &'a [u8]>,
266) -> Result<Option<T>, ParseError>
267where
268 T::Err: Error + Send + Sync + 'static,
269{
270 one_of_many_bytes(values, |v: &str| {
271 v.parse().map_err(|err| {
272 ParseError::new("failed during `FromString` conversion").with_source(err)
273 })
274 })
275}
276
277pub fn one_date_or_none_bytes<'a>(
283 values: impl Iterator<Item = &'a [u8]>,
284 format: Format,
285) -> Result<Option<DateTime>, ParseError> {
286 let mut first = None;
287 let mut count = 0usize;
288 for header in values {
289 let mut header = str_from_utf8(header)?;
290 while !header.is_empty() {
291 let (value, next) = DateTime::read(header, format, ',').map_err(|err| {
292 ParseError::new(format!("header could not be parsed as date: {err}"))
293 })?;
294 count += 1;
295 if first.is_none() {
296 first = Some(value);
297 }
298 header = next;
299 }
300 }
301 if count > 1 {
302 return Err(ParseError::new(format!(
303 "expected one item but found {count}"
304 )));
305 }
306 Ok(first)
307}
308
309pub fn quote_header_value<'a>(value: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
311 let value = value.into();
312 if value.trim().len() != value.len()
313 || value.contains('"')
314 || value.contains(',')
315 || value.contains('(')
316 || value.contains(')')
317 {
318 Cow::Owned(format!(
319 "\"{}\"",
320 value.replace('\\', "\\\\").replace('"', "\\\"")
321 ))
322 } else {
323 value
324 }
325}
326
327pub fn one_or_none<'a, T: FromStr>(
331 values: impl Iterator<Item = &'a str>,
332) -> Result<Option<T>, ParseError>
333where
334 T::Err: Error + Send + Sync + 'static,
335{
336 one_or_none_bytes(values.map(str::as_bytes))
337}
338
339pub fn one_or_none_bytes<'a, T: FromStr>(
346 mut values: impl Iterator<Item = &'a [u8]>,
347) -> Result<Option<T>, ParseError>
348where
349 T::Err: Error + Send + Sync + 'static,
350{
351 let first = match values.next() {
352 Some(v) => v,
353 None => return Ok(None),
354 };
355 match values.next() {
356 None => T::from_str(str_from_utf8(first)?.trim())
359 .map_err(|err| ParseError::new("failed to parse string").with_source(err))
360 .map(Some),
361 Some(_) => Err(ParseError::new(
362 "expected a single value but found multiple",
363 )),
364 }
365}
366
367mod parse_multi_header {
371 use super::ParseError;
372 use std::borrow::Cow;
373
374 fn trim(s: Cow<'_, str>) -> Cow<'_, str> {
375 match s {
376 Cow::Owned(s) => Cow::Owned(s.trim().into()),
377 Cow::Borrowed(s) => Cow::Borrowed(s.trim()),
378 }
379 }
380
381 fn replace<'a>(value: Cow<'a, str>, pattern: &str, replacement: &str) -> Cow<'a, str> {
382 if value.contains(pattern) {
383 Cow::Owned(value.replace(pattern, replacement))
384 } else {
385 value
386 }
387 }
388
389 pub(crate) fn read_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
393 for (index, &byte) in input.iter().enumerate() {
394 let current_slice = &input[index..];
395 match byte {
396 b' ' | b'\t' => { }
397 b'"' => return read_quoted_value(¤t_slice[1..]),
398 _ => {
399 let (value, rest) = read_unquoted_value(current_slice)?;
400 return Ok((trim(value), rest));
401 }
402 }
403 }
404
405 Ok((Cow::Borrowed(""), &[]))
407 }
408
409 fn read_unquoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
410 let next_delim = input.iter().position(|&b| b == b',').unwrap_or(input.len());
411 let (first, next) = input.split_at(next_delim);
412 let first =
413 std::str::from_utf8(first).map_err(|_| ParseError::new(super::NON_UTF8_HEADER))?;
414 Ok((Cow::Borrowed(first), then_comma(next).unwrap()))
415 }
416
417 fn read_quoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
420 for index in 0..input.len() {
421 match input[index] {
422 b'"' if index == 0 || input[index - 1] != b'\\' => {
423 let mut inner = Cow::Borrowed(
424 std::str::from_utf8(&input[0..index])
425 .map_err(|_| ParseError::new(super::NON_UTF8_HEADER))?,
426 );
427 inner = replace(inner, "\\\"", "\"");
428 inner = replace(inner, "\\\\", "\\");
429 let rest = then_comma(&input[(index + 1)..])?;
430 return Ok((inner, rest));
431 }
432 _ => {}
433 }
434 }
435 Err(ParseError::new(
436 "header value had quoted value without end quote",
437 ))
438 }
439
440 fn then_comma(s: &[u8]) -> Result<&[u8], ParseError> {
441 if s.is_empty() {
442 Ok(s)
443 } else if s.starts_with(b",") {
444 Ok(&s[1..])
445 } else {
446 Err(ParseError::new("expected delimiter `,`"))
447 }
448 }
449}
450
451fn read_one<'a, T>(
453 s: &'a [u8],
454 f: &impl Fn(&str) -> Result<T, ParseError>,
455) -> Result<(T, &'a [u8]), ParseError> {
456 let (value, rest) = parse_multi_header::read_value(s)?;
457 Ok((f(&value)?, rest))
458}
459
460#[cfg(test)]
461mod test {
462 use super::{
467 headers_for_prefix, many_dates, many_dates_bytes, one_date_or_none_bytes,
468 one_from_str_or_none_bytes, one_or_none, one_or_none_bytes, one_primitive_or_none_bytes,
469 read_many_from_str, read_many_from_str_bytes, read_many_primitive,
470 read_many_primitive_bytes, ParseError,
471 };
472 use aws_smithy_types::error::display::DisplayErrorContext;
473 use aws_smithy_types::{date_time::Format, DateTime};
474 use std::collections::HashMap;
475
476 #[test]
477 fn parse_floats() {
478 assert_eq!(
479 read_many_primitive::<f32>(["0.0,Infinity,-Infinity,5555.5"].into_iter())
480 .expect("valid"),
481 vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
482 );
483 let message = format!(
484 "{}",
485 DisplayErrorContext(
486 read_many_primitive::<f32>(["notafloat"].into_iter()).expect_err("invalid")
487 )
488 );
489 let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
490 assert!(
491 message.starts_with(expected),
492 "expected '{message}' to start with '{expected}'"
493 );
494 }
495
496 #[test]
497 fn test_many_dates() {
498 assert_eq!(
499 many_dates([""].into_iter(), Format::DateTime).expect("valid"),
500 Vec::<DateTime>::new()
501 );
502 assert_eq!(
503 many_dates(
504 ["Wed, 21 Oct 2015 07:28:00 GMT"].into_iter(),
505 Format::HttpDate
506 )
507 .expect("valid"),
508 vec![DateTime::from_secs_and_nanos(1445412480, 0)]
509 );
510 assert_eq!(
511 many_dates(
512 ["Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT"].into_iter(),
513 Format::HttpDate
514 )
515 .expect("valid"),
516 vec![
517 DateTime::from_secs_and_nanos(1445412480, 0),
518 DateTime::from_secs_and_nanos(1445498880, 0)
519 ]
520 );
521 assert_eq!(
522 many_dates(["1234.5678"].into_iter(), Format::EpochSeconds).expect("valid"),
523 vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
524 );
525 assert_eq!(
526 many_dates(["1234.5678,9012.3456"].into_iter(), Format::EpochSeconds).expect("valid"),
527 vec![
528 DateTime::from_secs_and_nanos(1234, 567_800_000),
529 DateTime::from_secs_and_nanos(9012, 345_600_000)
530 ]
531 );
532 }
533
534 const NON_UTF8_VALUE: &[u8] = b"value-\xe9";
536
537 #[test]
538 fn bytes_helpers_agree_with_str_helpers_on_valid_utf8() {
539 assert_eq!(
540 one_or_none::<String>([" foo "].into_iter()).unwrap(),
541 one_or_none_bytes::<String>([b" foo ".as_slice()].into_iter()).unwrap(),
542 );
543 assert_eq!(
544 read_many_from_str::<String>(["\"foo,bar\",baz"].into_iter()).unwrap(),
545 read_many_from_str_bytes::<String>([b"\"foo,bar\",baz".as_slice()].into_iter())
546 .unwrap(),
547 );
548 assert_eq!(
549 read_many_primitive::<i16>(["1,2", "3"].into_iter()).unwrap(),
550 read_many_primitive_bytes::<i16>([b"1,2".as_slice(), b"3".as_slice()].into_iter())
551 .unwrap(),
552 );
553 assert_eq!(
554 many_dates(
555 ["Mon, 16 Dec 2019 23:48:18 GMT"].into_iter(),
556 Format::HttpDate
557 )
558 .unwrap(),
559 many_dates_bytes(
560 [b"Mon, 16 Dec 2019 23:48:18 GMT".as_slice()].into_iter(),
561 Format::HttpDate
562 )
563 .unwrap(),
564 );
565 }
566
567 #[test]
568 fn bytes_helpers_reject_non_utf8() {
569 let expected = "header was not valid utf-8";
570
571 let err = one_or_none_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
572 assert!(err.to_string().contains(expected), "{err}");
573
574 let err =
575 read_many_from_str_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
576 assert!(err.to_string().contains(expected), "{err}");
577
578 let err =
579 read_many_primitive_bytes::<i16>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
580 assert!(err.to_string().contains(expected), "{err}");
581
582 let err =
583 many_dates_bytes([NON_UTF8_VALUE].into_iter(), Format::HttpDate).expect_err("non-utf8");
584 assert!(err.to_string().contains(expected), "{err}");
585 }
586
587 #[test]
588 fn one_or_none_bytes_reports_multiple_before_non_utf8() {
589 let err = one_or_none_bytes::<String>([NON_UTF8_VALUE, b"second".as_slice()].into_iter())
592 .expect_err("multiple values");
593 assert!(
594 err.to_string()
595 .contains("expected a single value but found multiple"),
596 "{err}"
597 );
598 }
599
600 #[test]
601 fn read_many_strings() {
602 let read = |value: &str| read_many_from_str::<String>([value].into_iter());
603 let read_valid = |value: &str| read(value).expect("valid");
604 assert_eq!(read_valid(""), Vec::<String>::new());
605 assert_eq!(read_valid(" foo"), vec!["foo"]);
606 assert_eq!(read_valid("foo "), vec!["foo"]);
607 assert_eq!(read_valid("\" foo \""), vec![" foo "]);
608 assert_eq!(read_valid("\"foo,bar\",baz"), vec!["foo,bar", "baz"]);
609 assert_eq!(read_valid("\"foo,bar\",baz "), vec!["foo,bar", "baz"]);
610 assert_eq!(
611 read_valid("\"foo\\\",bar\",\"\\\"asdf\\\"\",baz"),
612 vec!["foo\",bar", "\"asdf\"", "baz"]
613 );
614 assert_eq!(
615 read_valid("\"foo\\\",bar\", \"\\\"asdf\\\"\", baz"),
616 vec!["foo\",bar", "\"asdf\"", "baz"]
617 );
618 assert!(read("\"\\\"asdf\\\"\"baz").is_err());
619 assert_eq!(read_valid("\"\",baz"), vec!["", "baz"]);
620 assert_eq!(
621 read_valid("foo, \"(foo\\\\bar)\""),
622 vec!["foo", "(foo\\bar)"]
623 );
624 }
625
626 #[test]
627 fn read_many_bools() {
628 assert_eq!(
629 read_many_primitive::<bool>(["true,false", "true"].into_iter()).expect("valid"),
630 vec![true, false, true]
631 );
632 assert_eq!(
633 read_many_primitive::<bool>(["true"].into_iter()).unwrap(),
634 vec![true]
635 );
636 assert_eq!(
637 read_many_primitive::<bool>(["true,false,true,true"].into_iter()).unwrap(),
638 vec![true, false, true, true]
639 );
640 assert_eq!(
641 read_many_primitive::<bool>(["true,\"false\",true,true"].into_iter()).unwrap(),
642 vec![true, false, true, true]
643 );
644 read_many_primitive::<bool>(["truth,falsy"].into_iter()).expect_err("invalid");
645 }
646
647 #[test]
648 fn check_read_many_i16() {
649 assert_eq!(
650 read_many_primitive::<i16>(["123,456", "789"].into_iter()).expect("valid"),
651 vec![123, 456, 789]
652 );
653 assert_eq!(
654 read_many_primitive::<i16>(["777"].into_iter()).unwrap(),
655 vec![777]
656 );
657 assert_eq!(
658 read_many_primitive::<i16>(["1,2,3,-4,5"].into_iter()).unwrap(),
659 vec![1, 2, 3, -4, 5]
660 );
661 assert_eq!(
662 read_many_primitive::<i16>(["1, \"2\",3,\"-4\",5"].into_iter()).unwrap(),
663 vec![1, 2, 3, -4, 5]
664 );
665 read_many_primitive::<i16>(["12ef3"].into_iter()).expect_err("invalid");
666 }
667
668 #[test]
669 fn test_prefix_headers() {
670 let names = ["x-prefix-a", "x-prefix-b", "x-prefix-c", "other"];
673 let values: HashMap<&str, Vec<&str>> = [
674 ("x-prefix-a", vec!["123,456"]),
675 ("x-prefix-b", vec!["789"]),
676 ("x-prefix-c", vec!["777", "777"]),
677 ("other", vec!["1"]),
678 ]
679 .into_iter()
680 .collect();
681 let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
682 headers_for_prefix(names.into_iter(), "x-prefix-")
683 .map(|(key, header_name)| {
684 read_many_primitive(values[header_name].iter().copied())
685 .map(|v| (key.to_string(), v))
686 })
687 .collect();
688 let resp = resp.expect("valid");
689 assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
690 assert_eq!(resp.get("b"), Some(&vec![789_i16]));
691 assert_eq!(resp.get("c"), Some(&vec![777_i16, 777_i16]));
692 assert_eq!(resp.get("other"), None);
693 }
694
695 #[test]
696 fn prefix_matching_lowercases_the_modeled_prefix() {
697 let names = ["x-meta-foo", "x-meta-bar"];
698 let matched: Vec<_> = headers_for_prefix(names.into_iter(), "X-Meta-").collect();
699 assert_eq!(
700 matched,
701 vec![("foo", "x-meta-foo"), ("bar", "x-meta-bar")],
702 "a mixed-case modeled prefix must match normalized wire names"
703 );
704 }
705
706 #[test]
707 fn every_spelling_of_a_prefix_matches_the_same_normalized_names() {
708 for prefix in ["X-Meta-", "x-meta-", "X-META-", "x-Meta-"] {
712 let names = ["x-meta-foo", "x-meta-bar", "x-other", "x-me", "x-metadata"];
713 let matched: Vec<_> = headers_for_prefix(names.into_iter(), prefix).collect();
714 assert_eq!(
715 matched,
716 vec![("foo", "x-meta-foo"), ("bar", "x-meta-bar")],
717 "prefix {prefix:?} must match the same names"
718 );
719 }
720 }
721
722 #[test]
723 fn a_prefix_with_no_letters_matches_exactly() {
724 let names = ["1-2-a", "1-2-", "1-3-a"];
725 let matched: Vec<_> = headers_for_prefix(names.into_iter(), "1-2-").collect();
726 assert_eq!(matched, vec![("a", "1-2-a"), ("", "1-2-")]);
727 }
728
729 #[test]
730 fn a_prefix_matches_the_whole_name_and_yields_an_empty_key() {
731 let names = ["x-meta-"];
732 let matched: Vec<_> = headers_for_prefix(names.into_iter(), "x-meta-").collect();
733 assert_eq!(matched, vec![("", "x-meta-")]);
734 }
735
736 #[test]
740 fn scalar_helpers_agree_with_the_many_value_helpers() {
741 let cases: &[&[&[u8]]] = &[
742 &[],
743 &[b""],
744 &[b"", b""],
745 &[b"5"],
746 &[b" 5 "],
747 &[b"5", b""],
748 &[b"5,6"],
749 &[b"5", b"6"],
750 &[b"notanint"],
751 &[b"5,notanint"],
752 ];
753 for values in cases {
754 let many = read_many_primitive_bytes::<i32>(values.iter().copied());
755 let one = one_primitive_or_none_bytes::<i32>(values.iter().copied());
756 match many {
757 Ok(mut parsed) if parsed.len() <= 1 => assert_eq!(
758 one.as_ref().ok().and_then(|v| v.as_ref()),
759 parsed.pop().as_ref(),
760 "values {values:?} must produce the same single value"
761 ),
762 Ok(parsed) => {
763 let message = format!(
764 "{}",
765 DisplayErrorContext(&one.expect_err("cardinality must fail"))
766 );
767 assert!(
768 message.contains(&format!("expected one item but found {}", parsed.len())),
769 "values {values:?} must report the legacy cardinality message, got '{message}'"
770 );
771 }
772 Err(expected) => {
774 let expected = format!("{}", DisplayErrorContext(&expected));
775 let actual = format!(
776 "{}",
777 DisplayErrorContext(&one.expect_err("parse error must propagate"))
778 );
779 assert_eq!(
780 expected, actual,
781 "values {values:?} must report the same error"
782 );
783 }
784 }
785 }
786 }
787
788 #[test]
789 fn scalar_from_str_helper_agrees_with_the_many_value_helper() {
790 assert_eq!(
791 one_from_str_or_none_bytes::<String>([b"a".as_slice()].into_iter()).expect("valid"),
792 Some("a".to_string())
793 );
794 let err = one_from_str_or_none_bytes::<String>([b"a,b".as_slice()].into_iter())
797 .expect_err("two items");
798 assert!(
799 format!("{}", DisplayErrorContext(&err)).contains("expected one item but found 2"),
800 "expected a cardinality error"
801 );
802 assert_eq!(
803 one_from_str_or_none_bytes::<String>([b"".as_slice()].into_iter()).expect("valid"),
804 None,
805 "an empty value produces no tokens, which the legacy path reports as absent"
806 );
807 }
808
809 #[test]
810 fn scalar_date_helper_agrees_with_many_dates() {
811 let http_date = b"Wed, 21 Oct 2015 07:28:00 GMT".as_slice();
814 assert_eq!(
815 one_date_or_none_bytes([http_date].into_iter(), Format::HttpDate).expect("valid"),
816 Some(many_dates_bytes([http_date].into_iter(), Format::HttpDate).expect("valid")[0])
817 );
818 assert_eq!(
819 one_date_or_none_bytes([b"".as_slice()].into_iter(), Format::HttpDate).expect("valid"),
820 None
821 );
822 let two = b"Wed, 21 Oct 2015 07:28:00 GMT,Wed, 21 Oct 2015 07:28:01 GMT".as_slice();
823 assert_eq!(
824 many_dates_bytes([two].into_iter(), Format::HttpDate)
825 .expect("valid")
826 .len(),
827 2,
828 "precondition: this value really does hold two dates"
829 );
830 let err =
831 one_date_or_none_bytes([two].into_iter(), Format::HttpDate).expect_err("two items");
832 assert!(
833 format!("{}", DisplayErrorContext(&err)).contains("expected one item but found 2"),
834 "expected a cardinality error"
835 );
836 }
837
838 #[test]
839 fn scalar_helpers_reject_non_utf8() {
840 let non_utf8 = b"value-\xe9".as_slice();
841 for message in [
842 format!(
843 "{}",
844 DisplayErrorContext(
845 &one_primitive_or_none_bytes::<i32>([non_utf8].into_iter())
846 .expect_err("not utf-8")
847 )
848 ),
849 format!(
850 "{}",
851 DisplayErrorContext(
852 &one_from_str_or_none_bytes::<String>([non_utf8].into_iter())
853 .expect_err("not utf-8")
854 )
855 ),
856 format!(
857 "{}",
858 DisplayErrorContext(
859 &one_date_or_none_bytes([non_utf8].into_iter(), Format::HttpDate)
860 .expect_err("not utf-8")
861 )
862 ),
863 ] {
864 assert!(
865 message.contains(super::NON_UTF8_HEADER),
866 "expected '{message}' to report the non-UTF-8 reason"
867 );
868 }
869 }
870}