1#![cfg_attr(
94 not(test),
95 deny(
96 clippy::unwrap_used,
97 clippy::expect_used,
98 clippy::panic,
99 clippy::unreachable,
100 clippy::todo,
101 clippy::unimplemented,
102 clippy::indexing_slicing,
103 clippy::string_slice,
104 clippy::arithmetic_side_effects,
105 )
106)]
107
108use std::collections::BTreeMap;
109use std::fmt;
110
111use serde::de::{
112 self, DeserializeOwned, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, VariantAccess,
113 Visitor,
114};
115
116pub const MAX_DEPTH: usize = 16;
122
123#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct QueryError {
133 path: Vec<String>,
135 message: String,
136}
137
138impl QueryError {
139 fn msg(message: impl Into<String>) -> Self {
140 Self {
141 path: Vec::new(),
142 message: message.into(),
143 }
144 }
145
146 fn with_segment(mut self, segment: impl Into<String>) -> Self {
148 self.path.insert(0, segment.into());
149 self
150 }
151}
152
153impl fmt::Display for QueryError {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 if self.path.is_empty() {
156 f.write_str(&self.message)
157 } else {
158 write!(f, "{}: {}", self.path.join("."), self.message)
159 }
160 }
161}
162
163impl std::error::Error for QueryError {}
164
165impl de::Error for QueryError {
166 fn custom<T: fmt::Display>(_msg: T) -> Self {
180 Self::msg("a value was rejected by a custom deserializer".to_owned())
181 }
182
183 fn invalid_type(unexp: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
186 Self::msg(format!(
187 "invalid type: {}, expected {exp}",
188 unexpected_shape(&unexp)
189 ))
190 }
191
192 fn invalid_value(unexp: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
195 Self::msg(format!(
196 "invalid value: {}, expected {exp}",
197 unexpected_shape(&unexp)
198 ))
199 }
200
201 fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self {
204 let _ = variant;
205 Self::msg(format!(
206 "unknown variant, expected one of {}",
207 quoted_list(expected)
208 ))
209 }
210
211 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
214 let _ = field;
215 Self::msg(format!(
216 "unknown field, expected one of {}",
217 quoted_list(expected)
218 ))
219 }
220
221 }
224
225const fn unexpected_shape(unexp: &de::Unexpected<'_>) -> &'static str {
227 match unexp {
228 de::Unexpected::Bool(_) => "a boolean",
229 de::Unexpected::Unsigned(_) | de::Unexpected::Signed(_) => "an integer",
230 de::Unexpected::Float(_) => "a float",
231 de::Unexpected::Char(_) => "a character",
232 de::Unexpected::Str(_) => "a string",
233 de::Unexpected::Bytes(_) => "a byte string",
234 de::Unexpected::Unit => "a unit value",
235 de::Unexpected::Option => "an optional value",
236 de::Unexpected::NewtypeStruct => "a newtype struct",
237 de::Unexpected::Seq => "a sequence",
238 de::Unexpected::Map => "an object",
239 de::Unexpected::Enum => "an enum",
240 de::Unexpected::UnitVariant => "a unit variant",
241 de::Unexpected::NewtypeVariant => "a newtype variant",
242 de::Unexpected::TupleVariant => "a tuple variant",
243 de::Unexpected::StructVariant => "a struct variant",
244 de::Unexpected::Other(_) => "a value",
247 }
248}
249
250fn quoted_list(expected: &'static [&'static str]) -> String {
252 if expected.is_empty() {
253 return "no variants".to_owned();
254 }
255 expected
256 .iter()
257 .map(|name| format!("`{name}`"))
258 .collect::<Vec<_>>()
259 .join(", ")
260}
261
262fn sanitize_key(raw: &str) -> String {
271 const MAX: usize = 48;
272 let mut out: String = raw
273 .chars()
274 .take(MAX)
275 .map(|c| if c.is_control() { '.' } else { c })
276 .collect();
277 if raw.chars().nth(MAX).is_some() {
278 out.push('…');
279 }
280 out
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
289enum Segment<'a> {
290 Append,
292 Index { position: usize, raw: &'a str },
299 Key(&'a str),
301}
302
303fn parse_key(key: &str) -> Option<(&str, Vec<Segment<'_>>)> {
309 let open = key.find('[')?;
310 let (base, mut rest) = key.split_at(open);
311 let mut segments = Vec::new();
312 while !rest.is_empty() {
313 let (inner, remainder) = rest.strip_prefix('[')?.split_once(']')?;
315 if inner.contains('[') {
317 return None;
318 }
319 segments.push(classify_segment(inner));
320 rest = remainder;
321 }
322 Some((base, segments))
323}
324
325fn classify_segment(inner: &str) -> Segment<'_> {
332 if inner.is_empty() {
333 return Segment::Append;
334 }
335 if inner.bytes().all(|b| b.is_ascii_digit()) {
336 return Segment::Index {
337 position: inner.parse().unwrap_or(usize::MAX),
340 raw: inner,
341 };
342 }
343 Segment::Key(inner)
344}
345
346#[derive(Debug)]
356enum Values {
357 One(String),
358 Many(Vec<String>),
359}
360
361impl Default for Values {
362 fn default() -> Self {
363 Self::Many(Vec::new())
364 }
365}
366
367impl Values {
368 fn push(&mut self, value: String) {
369 match self {
370 Self::Many(values) if values.is_empty() => *self = Self::One(value),
371 Self::Many(values) => values.push(value),
372 Self::One(_) => {
373 if let Self::One(first) = std::mem::take(self) {
374 *self = Self::Many(vec![first, value]);
375 }
376 }
377 }
378 }
379}
380
381impl std::ops::Deref for Values {
382 type Target = [String];
383
384 fn deref(&self) -> &[String] {
385 match self {
386 Self::One(value) => std::slice::from_ref(value),
387 Self::Many(values) => values,
388 }
389 }
390}
391
392#[derive(Debug)]
394enum Node {
395 Scalar(Values),
401 Seq(BTreeMap<SeqKey, Self>),
405 Map(BTreeMap<String, Self>),
407 Conflict(String),
416}
417
418impl Node {
419 const fn kind(&self) -> &'static str {
421 match self {
422 Self::Scalar(_) => "a value",
423 Self::Seq(_) => "a sequence",
424 Self::Map(_) => "an object",
425 Self::Conflict(_) => "an unresolvable key",
426 }
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
439struct SeqKey {
440 position: usize,
441 tie: SeqKeyTie,
442}
443
444#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
452enum SeqKeyTie {
453 Explicit(String),
456 Appended(usize),
459}
460
461impl SeqKey {
462 const fn synthesized(position: usize, append_seq: usize) -> Self {
466 Self {
467 position,
468 tie: SeqKeyTie::Appended(append_seq),
469 }
470 }
471
472 fn spelling(&self) -> std::borrow::Cow<'_, str> {
476 match &self.tie {
477 SeqKeyTie::Explicit(raw) => std::borrow::Cow::Borrowed(raw.as_str()),
478 SeqKeyTie::Appended(_) => std::borrow::Cow::Owned(self.position.to_string()),
479 }
480 }
481}
482
483fn key_path(base: &str, segments: &[Segment<'_>], upto: usize) -> String {
488 let mut out = base.to_owned();
489 for segment in segments.iter().take(upto) {
490 match segment {
491 Segment::Key(name) => {
492 out.push('[');
493 out.push_str(name);
494 out.push(']');
495 }
496 Segment::Index { raw, .. } => {
497 out.push('[');
498 out.push_str(raw);
499 out.push(']');
500 }
501 Segment::Append => out.push_str("[]"),
502 }
503 }
504 out
505}
506
507fn insert(root: &mut BTreeMap<String, Node>, base: &str, segments: &[Segment<'_>], value: String) {
513 if segments.len() >= MAX_DEPTH {
517 let node = root
518 .entry(base.to_owned())
519 .or_insert_with(|| Node::Scalar(Values::default()));
520 poison(
521 node,
522 format!(
523 "query key `{}` nests deeper than the maximum of {MAX_DEPTH}",
524 sanitize_key(base)
525 ),
526 );
527 return;
528 }
529
530 let mut node = root
534 .entry(base.to_owned())
535 .or_insert_with(|| empty_for(segments.first()));
536
537 for (position, segment) in segments.iter().enumerate() {
538 let next = segments.get(position.saturating_add(1));
539 match segment {
540 Segment::Key(name) => {
541 promote_to_map(node);
547 let Node::Map(entries) = node else {
548 return poison(node, conflict(base, segments, position, "an object"));
549 };
550 node = entries
551 .entry((*name).to_owned())
552 .or_insert_with(|| empty_for(next));
553 }
554 Segment::Index {
557 position: index,
558 raw,
559 } => match node {
560 Node::Seq(entries) => {
561 node = entries
562 .entry(SeqKey {
563 position: *index,
564 tie: SeqKeyTie::Explicit((*raw).to_owned()),
565 })
566 .or_insert_with(|| empty_for(next));
567 }
568 Node::Map(entries) => {
569 node = entries
570 .entry((*raw).to_owned())
571 .or_insert_with(|| empty_for(next));
572 }
573 other => {
574 return poison(other, conflict(base, segments, position, "a sequence"));
575 }
576 },
577 Segment::Append => match node {
583 Node::Seq(entries) => {
584 let index = entries
585 .keys()
586 .next_back()
587 .map_or(0, |last| last.position.saturating_add(1));
588 let append_seq = entries.len();
589 node = entries
590 .entry(SeqKey::synthesized(index, append_seq))
591 .or_insert_with(|| empty_for(next));
592 }
593 Node::Map(entries) => {
594 let index = entries
595 .keys()
596 .filter_map(|key| key.parse::<usize>().ok())
597 .max()
598 .map_or(0, |last| last.saturating_add(1));
599 let mut key = index.to_string();
606 let mut suffix = 0usize;
607 while entries.contains_key(&key) {
608 key = format!("{index}-{suffix}");
609 suffix = suffix.saturating_add(1);
610 }
611 node = entries.entry(key).or_insert_with(|| empty_for(next));
612 }
613 other => {
614 return poison(other, conflict(base, segments, position, "a sequence"));
615 }
616 },
617 }
618 }
619
620 match node {
621 Node::Scalar(values) => values.push(value),
622 _ => poison(node, conflict(base, segments, segments.len(), "a value")),
623 }
624}
625
626fn promote_to_map(node: &mut Node) {
629 if let Node::Seq(entries) = node {
630 let promoted = std::mem::take(entries)
631 .into_iter()
632 .map(|(key, child)| (key.spelling().into_owned(), child))
633 .collect();
634 *node = Node::Map(promoted);
635 }
636}
637
638fn poison(node: &mut Node, message: String) {
641 if !matches!(node, Node::Conflict(_)) {
642 *node = Node::Conflict(message);
643 }
644}
645
646const fn empty_for(next: Option<&Segment<'_>>) -> Node {
648 match next {
649 None => Node::Scalar(Values::Many(Vec::new())),
650 Some(Segment::Key(_)) => Node::Map(BTreeMap::new()),
651 Some(Segment::Append | Segment::Index { .. }) => Node::Seq(BTreeMap::new()),
652 }
653}
654
655fn conflict(base: &str, segments: &[Segment<'_>], upto: usize, wanted: &str) -> String {
656 format!(
657 "query key `{}` is used as both a container and {wanted}",
658 sanitize_key(&key_path(base, segments, upto))
659 )
660}
661
662fn build_tree<'a>(
664 pairs: impl Iterator<Item = (std::borrow::Cow<'a, str>, std::borrow::Cow<'a, str>)>,
665) -> BTreeMap<String, Node> {
666 let mut root = BTreeMap::new();
667 for (key, value) in pairs {
668 match parse_key(&key) {
669 Some((base, segments)) => insert(&mut root, base, &segments, value.into_owned()),
670 None => insert(&mut root, &key, &[], value.into_owned()),
672 }
673 }
674 root
675}
676
677pub fn from_query_str<T: DeserializeOwned>(query: &str) -> Result<T, QueryError> {
689 let root = build_tree(url::form_urlencoded::parse(query.as_bytes()));
690 T::deserialize(NodeDeserializer {
691 node: &Node::Map(root),
692 })
693}
694
695struct ValueDeserializer<'a> {
702 value: &'a str,
703}
704
705macro_rules! parse_scalar {
706 ($($method:ident => $visit:ident : $ty:ty),* $(,)?) => {
707 $(
708 fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
709 let parsed: $ty = self.value.parse().map_err(|_| {
714 QueryError::msg(concat!("invalid ", stringify!($ty), " value"))
715 })?;
716 visitor.$visit(parsed)
717 }
718 )*
719 };
720}
721
722impl<'de> de::Deserializer<'de> for ValueDeserializer<'_> {
723 type Error = QueryError;
724
725 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
726 visitor.visit_str(self.value)
727 }
728
729 parse_scalar! {
730 deserialize_bool => visit_bool: bool,
731 deserialize_i8 => visit_i8: i8,
732 deserialize_i16 => visit_i16: i16,
733 deserialize_i32 => visit_i32: i32,
734 deserialize_i64 => visit_i64: i64,
735 deserialize_i128 => visit_i128: i128,
736 deserialize_u8 => visit_u8: u8,
737 deserialize_u16 => visit_u16: u16,
738 deserialize_u32 => visit_u32: u32,
739 deserialize_u64 => visit_u64: u64,
740 deserialize_u128 => visit_u128: u128,
741 deserialize_f32 => visit_f32: f32,
742 deserialize_f64 => visit_f64: f64,
743 deserialize_char => visit_char: char,
744 }
745
746 fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
747 visitor.visit_str(self.value)
748 }
749
750 fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
751 visitor.visit_str(self.value)
752 }
753
754 fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
755 visitor.visit_bytes(self.value.as_bytes())
756 }
757
758 fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
759 visitor.visit_bytes(self.value.as_bytes())
760 }
761
762 fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
765 visitor.visit_some(self)
766 }
767
768 fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
769 visitor.visit_unit()
770 }
771
772 fn deserialize_unit_struct<V: Visitor<'de>>(
773 self,
774 _name: &'static str,
775 visitor: V,
776 ) -> Result<V::Value, QueryError> {
777 visitor.visit_unit()
778 }
779
780 fn deserialize_newtype_struct<V: Visitor<'de>>(
781 self,
782 _name: &'static str,
783 visitor: V,
784 ) -> Result<V::Value, QueryError> {
785 visitor.visit_newtype_struct(self)
786 }
787
788 fn deserialize_seq<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, QueryError> {
789 Err(QueryError::msg("expected a sequence, found a single value"))
790 }
791
792 fn deserialize_tuple<V: Visitor<'de>>(
793 self,
794 _len: usize,
795 visitor: V,
796 ) -> Result<V::Value, QueryError> {
797 self.deserialize_seq(visitor)
798 }
799
800 fn deserialize_tuple_struct<V: Visitor<'de>>(
801 self,
802 _name: &'static str,
803 _len: usize,
804 visitor: V,
805 ) -> Result<V::Value, QueryError> {
806 self.deserialize_seq(visitor)
807 }
808
809 fn deserialize_map<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, QueryError> {
810 Err(QueryError::msg("expected an object, found a single value"))
811 }
812
813 fn deserialize_struct<V: Visitor<'de>>(
814 self,
815 _name: &'static str,
816 _fields: &'static [&'static str],
817 visitor: V,
818 ) -> Result<V::Value, QueryError> {
819 self.deserialize_map(visitor)
820 }
821
822 fn deserialize_enum<V: Visitor<'de>>(
824 self,
825 _name: &'static str,
826 _variants: &'static [&'static str],
827 visitor: V,
828 ) -> Result<V::Value, QueryError> {
829 visitor.visit_enum(UnitVariant { value: self.value })
830 }
831
832 fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
833 visitor.visit_str(self.value)
834 }
835
836 fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
837 visitor.visit_unit()
838 }
839}
840
841struct UnitVariant<'a> {
843 value: &'a str,
844}
845
846impl<'de> EnumAccess<'de> for UnitVariant<'_> {
847 type Error = QueryError;
848 type Variant = Self;
849
850 fn variant_seed<V: DeserializeSeed<'de>>(
851 self,
852 seed: V,
853 ) -> Result<(V::Value, Self), QueryError> {
854 let variant = seed.deserialize(ValueDeserializer { value: self.value })?;
855 Ok((variant, self))
856 }
857}
858
859impl<'de> VariantAccess<'de> for UnitVariant<'_> {
860 type Error = QueryError;
861
862 fn unit_variant(self) -> Result<(), QueryError> {
863 Ok(())
864 }
865
866 fn newtype_variant_seed<T: DeserializeSeed<'de>>(
867 self,
868 _seed: T,
869 ) -> Result<T::Value, QueryError> {
870 Err(QueryError::msg(
871 "expected a unit variant; a data-carrying variant needs the bracketed form \
872 `key[variant][field]=…`",
873 ))
874 }
875
876 fn tuple_variant<V: Visitor<'de>>(
877 self,
878 _len: usize,
879 _visitor: V,
880 ) -> Result<V::Value, QueryError> {
881 Err(QueryError::msg("expected a unit variant"))
882 }
883
884 fn struct_variant<V: Visitor<'de>>(
885 self,
886 _fields: &'static [&'static str],
887 _visitor: V,
888 ) -> Result<V::Value, QueryError> {
889 Err(QueryError::msg("expected a unit variant"))
890 }
891}
892
893struct NodeDeserializer<'a> {
898 node: &'a Node,
899}
900
901impl<'a> NodeDeserializer<'a> {
902 fn check(&self) -> Result<(), QueryError> {
907 match self.node {
908 Node::Conflict(message) => Err(QueryError::msg(message.clone())),
909 _ => Ok(()),
910 }
911 }
912
913 fn as_value(&self, wanted: &str) -> Result<ValueDeserializer<'a>, QueryError> {
922 self.check()?;
923 match self.node {
924 Node::Scalar(values) if values.len() > 1 => Err(QueryError::msg(
925 "duplicate query parameter: more than one value was submitted for a \
926 single-valued field",
927 )),
928 Node::Scalar(values) => Ok(ValueDeserializer {
929 value: values.first().map_or("", String::as_str),
932 }),
933 other => Err(QueryError::msg(format!(
938 "expected {wanted}, found {} — the request used the bracketed form \
939 (`key[…]=`) for this parameter, so give the field a nested type or \
940 accept it as `serde_json::Value`",
941 other.kind()
942 ))),
943 }
944 }
945}
946
947macro_rules! forward_scalar {
949 ($($method:ident),* $(,)?) => {
950 $(
951 fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
952 self.as_value("a value")?.$method(visitor)
953 }
954 )*
955 };
956}
957
958impl<'de> de::Deserializer<'de> for NodeDeserializer<'_> {
959 type Error = QueryError;
960
961 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
965 self.check()?;
966 match self.node {
967 Node::Scalar(values) if values.len() == 1 => {
968 visitor.visit_str(values.first().map_or("", String::as_str))
969 }
970 Node::Scalar(_) | Node::Seq(_) => self.deserialize_seq(visitor),
971 Node::Map(_) | Node::Conflict(_) => self.deserialize_map(visitor),
972 }
973 }
974
975 forward_scalar! {
976 deserialize_bool,
977 deserialize_i8, deserialize_i16, deserialize_i32, deserialize_i64, deserialize_i128,
978 deserialize_u8, deserialize_u16, deserialize_u32, deserialize_u64, deserialize_u128,
979 deserialize_f32, deserialize_f64,
980 deserialize_char, deserialize_str, deserialize_string,
981 deserialize_bytes, deserialize_byte_buf,
982 deserialize_identifier,
983 }
984
985 fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
986 visitor.visit_some(self)
987 }
988
989 fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
994 self.as_value("a value")?;
995 visitor.visit_unit()
996 }
997
998 fn deserialize_unit_struct<V: Visitor<'de>>(
999 self,
1000 _name: &'static str,
1001 visitor: V,
1002 ) -> Result<V::Value, QueryError> {
1003 self.as_value("a value")?;
1004 visitor.visit_unit()
1005 }
1006
1007 fn deserialize_newtype_struct<V: Visitor<'de>>(
1008 self,
1009 _name: &'static str,
1010 visitor: V,
1011 ) -> Result<V::Value, QueryError> {
1012 visitor.visit_newtype_struct(self)
1013 }
1014
1015 fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
1016 self.check()?;
1017 match self.node {
1018 Node::Scalar(values) => visitor.visit_seq(ValueSeq {
1020 values: values.iter(),
1021 }),
1022 Node::Seq(entries) => visitor.visit_seq(NodeSeq {
1024 entries: entries.iter(),
1025 }),
1026 Node::Map(entries) => visitor.visit_seq(PairSeq {
1029 pairs: flatten_pairs(entries).into_iter(),
1030 }),
1031 Node::Conflict(message) => Err(QueryError::msg(message.clone())),
1033 }
1034 }
1035
1036 fn deserialize_tuple<V: Visitor<'de>>(
1037 self,
1038 _len: usize,
1039 visitor: V,
1040 ) -> Result<V::Value, QueryError> {
1041 self.deserialize_seq(visitor)
1042 }
1043
1044 fn deserialize_tuple_struct<V: Visitor<'de>>(
1045 self,
1046 _name: &'static str,
1047 _len: usize,
1048 visitor: V,
1049 ) -> Result<V::Value, QueryError> {
1050 self.deserialize_seq(visitor)
1051 }
1052
1053 fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
1054 self.check()?;
1055 match self.node {
1056 Node::Map(entries) => visitor.visit_map(NodeMap {
1057 entries: entries.iter(),
1058 value: None,
1059 key: "",
1060 }),
1061 Node::Seq(entries) => visitor.visit_map(IndexMap {
1067 entries: entries.iter(),
1068 value: None,
1069 key: String::new(),
1070 }),
1071 other => Err(QueryError::msg(format!(
1072 "expected an object, found {}",
1073 other.kind()
1074 ))),
1075 }
1076 }
1077
1078 fn deserialize_struct<V: Visitor<'de>>(
1079 self,
1080 _name: &'static str,
1081 _fields: &'static [&'static str],
1082 visitor: V,
1083 ) -> Result<V::Value, QueryError> {
1084 self.deserialize_map(visitor)
1085 }
1086
1087 fn deserialize_enum<V: Visitor<'de>>(
1090 self,
1091 name: &'static str,
1092 variants: &'static [&'static str],
1093 visitor: V,
1094 ) -> Result<V::Value, QueryError> {
1095 self.check()?;
1096 match self.node {
1097 Node::Scalar(_) => self
1098 .as_value("a value")?
1099 .deserialize_enum(name, variants, visitor),
1100 Node::Map(entries) if entries.len() == 1 => match entries.iter().next() {
1101 Some((variant, content)) => visitor.visit_enum(NodeVariant { variant, content }),
1102 None => Err(QueryError::msg("expected an enum variant name")),
1105 },
1106 other => Err(QueryError::msg(format!(
1107 "expected an enum variant name or a single-entry object, found {}",
1108 other.kind()
1109 ))),
1110 }
1111 }
1112
1113 fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
1115 visitor.visit_unit()
1116 }
1117}
1118
1119enum PairValue<'a> {
1121 Value(&'a str),
1122 Node(&'a Node),
1123}
1124
1125fn flatten_pairs(entries: &BTreeMap<String, Node>) -> Vec<(&str, PairValue<'_>)> {
1128 let mut out = Vec::new();
1129 for (key, node) in entries {
1130 match node {
1131 Node::Scalar(values) => {
1132 out.extend(values.iter().map(|v| (key.as_str(), PairValue::Value(v))));
1133 }
1134 other => out.push((key.as_str(), PairValue::Node(other))),
1135 }
1136 }
1137 out
1138}
1139
1140struct ValueSeq<'a> {
1141 values: std::slice::Iter<'a, String>,
1142}
1143
1144impl<'de> SeqAccess<'de> for ValueSeq<'_> {
1145 type Error = QueryError;
1146
1147 fn next_element_seed<T: DeserializeSeed<'de>>(
1148 &mut self,
1149 seed: T,
1150 ) -> Result<Option<T::Value>, QueryError> {
1151 self.values
1152 .next()
1153 .map(|value| seed.deserialize(ValueDeserializer { value }))
1154 .transpose()
1155 }
1156
1157 fn size_hint(&self) -> Option<usize> {
1158 Some(self.values.len())
1159 }
1160}
1161
1162struct NodeSeq<'a> {
1163 entries: std::collections::btree_map::Iter<'a, SeqKey, Node>,
1164}
1165
1166impl<'de> SeqAccess<'de> for NodeSeq<'_> {
1167 type Error = QueryError;
1168
1169 fn next_element_seed<T: DeserializeSeed<'de>>(
1170 &mut self,
1171 seed: T,
1172 ) -> Result<Option<T::Value>, QueryError> {
1173 let Some((key, node)) = self.entries.next() else {
1174 return Ok(None);
1175 };
1176 seed.deserialize(NodeDeserializer { node })
1177 .map(Some)
1178 .map_err(|err| err.with_segment(sanitize_key(&key.spelling())))
1181 }
1182
1183 fn size_hint(&self) -> Option<usize> {
1184 Some(self.entries.len())
1185 }
1186}
1187
1188struct PairSeq<'a> {
1189 pairs: std::vec::IntoIter<(&'a str, PairValue<'a>)>,
1190}
1191
1192impl<'de> SeqAccess<'de> for PairSeq<'_> {
1193 type Error = QueryError;
1194
1195 fn next_element_seed<T: DeserializeSeed<'de>>(
1196 &mut self,
1197 seed: T,
1198 ) -> Result<Option<T::Value>, QueryError> {
1199 let Some((key, value)) = self.pairs.next() else {
1200 return Ok(None);
1201 };
1202 seed.deserialize(PairDeserializer { key, value })
1203 .map(Some)
1204 .map_err(|err| err.with_segment(sanitize_key(key)))
1207 }
1208
1209 fn size_hint(&self) -> Option<usize> {
1210 Some(self.pairs.len())
1211 }
1212}
1213
1214struct PairDeserializer<'a> {
1216 key: &'a str,
1217 value: PairValue<'a>,
1218}
1219
1220impl<'de> de::Deserializer<'de> for PairDeserializer<'_> {
1221 type Error = QueryError;
1222
1223 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, QueryError> {
1224 visitor.visit_seq(PairElements {
1225 key: Some(self.key),
1226 value: Some(self.value),
1227 })
1228 }
1229
1230 serde::forward_to_deserialize_any! {
1231 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
1232 bytes byte_buf option unit unit_struct newtype_struct seq tuple
1233 tuple_struct map struct enum identifier ignored_any
1234 }
1235}
1236
1237struct PairElements<'a> {
1238 key: Option<&'a str>,
1239 value: Option<PairValue<'a>>,
1240}
1241
1242impl<'de> SeqAccess<'de> for PairElements<'_> {
1243 type Error = QueryError;
1244
1245 fn next_element_seed<T: DeserializeSeed<'de>>(
1246 &mut self,
1247 seed: T,
1248 ) -> Result<Option<T::Value>, QueryError> {
1249 if let Some(key) = self.key.take() {
1250 return seed.deserialize(ValueDeserializer { value: key }).map(Some);
1251 }
1252 match self.value.take() {
1253 Some(PairValue::Value(value)) => {
1254 seed.deserialize(ValueDeserializer { value }).map(Some)
1255 }
1256 Some(PairValue::Node(node)) => seed.deserialize(NodeDeserializer { node }).map(Some),
1257 None => Ok(None),
1258 }
1259 }
1260}
1261
1262struct NodeMap<'a> {
1263 entries: std::collections::btree_map::Iter<'a, String, Node>,
1264 value: Option<&'a Node>,
1265 key: &'a str,
1266}
1267
1268impl<'de> MapAccess<'de> for NodeMap<'_> {
1269 type Error = QueryError;
1270
1271 fn next_key_seed<K: DeserializeSeed<'de>>(
1272 &mut self,
1273 seed: K,
1274 ) -> Result<Option<K::Value>, QueryError> {
1275 let Some((key, node)) = self.entries.next() else {
1276 return Ok(None);
1277 };
1278 self.value = Some(node);
1279 self.key = key;
1280 seed.deserialize(ValueDeserializer { value: key }).map(Some)
1281 }
1282
1283 fn next_value_seed<V: DeserializeSeed<'de>>(
1284 &mut self,
1285 seed: V,
1286 ) -> Result<V::Value, QueryError> {
1287 let node = self.value.take().ok_or_else(|| {
1288 QueryError::msg("internal error: query map value requested before its key")
1289 })?;
1290 seed.deserialize(NodeDeserializer { node })
1293 .map_err(|err| err.with_segment(sanitize_key(self.key)))
1294 }
1295
1296 fn size_hint(&self) -> Option<usize> {
1297 Some(self.entries.len())
1298 }
1299}
1300
1301struct IndexMap<'a> {
1303 entries: std::collections::btree_map::Iter<'a, SeqKey, Node>,
1304 value: Option<&'a Node>,
1305 key: String,
1306}
1307
1308impl<'de> MapAccess<'de> for IndexMap<'_> {
1309 type Error = QueryError;
1310
1311 fn next_key_seed<K: DeserializeSeed<'de>>(
1312 &mut self,
1313 seed: K,
1314 ) -> Result<Option<K::Value>, QueryError> {
1315 let Some((key, node)) = self.entries.next() else {
1316 return Ok(None);
1317 };
1318 self.value = Some(node);
1319 self.key = key.spelling().into_owned();
1322 seed.deserialize(ValueDeserializer { value: &self.key })
1323 .map(Some)
1324 }
1325
1326 fn next_value_seed<V: DeserializeSeed<'de>>(
1327 &mut self,
1328 seed: V,
1329 ) -> Result<V::Value, QueryError> {
1330 let node = self.value.take().ok_or_else(|| {
1331 QueryError::msg("internal error: query map value requested before its key")
1332 })?;
1333 let key = sanitize_key(&self.key);
1334 seed.deserialize(NodeDeserializer { node })
1335 .map_err(|err| err.with_segment(key))
1336 }
1337
1338 fn size_hint(&self) -> Option<usize> {
1339 Some(self.entries.len())
1340 }
1341}
1342
1343struct NodeVariant<'a> {
1345 variant: &'a str,
1346 content: &'a Node,
1347}
1348
1349impl<'de, 'a> EnumAccess<'de> for NodeVariant<'a> {
1350 type Error = QueryError;
1351 type Variant = NodeContent<'a>;
1352
1353 fn variant_seed<V: DeserializeSeed<'de>>(
1354 self,
1355 seed: V,
1356 ) -> Result<(V::Value, NodeContent<'a>), QueryError> {
1357 let variant = seed.deserialize(ValueDeserializer {
1358 value: self.variant,
1359 })?;
1360 Ok((
1361 variant,
1362 NodeContent {
1363 content: self.content,
1364 },
1365 ))
1366 }
1367}
1368
1369struct NodeContent<'a> {
1370 content: &'a Node,
1371}
1372
1373impl<'de> VariantAccess<'de> for NodeContent<'_> {
1374 type Error = QueryError;
1375
1376 fn unit_variant(self) -> Result<(), QueryError> {
1381 NodeDeserializer { node: self.content }.as_value("a value")?;
1382 Ok(())
1383 }
1384
1385 fn newtype_variant_seed<T: DeserializeSeed<'de>>(
1386 self,
1387 seed: T,
1388 ) -> Result<T::Value, QueryError> {
1389 seed.deserialize(NodeDeserializer { node: self.content })
1390 }
1391
1392 fn tuple_variant<V: Visitor<'de>>(
1393 self,
1394 _len: usize,
1395 visitor: V,
1396 ) -> Result<V::Value, QueryError> {
1397 de::Deserializer::deserialize_seq(NodeDeserializer { node: self.content }, visitor)
1398 }
1399
1400 fn struct_variant<V: Visitor<'de>>(
1401 self,
1402 _fields: &'static [&'static str],
1403 visitor: V,
1404 ) -> Result<V::Value, QueryError> {
1405 de::Deserializer::deserialize_map(NodeDeserializer { node: self.content }, visitor)
1406 }
1407}
1408
1409#[cfg(test)]
1410mod tests {
1411 use super::*;
1412 use serde::Deserialize;
1413
1414 #[derive(Debug, Deserialize, PartialEq)]
1415 struct Filter {
1416 status: String,
1417 limit: Option<u32>,
1418 }
1419
1420 #[derive(Debug, Deserialize, PartialEq)]
1421 struct Item {
1422 sku: String,
1423 qty: u32,
1424 }
1425
1426 #[derive(Debug, Deserialize, PartialEq, Default)]
1427 struct Args {
1428 q: Option<String>,
1429 page: Option<u32>,
1430 flag: Option<bool>,
1431 tags: Option<Vec<String>>,
1432 filter: Option<Filter>,
1433 items: Option<Vec<Item>>,
1434 }
1435
1436 fn args(query: &str) -> Args {
1437 from_query_str(query).expect("decodes")
1438 }
1439
1440 #[test]
1441 fn flat_scalars_match_the_previous_urlencoded_behaviour() {
1442 let out = args("q=foo&page=2&flag=true");
1443 assert_eq!(out.q.as_deref(), Some("foo"));
1444 assert_eq!(out.page, Some(2));
1445 assert_eq!(out.flag, Some(true));
1446 }
1447
1448 #[test]
1449 fn absent_keys_are_none_and_an_empty_query_decodes() {
1450 assert_eq!(args(""), Args::default());
1451 }
1452
1453 #[test]
1454 fn a_present_but_empty_optional_still_visits_some() {
1455 assert_eq!(args("q=").q.as_deref(), Some(""));
1457 assert!(
1458 from_query_str::<Args>("page=").is_err(),
1459 "an empty value for a numeric field is still a coercion failure"
1460 );
1461 }
1462
1463 #[test]
1464 fn repeated_keys_fill_a_sequence_but_a_duplicated_scalar_is_rejected() {
1465 assert_eq!(
1466 args("tags=a&tags=b").tags.unwrap(),
1467 vec!["a".to_owned(), "b".to_owned()]
1468 );
1469 let err = from_query_str::<Args>("q=first&q=second").expect_err("duplicate scalar");
1473 assert!(
1474 err.to_string().contains("duplicate query parameter"),
1475 "{err}"
1476 );
1477 }
1478
1479 #[test]
1480 fn distinct_index_spellings_stay_distinct() {
1481 #[derive(Debug, Deserialize)]
1485 struct Counts {
1486 counts: std::collections::HashMap<String, u32>,
1487 }
1488
1489 assert_eq!(
1490 args("tags[0]=a&tags[00]=b").tags.unwrap(),
1491 vec!["a".to_owned(), "b".to_owned()]
1492 );
1493
1494 let out: Counts = from_query_str("counts[00]=5&counts[0]=6").expect("decodes");
1495 assert_eq!(
1496 out.counts.get("00"),
1497 Some(&5),
1498 "spelling preserved: {out:?}"
1499 );
1500 assert_eq!(out.counts.get("0"), Some(&6));
1501
1502 let out: Counts = from_query_str("counts[99999999999999999999]=7").expect("decodes");
1504 assert_eq!(out.counts.get("99999999999999999999"), Some(&7));
1505
1506 assert!(from_query_str::<Args>("tags[0]=a&tags[0]=b").is_err());
1508 }
1509
1510 #[test]
1511 fn a_custom_deserializer_message_is_discarded_not_merely_bounded() {
1512 fn picky<'de, D>(deserializer: D) -> Result<String, D::Error>
1516 where
1517 D: serde::Deserializer<'de>,
1518 {
1519 let raw = String::deserialize(deserializer)?;
1520 Err(serde::de::Error::custom(format!("invalid token {raw}")))
1521 }
1522
1523 #[derive(Debug, Deserialize)]
1524 struct Guarded {
1525 #[serde(deserialize_with = "picky")]
1526 #[allow(
1527 dead_code,
1528 reason = "the helper always errors; the field is never built"
1529 )]
1530 token: String,
1531 }
1532
1533 let err = from_query_str::<Guarded>("token=SUPERSECRET").expect_err("helper rejects");
1534 let rendered = err.to_string();
1535 assert!(!rendered.contains("SUPERSECRET"), "leaked: {rendered}");
1536 assert!(
1537 !rendered.contains("invalid token"),
1538 "retained content: {rendered}"
1539 );
1540 assert!(rendered.contains("token"), "{rendered}");
1543
1544 let long = "S3CRET".repeat(60);
1546 let err = from_query_str::<Guarded>(&format!("token={long}")).expect_err("helper rejects");
1547 assert!(!err.to_string().contains("S3CRET"), "{err}");
1548 }
1549
1550 #[test]
1551 fn a_unit_variant_still_validates_the_node_it_claims() {
1552 #[derive(Debug, Deserialize, PartialEq)]
1555 #[serde(rename_all = "lowercase")]
1556 enum Mode {
1557 Asc,
1558 }
1559 #[derive(Debug, Deserialize)]
1560 struct Sorted {
1561 mode: Mode,
1562 }
1563 assert_eq!(
1564 from_query_str::<Sorted>("mode[asc]=1")
1565 .expect("decodes")
1566 .mode,
1567 Mode::Asc
1568 );
1569 assert!(
1570 from_query_str::<Sorted>("mode[asc][unexpected]=value").is_err(),
1571 "an attached payload must not be silently discarded"
1572 );
1573 assert!(from_query_str::<Sorted>("mode=asc").is_ok());
1574 }
1575
1576 #[test]
1577 fn serde_generated_errors_never_echo_request_text() {
1578 #[derive(Debug, Deserialize)]
1581 #[serde(rename_all = "lowercase")]
1582 enum Sort {
1583 Asc,
1584 }
1585 #[derive(Debug, Deserialize)]
1586 struct Sorted {
1587 #[allow(dead_code)]
1588 sort: Sort,
1589 }
1590 #[derive(Debug, Deserialize)]
1591 #[serde(deny_unknown_fields)]
1592 struct Strict {
1593 #[allow(dead_code)]
1594 q: String,
1595 }
1596 #[derive(Debug, Deserialize)]
1598 struct Nested {
1599 #[allow(dead_code)]
1600 filter: std::collections::HashMap<String, String>,
1601 }
1602
1603 let err = from_query_str::<Sorted>("sort=SUPERSECRET").expect_err("unknown variant");
1604 assert!(!err.to_string().contains("SUPERSECRET"), "{err}");
1605 let err = from_query_str::<Strict>("q=x&SUPERSECRET=1").expect_err("unknown field");
1606 assert!(!err.to_string().contains("SUPERSECRET"), "{err}");
1607 let err =
1608 from_query_str::<Nested>("filter[a]=x&filter=SUPERSECRET").expect_err("shape conflict");
1609 assert!(!err.to_string().contains("SUPERSECRET"), "{err}");
1610 }
1611
1612 #[test]
1613 fn a_unit_field_still_validates_the_node_it_claims() {
1614 #[derive(Debug, Deserialize)]
1618 struct WithUnit {
1619 #[allow(dead_code)]
1620 flag: (),
1621 }
1622 assert!(
1623 from_query_str::<WithUnit>("flag=x&flag[y]=z").is_err(),
1624 "a claimed conflicting key must fail even for a unit field"
1625 );
1626 assert!(
1627 from_query_str::<WithUnit>("flag[y]=z").is_err(),
1628 "a bracketed container is not a value"
1629 );
1630 assert!(from_query_str::<WithUnit>("flag=x").is_ok());
1631 }
1632
1633 #[test]
1634 fn pair_sequence_errors_sanitize_their_key() {
1635 let err = from_query_str::<Vec<(String, u32)>>("\nATTACKER=not-a-number")
1639 .expect_err("coercion failure");
1640 let rendered = err.to_string();
1641 assert!(
1642 !rendered.contains('\n'),
1643 "control chars stripped: {rendered:?}"
1644 );
1645
1646 let long = format!("{}=not-a-number", "k".repeat(500));
1647 let err = from_query_str::<Vec<(String, u32)>>(&long).expect_err("coercion failure");
1648 assert!(
1649 err.to_string().chars().count() < 200,
1650 "key bounded: {}",
1651 err.to_string().len()
1652 );
1653 }
1654
1655 #[test]
1656 fn a_container_may_mix_numeric_and_named_keys() {
1657 #[derive(Debug, Deserialize)]
1660 struct Dynamic {
1661 filter: std::collections::HashMap<String, String>,
1662 }
1663 let out: Dynamic = from_query_str("filter[0]=zero&filter[name]=value").expect("decodes");
1664 assert_eq!(out.filter.get("0").map(String::as_str), Some("zero"));
1665 assert_eq!(out.filter.get("name").map(String::as_str), Some("value"));
1666
1667 let out: Dynamic = from_query_str("filter[name]=value&filter[0]=zero").expect("decodes");
1669 assert_eq!(out.filter.get("0").map(String::as_str), Some("zero"));
1670 assert_eq!(out.filter.get("name").map(String::as_str), Some("value"));
1671
1672 assert_eq!(
1674 args("tags[0]=a&tags[1]=b").tags.unwrap(),
1675 vec!["a".to_owned(), "b".to_owned()]
1676 );
1677 }
1678
1679 #[test]
1680 fn errors_never_echo_the_submitted_value() {
1681 let err = from_query_str::<Args>("page=SUPERSECRET").expect_err("coercion failure");
1684 let rendered = err.to_string();
1685 assert!(
1686 !rendered.contains("SUPERSECRET"),
1687 "value leaked: {rendered}"
1688 );
1689 assert!(rendered.contains("page"), "field path kept: {rendered}");
1690 }
1691
1692 #[test]
1693 fn error_text_bounds_and_cleans_attacker_supplied_key_text() {
1694 let long = "k".repeat(500);
1695 let rendered = sanitize_key(&long);
1696 assert!(
1697 rendered.chars().count() <= 49,
1698 "bounded: {}",
1699 rendered.len()
1700 );
1701 assert_eq!(sanitize_key("a\nb"), "a.b");
1702 }
1703
1704 #[test]
1705 fn a_conflicting_key_the_target_ignores_is_still_ignored() {
1706 let out = args("q=ok&utm=1&utm[src]=x");
1709 assert_eq!(out.q.as_deref(), Some("ok"));
1710 let deep = format!("q=ok&junk{}=1", "[x]".repeat(MAX_DEPTH));
1712 assert_eq!(args(&deep).q.as_deref(), Some("ok"));
1713 }
1714
1715 #[test]
1716 fn an_all_digit_object_key_still_lands_in_a_map_target() {
1717 let out: std::collections::HashMap<String, std::collections::HashMap<String, u32>> =
1720 from_query_str("counts[2024]=5").expect("decodes");
1721 assert_eq!(out["counts"]["2024"], 5);
1722 }
1723
1724 #[test]
1725 fn append_and_indexed_forms_fill_a_sequence() {
1726 assert_eq!(
1727 args("tags[]=a&tags[]=b").tags.unwrap(),
1728 vec!["a".to_owned(), "b".to_owned()]
1729 );
1730 assert_eq!(
1731 args("tags[1]=b&tags[0]=a").tags.unwrap(),
1732 vec!["a".to_owned(), "b".to_owned()]
1733 );
1734 }
1735
1736 #[test]
1737 fn gapped_indices_compact_in_ascending_order() {
1738 assert_eq!(
1739 args("tags[4]=c&tags[0]=a&tags[2]=b").tags.unwrap(),
1740 vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
1741 );
1742 }
1743
1744 #[test]
1745 fn an_absurd_index_sorts_last_without_preallocating() {
1746 assert_eq!(
1747 args("tags[99999999999999999999]=last&tags[3]=first")
1748 .tags
1749 .unwrap(),
1750 vec!["first".to_owned(), "last".to_owned()]
1751 );
1752 }
1753
1754 #[test]
1755 fn appends_continue_after_the_highest_explicit_index() {
1756 assert_eq!(
1757 args("tags[]=a&tags[5]=b&tags[]=c").tags.unwrap(),
1758 vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
1759 );
1760 }
1761
1762 #[test]
1763 fn append_after_a_saturated_explicit_index_sorts_after_it() {
1764 assert_eq!(
1768 args("tags[99999999999999999999]=a&tags[]=b").tags.unwrap(),
1769 vec!["a".to_owned(), "b".to_owned()]
1770 );
1771 }
1772
1773 #[test]
1774 fn append_after_an_index_spelled_exactly_usize_max_does_not_collide() {
1775 assert_eq!(
1779 args("tags[18446744073709551615]=a&tags[]=b").tags.unwrap(),
1780 vec!["a".to_owned(), "b".to_owned()]
1781 );
1782 }
1783
1784 #[test]
1785 fn append_after_a_saturated_index_does_not_merge_nested_elements() {
1786 #[derive(Debug, Deserialize, PartialEq)]
1789 struct Wrapper {
1790 x: String,
1791 }
1792 #[derive(Debug, Deserialize)]
1793 struct Nested {
1794 tags: Vec<Wrapper>,
1795 }
1796
1797 let out: Nested = from_query_str("tags[18446744073709551615][x]=first&tags[][x]=second")
1798 .expect("decodes");
1799 assert_eq!(
1800 out.tags,
1801 vec![
1802 Wrapper {
1803 x: "first".to_owned()
1804 },
1805 Wrapper {
1806 x: "second".to_owned()
1807 },
1808 ]
1809 );
1810 }
1811
1812 #[test]
1813 fn an_explicit_index_always_sorts_before_an_append_at_the_same_position() {
1814 assert!(SeqKeyTie::Explicit(String::new()) < SeqKeyTie::Appended(0));
1819 }
1820
1821 #[test]
1822 fn append_into_a_promoted_map_after_a_saturated_index_does_not_merge() {
1823 #[derive(Debug, Deserialize)]
1827 struct Dynamic {
1828 filter: std::collections::HashMap<String, String>,
1829 }
1830
1831 let out: Dynamic =
1832 from_query_str("filter[18446744073709551615]=zero&filter[name]=value&filter[]=next")
1833 .expect("decodes");
1834 assert_eq!(
1835 out.filter.len(),
1836 3,
1837 "the append must not merge into the saturated key: {out:?}"
1838 );
1839 assert_eq!(
1840 out.filter.get("18446744073709551615").map(String::as_str),
1841 Some("zero")
1842 );
1843 assert_eq!(out.filter.get("name").map(String::as_str), Some("value"));
1844 }
1845
1846 #[test]
1847 fn nested_objects_decode() {
1848 assert_eq!(
1849 args("filter[status]=open&filter[limit]=5").filter.unwrap(),
1850 Filter {
1851 status: "open".to_owned(),
1852 limit: Some(5),
1853 }
1854 );
1855 }
1856
1857 #[test]
1858 fn arrays_of_objects_decode() {
1859 let out = args("items[0][sku]=A-1&items[0][qty]=2&items[1][sku]=B-2&items[1][qty]=3");
1860 assert_eq!(
1861 out.items.unwrap(),
1862 vec![
1863 Item {
1864 sku: "A-1".to_owned(),
1865 qty: 2
1866 },
1867 Item {
1868 sku: "B-2".to_owned(),
1869 qty: 3
1870 },
1871 ]
1872 );
1873 }
1874
1875 #[test]
1876 fn percent_encoded_brackets_parse_identically() {
1877 assert_eq!(
1878 args("filter%5Bstatus%5D=open").filter.unwrap().status,
1879 "open"
1880 );
1881 }
1882
1883 #[test]
1884 fn plus_is_decoded_as_a_space() {
1885 assert_eq!(args("q=hello+world").q.as_deref(), Some("hello world"));
1886 }
1887
1888 #[test]
1889 fn malformed_bracket_keys_stay_literal() {
1890 assert_eq!(parse_key("weird[unclosed"), None);
1891 assert_eq!(parse_key("a[b][c"), None);
1892 assert_eq!(parse_key("a[[b]]"), None);
1893 assert_eq!(parse_key("plain"), None);
1894 assert_eq!(args("weird[unclosed=1&q=ok").q.as_deref(), Some("ok"));
1896 }
1897
1898 #[test]
1899 fn bracket_keys_parse_into_segments() {
1900 assert_eq!(
1901 parse_key("items[0][sku]"),
1902 Some((
1903 "items",
1904 vec![
1905 Segment::Index {
1906 position: 0,
1907 raw: "0"
1908 },
1909 Segment::Key("sku")
1910 ]
1911 ))
1912 );
1913 assert_eq!(parse_key("tags[]"), Some(("tags", vec![Segment::Append])));
1914 }
1915
1916 #[test]
1917 fn shape_conflicts_are_rejected() {
1918 let scalar_then_object = from_query_str::<Args>("filter=flat&filter[status]=open");
1919 assert!(scalar_then_object.is_err());
1920 let object_then_scalar = from_query_str::<Args>("filter[status]=open&filter=flat");
1921 assert!(object_then_scalar.is_err());
1922 let seq_then_object = from_query_str::<Args>("tags[0]=a&tags[name]=b");
1923 assert!(seq_then_object.is_err());
1924 }
1925
1926 #[test]
1927 fn nesting_deeper_than_the_cap_is_rejected_for_a_claimed_key() {
1928 let deep = format!("filter{}=1", "[x]".repeat(MAX_DEPTH));
1929 let err = from_query_str::<Args>(&deep).expect_err("depth-capped");
1930 assert!(err.to_string().contains("deeper"), "{err}");
1931 let ok = format!("q=x&junk{}=1", "[x]".repeat(MAX_DEPTH - 2));
1934 assert!(from_query_str::<Args>(&ok).is_ok());
1935 }
1936
1937 #[test]
1938 fn errors_name_the_failing_field_path() {
1939 let err = from_query_str::<Args>("filter[status]=open&filter[limit]=nope")
1940 .expect_err("coercion failure");
1941 assert!(err.to_string().starts_with("filter.limit:"), "{err}");
1942 }
1943
1944 #[test]
1945 fn shape_conflicts_surface_only_when_the_target_claims_the_key() {
1946 let err = from_query_str::<Args>("filter=flat&filter[status]=open")
1947 .expect_err("claimed conflicting key");
1948 assert!(err.to_string().contains("used as both"), "{err}");
1949 }
1950
1951 #[test]
1952 fn untyped_targets_decode_through_deserialize_any() {
1953 let out: serde_json::Value =
1954 from_query_str("q=foo&tags=a&tags=b&filter[status]=open").expect("decodes");
1955 assert_eq!(out["q"], "foo");
1956 assert_eq!(out["tags"], serde_json::json!(["a", "b"]));
1957 assert_eq!(out["filter"]["status"], "open");
1958 }
1959
1960 #[test]
1961 fn map_targets_keep_working() {
1962 let out: std::collections::HashMap<String, String> =
1963 from_query_str("a=1&b=2").expect("decodes");
1964 assert_eq!(out.get("a").map(String::as_str), Some("1"));
1965 assert_eq!(out.get("b").map(String::as_str), Some("2"));
1966 }
1967
1968 #[test]
1969 fn pair_sequence_targets_keep_working() {
1970 let out: Vec<(String, String)> = from_query_str("a=1&a=2&b=3").expect("decodes");
1972 assert_eq!(
1973 out,
1974 vec![
1975 ("a".to_owned(), "1".to_owned()),
1976 ("a".to_owned(), "2".to_owned()),
1977 ("b".to_owned(), "3".to_owned()),
1978 ]
1979 );
1980 }
1981
1982 #[test]
1983 fn unit_enum_variants_decode_from_a_bare_value() {
1984 #[derive(Debug, Deserialize, PartialEq)]
1985 #[serde(rename_all = "lowercase")]
1986 enum Sort {
1987 Asc,
1988 Desc,
1989 }
1990 #[derive(Debug, Deserialize)]
1991 struct Sorted {
1992 sort: Sort,
1993 }
1994 let out: Sorted = from_query_str("sort=desc").expect("decodes");
1995 assert_eq!(out.sort, Sort::Desc);
1996 }
1997
1998 #[test]
1999 fn unknown_keys_are_ignored_including_nested_ones() {
2000 let out = args("q=ok&unknown[deep][deeper]=1&other=2");
2001 assert_eq!(out.q.as_deref(), Some("ok"));
2002 }
2003
2004 #[test]
2005 fn a_missing_required_field_still_fails() {
2006 assert!(from_query_str::<Filter>("limit=5").is_err());
2007 }
2008
2009 #[test]
2010 fn serde_renames_are_honored_because_serde_resolves_the_key() {
2011 #[derive(Debug, Deserialize)]
2012 #[serde(rename_all = "camelCase")]
2013 struct Renamed {
2014 word_count: u32,
2015 }
2016 let out: Renamed = from_query_str("wordCount=7").expect("decodes");
2017 assert_eq!(out.word_count, 7);
2018 }
2019}