1use cookie::Cookie;
2use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Object, Value};
3use url::form_urlencoded;
4
5use crate::processor::ProcessValue;
6use crate::protocol::{JsonLenientString, LenientString, PairList};
7
8type CookieEntry = Annotated<(Annotated<String>, Annotated<String>)>;
9
10#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
12pub struct Cookies(pub PairList<(Annotated<String>, Annotated<String>)>);
13
14impl Cookies {
15 pub fn parse(string: &str) -> Result<Self, Error> {
16 let pairs: Result<_, _> = Self::iter_cookies(string).collect();
17 pairs.map(Cookies)
18 }
19
20 fn iter_cookies(string: &str) -> impl Iterator<Item = Result<CookieEntry, Error>> + '_ {
21 string
22 .split(';')
23 .filter(|cookie| !cookie.trim().is_empty())
24 .map(Cookies::parse_cookie)
25 }
26
27 fn parse_cookie(string: &str) -> Result<CookieEntry, Error> {
28 match Cookie::parse_encoded(string) {
29 Ok(cookie) => Ok(Annotated::from((
30 cookie.name().to_owned().into(),
31 cookie.value().to_owned().into(),
32 ))),
33 Err(error) => Err(Error::invalid(error)),
34 }
35 }
36}
37
38impl std::ops::Deref for Cookies {
39 type Target = PairList<(Annotated<String>, Annotated<String>)>;
40
41 fn deref(&self) -> &Self::Target {
42 &self.0
43 }
44}
45
46impl std::ops::DerefMut for Cookies {
47 fn deref_mut(&mut self) -> &mut Self::Target {
48 &mut self.0
49 }
50}
51
52impl FromValue for Cookies {
53 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
54 match value {
55 Annotated(Some(Value::String(value)), mut meta) => {
56 let mut cookies = Vec::new();
57 for result in Cookies::iter_cookies(&value) {
58 match result {
59 Ok(cookie) => cookies.push(cookie),
60 Err(error) => meta.add_error(error),
61 }
62 }
63
64 if meta.has_errors() && meta.original_value().is_none() {
65 meta.set_original_value(Some(value));
66 }
67
68 Annotated(Some(Cookies(PairList(cookies))), meta)
69 }
70 annotated @ Annotated(Some(Value::Object(_)), _)
71 | annotated @ Annotated(Some(Value::Array(_)), _) => {
72 PairList::from_value(annotated).map_value(Cookies)
73 }
74 Annotated(None, meta) => Annotated(None, meta),
75 Annotated(Some(value), mut meta) => {
76 meta.add_error(Error::expected("cookies"));
77 meta.set_original_value(Some(value));
78 Annotated(None, meta)
79 }
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Empty, IntoValue, ProcessValue)]
86#[metastructure(process_func = "process_header_name")]
87pub struct HeaderName(String);
88
89impl HeaderName {
90 pub fn new<S: AsRef<str>>(name: S) -> Self {
92 let name = name.as_ref();
93 let mut normalized = String::with_capacity(name.len());
94
95 name.chars().fold(true, |uppercase, c| {
96 if uppercase {
97 normalized.extend(c.to_uppercase());
98 } else {
99 normalized.push(c); }
101 c == '-'
102 });
103
104 HeaderName(normalized)
105 }
106
107 pub fn as_str(&self) -> &str {
109 &self.0
110 }
111
112 pub fn into_inner(self) -> String {
114 self.0
115 }
116}
117
118impl AsRef<str> for HeaderName {
119 fn as_ref(&self) -> &str {
120 &self.0
121 }
122}
123
124impl std::ops::Deref for HeaderName {
125 type Target = String;
126
127 fn deref(&self) -> &Self::Target {
128 &self.0
129 }
130}
131
132impl std::ops::DerefMut for HeaderName {
133 fn deref_mut(&mut self) -> &mut Self::Target {
134 &mut self.0
135 }
136}
137
138impl From<String> for HeaderName {
139 fn from(value: String) -> Self {
140 HeaderName::new(value)
141 }
142}
143
144impl From<&'_ str> for HeaderName {
145 fn from(value: &str) -> Self {
146 HeaderName::new(value)
147 }
148}
149
150impl FromValue for HeaderName {
151 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
152 String::from_value(value).map_value(HeaderName::new)
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Empty, IntoValue, ProcessValue)]
158pub struct HeaderValue(String);
159
160impl HeaderValue {
161 pub fn new<S: AsRef<str>>(value: S) -> Self {
162 HeaderValue(value.as_ref().to_owned())
163 }
164}
165
166impl AsRef<str> for HeaderValue {
167 fn as_ref(&self) -> &str {
168 &self.0
169 }
170}
171
172impl std::ops::Deref for HeaderValue {
173 type Target = String;
174
175 fn deref(&self) -> &Self::Target {
176 &self.0
177 }
178}
179
180impl std::ops::DerefMut for HeaderValue {
181 fn deref_mut(&mut self) -> &mut Self::Target {
182 &mut self.0
183 }
184}
185
186impl From<String> for HeaderValue {
187 fn from(value: String) -> Self {
188 HeaderValue::new(value)
189 }
190}
191
192impl From<&'_ str> for HeaderValue {
193 fn from(value: &str) -> Self {
194 HeaderValue::new(value)
195 }
196}
197
198impl FromValue for HeaderValue {
199 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
200 match value {
201 Annotated(Some(Value::Array(array)), mut meta) => {
202 let mut header_value = String::new();
203 for array_value in array {
204 let array_value = LenientString::from_value(array_value);
205 for error in array_value.meta().iter_errors() {
206 meta.add_error(error.clone());
207 }
208
209 if let Some(string) = array_value.value() {
210 if !header_value.is_empty() {
211 header_value.push(',');
212 }
213
214 header_value.push_str(string);
215 }
216 }
217
218 Annotated(Some(HeaderValue::new(header_value)), meta)
219 }
220 annotated => LenientString::from_value(annotated).map_value(HeaderValue::new),
221 }
222 }
223}
224
225#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
227pub struct Headers(pub PairList<(Annotated<HeaderName>, Annotated<HeaderValue>)>);
228
229impl Headers {
230 pub fn get_header(&self, key: &str) -> Option<&str> {
231 for item in self.iter() {
232 if let Some((k, v)) = item.value()
233 && k.as_str() == Some(key)
234 {
235 return v.as_str();
236 }
237 }
238
239 None
240 }
241}
242
243impl std::ops::Deref for Headers {
244 type Target = PairList<(Annotated<HeaderName>, Annotated<HeaderValue>)>;
245
246 fn deref(&self) -> &Self::Target {
247 &self.0
248 }
249}
250
251impl std::ops::DerefMut for Headers {
252 fn deref_mut(&mut self) -> &mut Self::Target {
253 &mut self.0
254 }
255}
256
257impl FromValue for Headers {
258 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
259 let should_sort = matches!(value.value(), Some(Value::Object(_)));
261
262 type HeaderTuple = (Annotated<HeaderName>, Annotated<HeaderValue>);
263 PairList::<HeaderTuple>::from_value(value).map_value(|mut pair_list| {
264 if should_sort {
265 pair_list.sort_unstable_by(|a, b| {
266 a.value()
267 .map(|x| x.0.value())
268 .cmp(&b.value().map(|x| x.0.value()))
269 });
270 }
271
272 Headers(pair_list)
273 })
274 }
275}
276
277#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
279pub struct Query(pub PairList<(Annotated<String>, Annotated<JsonLenientString>)>);
280
281impl Query {
282 pub fn parse(mut string: &str) -> Self {
283 if string.starts_with('?') {
284 string = &string[1..];
285 }
286
287 form_urlencoded::parse(string.as_bytes()).collect()
288 }
289
290 pub fn to_query_string(&self) -> Option<String> {
294 let mut serializer = form_urlencoded::Serializer::new(String::new());
295 let mut has_pairs = false;
296 for pair in self.iter() {
297 if let Some((key, value)) = pair.value()
298 && let (Some(k), Some(v)) = (key.as_str(), value.as_str())
299 {
300 serializer.append_pair(k, v);
301 has_pairs = true;
302 }
303 }
304 has_pairs.then(|| serializer.finish())
305 }
306}
307
308impl std::ops::Deref for Query {
309 type Target = PairList<(Annotated<String>, Annotated<JsonLenientString>)>;
310
311 fn deref(&self) -> &Self::Target {
312 &self.0
313 }
314}
315
316impl std::ops::DerefMut for Query {
317 fn deref_mut(&mut self) -> &mut Self::Target {
318 &mut self.0
319 }
320}
321
322impl<K, V> FromIterator<(K, V)> for Query
323where
324 K: Into<String>,
325 V: Into<String>,
326{
327 fn from_iter<T>(iter: T) -> Self
328 where
329 T: IntoIterator<Item = (K, V)>,
330 {
331 let pairs = iter.into_iter().map(|(key, value)| {
332 Annotated::new((
333 Annotated::new(key.into()),
334 Annotated::new(value.into().into()),
335 ))
336 });
337
338 Query(pairs.collect())
339 }
340}
341
342impl FromValue for Query {
343 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
344 match value {
345 Annotated(Some(Value::String(v)), meta) => Annotated(Some(Query::parse(&v)), meta),
346 annotated @ Annotated(Some(Value::Object(_)), _)
347 | annotated @ Annotated(Some(Value::Array(_)), _) => {
348 PairList::from_value(annotated).map_value(Query)
349 }
350 Annotated(None, meta) => Annotated(None, meta),
351 Annotated(Some(value), mut meta) => {
352 meta.add_error(Error::expected("a query string or map"));
353 meta.set_original_value(Some(value));
354 Annotated(None, meta)
355 }
356 }
357 }
358}
359
360#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
419#[metastructure(process_func = "process_request", value_type = "Request")]
420pub struct Request {
421 #[metastructure(max_chars = 256, max_chars_allowance = 40, pii = "maybe")]
425 pub url: Annotated<String>,
426
427 pub method: Annotated<String>,
429
430 pub protocol: Annotated<String>,
432
433 #[metastructure(pii = "true", max_depth = 7, max_bytes = 8192)]
438 pub data: Annotated<Value>,
439
440 #[metastructure(pii = "true", max_depth = 3, max_bytes = 1024)]
447 #[metastructure(skip_serialization = "empty")]
448 pub query_string: Annotated<Query>,
449
450 #[metastructure(pii = "true", max_chars = 1024, max_chars_allowance = 100)]
452 #[metastructure(skip_serialization = "empty")]
453 pub fragment: Annotated<String>,
454
455 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
459 #[metastructure(skip_serialization = "empty")]
460 pub cookies: Annotated<Cookies>,
461
462 #[metastructure(pii = "true", max_depth = 7, max_bytes = 8192)]
467 #[metastructure(skip_serialization = "empty")]
468 pub headers: Annotated<Headers>,
469
470 pub body_size: Annotated<u64>,
472
473 #[metastructure(pii = "true", max_depth = 7, max_bytes = 8192)]
480 #[metastructure(skip_serialization = "empty")]
481 pub env: Annotated<Object<Value>>,
482
483 #[metastructure(skip_serialization = "empty")]
485 pub inferred_content_type: Annotated<String>,
486
487 pub api_target: Annotated<String>,
495
496 #[metastructure(additional_properties, pii = "true")]
498 pub other: Object<Value>,
499}
500
501#[cfg(test)]
502mod tests {
503 use similar_asserts::assert_eq;
504
505 use super::*;
506
507 #[test]
508 fn test_header_normalization() {
509 let json = r#"{
510 "-other-": "header",
511 "accept": "application/json",
512 "WWW-Authenticate": "basic",
513 "x-sentry": "version=8"
514}"#;
515
516 let headers = vec![
517 Annotated::new((
518 Annotated::new("-Other-".to_owned().into()),
519 Annotated::new("header".to_owned().into()),
520 )),
521 Annotated::new((
522 Annotated::new("Accept".to_owned().into()),
523 Annotated::new("application/json".to_owned().into()),
524 )),
525 Annotated::new((
526 Annotated::new("WWW-Authenticate".to_owned().into()),
527 Annotated::new("basic".to_owned().into()),
528 )),
529 Annotated::new((
530 Annotated::new("X-Sentry".to_owned().into()),
531 Annotated::new("version=8".to_owned().into()),
532 )),
533 ];
534
535 let headers = Annotated::new(Headers(PairList(headers)));
536 assert_eq!(headers, Annotated::from_json(json).unwrap());
537 }
538
539 #[test]
540 fn test_header_from_sequence() {
541 let json = r#"[
542 ["accept", "application/json"]
543]"#;
544
545 let headers = vec![Annotated::new((
546 Annotated::new("Accept".to_owned().into()),
547 Annotated::new("application/json".to_owned().into()),
548 ))];
549
550 let headers = Annotated::new(Headers(PairList(headers)));
551 assert_eq!(headers, Annotated::from_json(json).unwrap());
552
553 let json = r#"[
554 ["accept", "application/json"],
555 [1, 2],
556 ["a", "b", "c"],
557 23
558]"#;
559 let headers = Annotated::<Headers>::from_json(json).unwrap();
560 #[derive(Debug, Empty, IntoValue)]
561 pub struct Container {
562 headers: Annotated<Headers>,
563 }
564 assert_eq!(
565 Annotated::new(Container { headers })
566 .to_json_pretty()
567 .unwrap(),
568 r#"{
569 "headers": [
570 [
571 "Accept",
572 "application/json"
573 ],
574 [
575 null,
576 "2"
577 ],
578 null,
579 null
580 ],
581 "_meta": {
582 "headers": {
583 "1": {
584 "0": {
585 "": {
586 "err": [
587 [
588 "invalid_data",
589 {
590 "reason": "expected a string"
591 }
592 ]
593 ],
594 "val": 1
595 }
596 }
597 },
598 "2": {
599 "": {
600 "err": [
601 [
602 "invalid_data",
603 {
604 "reason": "expected a tuple"
605 }
606 ]
607 ],
608 "val": [
609 "a",
610 "b",
611 "c"
612 ]
613 }
614 },
615 "3": {
616 "": {
617 "err": [
618 [
619 "invalid_data",
620 {
621 "reason": "expected a tuple"
622 }
623 ]
624 ],
625 "val": 23
626 }
627 }
628 }
629 }
630}"#
631 );
632 }
633
634 #[test]
635 fn test_request_roundtrip() {
636 let json = r#"{
637 "url": "https://google.com/search",
638 "method": "GET",
639 "data": {
640 "some": 1
641 },
642 "query_string": [
643 [
644 "q",
645 "foo"
646 ]
647 ],
648 "fragment": "home",
649 "cookies": [
650 [
651 "GOOGLE",
652 "1"
653 ]
654 ],
655 "headers": [
656 [
657 "Referer",
658 "https://google.com/"
659 ]
660 ],
661 "body_size": 1024,
662 "env": {
663 "REMOTE_ADDR": "213.47.147.207"
664 },
665 "inferred_content_type": "application/json",
666 "api_target": "graphql",
667 "other": "value"
668}"#;
669
670 let request = Annotated::new(Request {
671 url: Annotated::new("https://google.com/search".to_owned()),
672 method: Annotated::new("GET".to_owned()),
673 protocol: Annotated::empty(),
674 data: {
675 let mut map = Object::new();
676 map.insert("some".to_owned(), Annotated::new(Value::I64(1)));
677 Annotated::new(Value::Object(map))
678 },
679 query_string: Annotated::new(Query(
680 vec![Annotated::new((
681 Annotated::new("q".to_owned()),
682 Annotated::new("foo".to_owned().into()),
683 ))]
684 .into(),
685 )),
686 fragment: Annotated::new("home".to_owned()),
687 cookies: Annotated::new(Cookies({
688 PairList(vec![Annotated::new((
689 Annotated::new("GOOGLE".to_owned()),
690 Annotated::new("1".to_owned()),
691 ))])
692 })),
693 headers: Annotated::new(Headers({
694 let headers = vec![Annotated::new((
695 Annotated::new("Referer".to_owned().into()),
696 Annotated::new("https://google.com/".to_owned().into()),
697 ))];
698 PairList(headers)
699 })),
700 body_size: Annotated::new(1024),
701 env: Annotated::new({
702 let mut map = Object::new();
703 map.insert(
704 "REMOTE_ADDR".to_owned(),
705 Annotated::new(Value::String("213.47.147.207".to_owned())),
706 );
707 map
708 }),
709 inferred_content_type: Annotated::new("application/json".to_owned()),
710 api_target: Annotated::new("graphql".to_owned()),
711 other: {
712 let mut map = Object::new();
713 map.insert(
714 "other".to_owned(),
715 Annotated::new(Value::String("value".to_owned())),
716 );
717 map
718 },
719 });
720
721 assert_eq!(request, Annotated::from_json(json).unwrap());
722 assert_eq!(json, request.to_json_pretty().unwrap());
723 }
724
725 #[test]
726 fn test_query_string() {
727 let query = Annotated::new(Query(
728 vec![Annotated::new((
729 Annotated::new("foo".to_owned()),
730 Annotated::new("bar".to_owned().into()),
731 ))]
732 .into(),
733 ));
734 assert_eq!(query, Annotated::from_json("\"foo=bar\"").unwrap());
735 assert_eq!(query, Annotated::from_json("\"?foo=bar\"").unwrap());
736
737 let query = Annotated::new(Query(
738 vec![
739 Annotated::new((
740 Annotated::new("foo".to_owned()),
741 Annotated::new("bar".to_owned().into()),
742 )),
743 Annotated::new((
744 Annotated::new("baz".to_owned()),
745 Annotated::new("42".to_owned().into()),
746 )),
747 ]
748 .into(),
749 ));
750 assert_eq!(query, Annotated::from_json("\"foo=bar&baz=42\"").unwrap());
751 }
752
753 #[test]
754 fn test_query_string_legacy_nested() {
755 let query = Annotated::new(Query(
759 vec![Annotated::new((
760 Annotated::new("foo".to_owned()),
761 Annotated::new("bar".to_owned().into()),
762 ))]
763 .into(),
764 ));
765 assert_eq!(query, Annotated::from_json("\"foo=bar\"").unwrap());
766
767 let query = Annotated::new(Query(
768 vec![
769 Annotated::new((
770 Annotated::new("baz".to_owned()),
771 Annotated::new(r#"{"a":42}"#.to_owned().into()),
772 )),
773 Annotated::new((
774 Annotated::new("foo".to_owned()),
775 Annotated::new("bar".to_owned().into()),
776 )),
777 ]
778 .into(),
779 ));
780 assert_eq!(
781 query,
782 Annotated::from_json(
783 r#"
784 {
785 "foo": "bar",
786 "baz": {"a": 42}
787 }
788 "#
789 )
790 .unwrap()
791 );
792 }
793
794 #[test]
795 fn test_query_invalid() {
796 let query = Annotated::<Query>::from_error(
797 Error::expected("a query string or map"),
798 Some(Value::I64(42)),
799 );
800 assert_eq!(query, Annotated::from_json("42").unwrap());
801 }
802
803 #[test]
804 fn test_cookies_parsing() {
805 let json = "\" PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;\"";
806
807 let map = vec![
808 Annotated::new((
809 Annotated::new("PHPSESSID".to_owned()),
810 Annotated::new("298zf09hf012fh2".to_owned()),
811 )),
812 Annotated::new((
813 Annotated::new("csrftoken".to_owned()),
814 Annotated::new("u32t4o3tb3gg43".to_owned()),
815 )),
816 Annotated::new((
817 Annotated::new("_gat".to_owned()),
818 Annotated::new("1".to_owned()),
819 )),
820 ];
821
822 let cookies = Annotated::new(Cookies(PairList(map)));
823 assert_eq!(cookies, Annotated::from_json(json).unwrap());
824 }
825
826 #[test]
827 fn test_cookies_array() {
828 let input = r#"{"cookies":[["foo","bar"],["invalid", 42],["none",null]]}"#;
829 let output = r#"{"cookies":[["foo","bar"],["invalid",null],["none",null]],"_meta":{"cookies":{"1":{"1":{"":{"err":[["invalid_data",{"reason":"expected a string"}]],"val":42}}}}}}"#;
830
831 let map = vec![
832 Annotated::new((
833 Annotated::new("foo".to_owned()),
834 Annotated::new("bar".to_owned()),
835 )),
836 Annotated::new((
837 Annotated::new("invalid".to_owned()),
838 Annotated::from_error(Error::expected("a string"), Some(Value::I64(42))),
839 )),
840 Annotated::new((Annotated::new("none".to_owned()), Annotated::empty())),
841 ];
842
843 let cookies = Annotated::new(Cookies(PairList(map)));
844 let request = Annotated::new(Request {
845 cookies,
846 ..Default::default()
847 });
848 assert_eq!(request, Annotated::from_json(input).unwrap());
849 assert_eq!(request.to_json().unwrap(), output);
850 }
851
852 #[test]
853 fn test_cookies_object() {
854 let json = r#"{"foo":"bar", "invalid": 42}"#;
855
856 let map = vec![
857 Annotated::new((
858 Annotated::new("foo".to_owned()),
859 Annotated::new("bar".to_owned()),
860 )),
861 Annotated::new((
862 Annotated::new("invalid".to_owned()),
863 Annotated::from_error(Error::expected("a string"), Some(Value::I64(42))),
864 )),
865 ];
866
867 let cookies = Annotated::new(Cookies(PairList(map)));
868 assert_eq!(cookies, Annotated::from_json(json).unwrap());
869 }
870
871 #[test]
872 fn test_cookies_invalid() {
873 let cookies =
874 Annotated::<Cookies>::from_error(Error::expected("cookies"), Some(Value::I64(42)));
875 assert_eq!(cookies, Annotated::from_json("42").unwrap());
876 }
877
878 #[test]
879 fn test_query_to_query_string() {
880 let query = Query(
881 vec![
882 Annotated::new((
883 Annotated::new("foo".to_owned()),
884 Annotated::new("bar".to_owned().into()),
885 )),
886 Annotated::new((
887 Annotated::new("baz".to_owned()),
888 Annotated::new("qux".to_owned().into()),
889 )),
890 ]
891 .into(),
892 );
893
894 assert_eq!(query.to_query_string(), Some("foo=bar&baz=qux".to_owned()));
895 }
896
897 #[test]
898 fn test_query_to_query_string_empty() {
899 let query = Query(PairList(vec![]));
900 assert_eq!(query.to_query_string(), None);
901 }
902
903 #[test]
904 fn test_query_to_query_string_special_chars() {
905 let query = Query(
906 vec![Annotated::new((
907 Annotated::new("q".to_owned()),
908 Annotated::new("hello world&more".to_owned().into()),
909 ))]
910 .into(),
911 );
912
913 assert_eq!(
914 query.to_query_string(),
915 Some("q=hello+world%26more".to_owned())
916 );
917 }
918
919 #[test]
920 fn test_querystring_without_value() {
921 let json = r#""foo=bar&baz""#;
922
923 let query = Annotated::new(Query(
924 vec![
925 Annotated::new((
926 Annotated::new("foo".to_owned()),
927 Annotated::new("bar".to_owned().into()),
928 )),
929 Annotated::new((
930 Annotated::new("baz".to_owned()),
931 Annotated::new("".to_owned().into()),
932 )),
933 ]
934 .into(),
935 ));
936
937 assert_eq!(query, Annotated::from_json(json).unwrap());
938 }
939
940 #[test]
941 fn test_headers_lenient_value() {
942 let input = r#"{
943 "headers": {
944 "X-Foo": "",
945 "X-Bar": 42
946 }
947}"#;
948
949 let output = r#"{
950 "headers": [
951 [
952 "X-Bar",
953 "42"
954 ],
955 [
956 "X-Foo",
957 ""
958 ]
959 ]
960}"#;
961
962 let request = Annotated::new(Request {
963 headers: Annotated::new(Headers(PairList(vec![
964 Annotated::new((
965 Annotated::new("X-Bar".to_owned().into()),
966 Annotated::new("42".to_owned().into()),
967 )),
968 Annotated::new((
969 Annotated::new("X-Foo".to_owned().into()),
970 Annotated::new("".to_owned().into()),
971 )),
972 ]))),
973 ..Default::default()
974 });
975
976 assert_eq!(Annotated::from_json(input).unwrap(), request);
977 assert_eq!(request.to_json_pretty().unwrap(), output);
978 }
979
980 #[test]
981 fn test_headers_multiple_values() {
982 let input = r#"{
983 "headers": {
984 "X-Foo": [""],
985 "X-Bar": [
986 42,
987 "bar",
988 "baz"
989 ]
990 }
991}"#;
992
993 let output = r#"{
994 "headers": [
995 [
996 "X-Bar",
997 "42,bar,baz"
998 ],
999 [
1000 "X-Foo",
1001 ""
1002 ]
1003 ]
1004}"#;
1005
1006 let request = Annotated::new(Request {
1007 headers: Annotated::new(Headers(PairList(vec![
1008 Annotated::new((
1009 Annotated::new("X-Bar".to_owned().into()),
1010 Annotated::new("42,bar,baz".to_owned().into()),
1011 )),
1012 Annotated::new((
1013 Annotated::new("X-Foo".to_owned().into()),
1014 Annotated::new("".to_owned().into()),
1015 )),
1016 ]))),
1017 ..Default::default()
1018 });
1019
1020 assert_eq!(Annotated::from_json(input).unwrap(), request);
1021 assert_eq!(request.to_json_pretty().unwrap(), output);
1022 }
1023}