1use aws_smithy_types::date_time::Format;
9use aws_smithy_types::primitive::Parse;
10use aws_smithy_types::DateTime;
11use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
12use std::borrow::Cow;
13use std::convert::TryFrom;
14use std::error::Error;
15use std::fmt;
16use std::str::FromStr;
17
18#[derive(Debug)]
20pub struct ParseError {
21 message: Cow<'static, str>,
22 source: Option<Box<dyn Error + Send + Sync + 'static>>,
23}
24
25impl ParseError {
26 pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
28 Self {
29 message: message.into(),
30 source: None,
31 }
32 }
33
34 pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
36 Self {
37 source: Some(source.into()),
38 ..self
39 }
40 }
41}
42
43impl fmt::Display for ParseError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "output failed to parse in headers: {}", self.message)
46 }
47}
48
49impl Error for ParseError {
50 fn source(&self) -> Option<&(dyn Error + 'static)> {
51 self.source.as_ref().map(|err| err.as_ref() as _)
52 }
53}
54
55pub fn many_dates<'a>(
60 values: impl Iterator<Item = &'a str>,
61 format: Format,
62) -> Result<Vec<DateTime>, ParseError> {
63 let mut out = vec![];
64 for header in values {
65 let mut header = header;
66 while !header.is_empty() {
67 let (v, next) = DateTime::read(header, format, ',').map_err(|err| {
68 ParseError::new(format!("header could not be parsed as date: {}", err))
69 })?;
70 out.push(v);
71 header = next;
72 }
73 }
74 Ok(out)
75}
76
77pub fn headers_for_prefix<'a>(
80 header_names: impl Iterator<Item = &'a str>,
81 key: &'a str,
82) -> impl Iterator<Item = (&'a str, &'a str)> {
83 let lower_key = key.to_ascii_lowercase();
84 header_names
85 .filter(move |k| k.starts_with(&lower_key))
86 .map(move |k| (&k[key.len()..], k))
87}
88
89pub fn read_many_from_str<'a, T: FromStr>(
91 values: impl Iterator<Item = &'a str>,
92) -> Result<Vec<T>, ParseError>
93where
94 T::Err: Error + Send + Sync + 'static,
95{
96 read_many(values, |v: &str| {
97 v.parse().map_err(|err| {
98 ParseError::new("failed during `FromString` conversion").with_source(err)
99 })
100 })
101}
102
103pub fn read_many_primitive<'a, T: Parse>(
105 values: impl Iterator<Item = &'a str>,
106) -> Result<Vec<T>, ParseError> {
107 read_many(values, |v: &str| {
108 T::parse_smithy_primitive(v)
109 .map_err(|err| ParseError::new("failed reading a list of primitives").with_source(err))
110 })
111}
112
113fn read_many<'a, T>(
115 values: impl Iterator<Item = &'a str>,
116 f: impl Fn(&str) -> Result<T, ParseError>,
117) -> Result<Vec<T>, ParseError> {
118 let mut out = vec![];
119 for header in values {
120 let mut header = header.as_bytes();
121 while !header.is_empty() {
122 let (v, next) = read_one(header, &f)?;
123 out.push(v);
124 header = next;
125 }
126 }
127 Ok(out)
128}
129
130pub fn one_or_none<'a, T: FromStr>(
134 mut values: impl Iterator<Item = &'a str>,
135) -> Result<Option<T>, ParseError>
136where
137 T::Err: Error + Send + Sync + 'static,
138{
139 let first = match values.next() {
140 Some(v) => v,
141 None => return Ok(None),
142 };
143 match values.next() {
144 None => T::from_str(first.trim())
145 .map_err(|err| ParseError::new("failed to parse string").with_source(err))
146 .map(Some),
147 Some(_) => Err(ParseError::new(
148 "expected a single value but found multiple",
149 )),
150 }
151}
152
153pub fn set_request_header_if_absent<V>(
155 request: http_02x::request::Builder,
156 key: HeaderName,
157 value: V,
158) -> http_02x::request::Builder
159where
160 HeaderValue: TryFrom<V>,
161 <HeaderValue as TryFrom<V>>::Error: Into<http_02x::Error>,
162{
163 if !request
164 .headers_ref()
165 .map(|map| map.contains_key(&key))
166 .unwrap_or(false)
167 {
168 request.header(key, value)
169 } else {
170 request
171 }
172}
173
174pub fn set_response_header_if_absent<V>(
176 response: http_02x::response::Builder,
177 key: HeaderName,
178 value: V,
179) -> http_02x::response::Builder
180where
181 HeaderValue: TryFrom<V>,
182 <HeaderValue as TryFrom<V>>::Error: Into<http_02x::Error>,
183{
184 if !response
185 .headers_ref()
186 .map(|map| map.contains_key(&key))
187 .unwrap_or(false)
188 {
189 response.header(key, value)
190 } else {
191 response
192 }
193}
194
195mod parse_multi_header {
199 use super::ParseError;
200 use std::borrow::Cow;
201
202 fn trim(s: Cow<'_, str>) -> Cow<'_, str> {
203 match s {
204 Cow::Owned(s) => Cow::Owned(s.trim().into()),
205 Cow::Borrowed(s) => Cow::Borrowed(s.trim()),
206 }
207 }
208
209 fn replace<'a>(value: Cow<'a, str>, pattern: &str, replacement: &str) -> Cow<'a, str> {
210 if value.contains(pattern) {
211 Cow::Owned(value.replace(pattern, replacement))
212 } else {
213 value
214 }
215 }
216
217 pub(crate) fn read_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
221 for (index, &byte) in input.iter().enumerate() {
222 let current_slice = &input[index..];
223 match byte {
224 b' ' | b'\t' => { }
225 b'"' => return read_quoted_value(¤t_slice[1..]),
226 _ => {
227 let (value, rest) = read_unquoted_value(current_slice)?;
228 return Ok((trim(value), rest));
229 }
230 }
231 }
232
233 Ok((Cow::Borrowed(""), &[]))
235 }
236
237 fn read_unquoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
238 let next_delim = input.iter().position(|&b| b == b',').unwrap_or(input.len());
239 let (first, next) = input.split_at(next_delim);
240 let first = std::str::from_utf8(first)
241 .map_err(|_| ParseError::new("header was not valid utf-8"))?;
242 Ok((Cow::Borrowed(first), then_comma(next).unwrap()))
243 }
244
245 fn read_quoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
248 for index in 0..input.len() {
249 match input[index] {
250 b'"' if index == 0 || input[index - 1] != b'\\' => {
251 let mut inner = Cow::Borrowed(
252 std::str::from_utf8(&input[0..index])
253 .map_err(|_| ParseError::new("header was not valid utf-8"))?,
254 );
255 inner = replace(inner, "\\\"", "\"");
256 inner = replace(inner, "\\\\", "\\");
257 let rest = then_comma(&input[(index + 1)..])?;
258 return Ok((inner, rest));
259 }
260 _ => {}
261 }
262 }
263 Err(ParseError::new(
264 "header value had quoted value without end quote",
265 ))
266 }
267
268 fn then_comma(s: &[u8]) -> Result<&[u8], ParseError> {
269 if s.is_empty() {
270 Ok(s)
271 } else if s.starts_with(b",") {
272 Ok(&s[1..])
273 } else {
274 Err(ParseError::new("expected delimiter `,`"))
275 }
276 }
277}
278
279fn read_one<'a, T>(
281 s: &'a [u8],
282 f: &impl Fn(&str) -> Result<T, ParseError>,
283) -> Result<(T, &'a [u8]), ParseError> {
284 let (value, rest) = parse_multi_header::read_value(s)?;
285 Ok((f(&value)?, rest))
286}
287
288pub fn quote_header_value<'a>(value: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
290 let value = value.into();
291 if value.trim().len() != value.len()
292 || value.contains('"')
293 || value.contains(',')
294 || value.contains('(')
295 || value.contains(')')
296 {
297 Cow::Owned(format!(
298 "\"{}\"",
299 value.replace('\\', "\\\\").replace('"', "\\\"")
300 ))
301 } else {
302 value
303 }
304}
305
306pub fn append_merge_header_maps(
309 mut lhs: HeaderMap<HeaderValue>,
310 rhs: HeaderMap<HeaderValue>,
311) -> HeaderMap<HeaderValue> {
312 let mut last_header_name_seen = None;
313 for (header_name, header_value) in rhs.into_iter() {
314 match (&mut last_header_name_seen, header_name) {
319 (_, Some(header_name)) => {
320 lhs.append(header_name.clone(), header_value);
321 last_header_name_seen = Some(header_name);
322 }
323 (Some(header_name), None) => {
324 lhs.append(header_name.clone(), header_value);
325 }
326 (None, None) => unreachable!(),
327 };
328 }
329
330 lhs
331}
332
333#[cfg(test)]
334mod test {
335 use super::quote_header_value;
336 use crate::header::{
337 append_merge_header_maps, headers_for_prefix, many_dates, read_many_from_str,
338 read_many_primitive, set_request_header_if_absent, set_response_header_if_absent,
339 ParseError,
340 };
341 use aws_smithy_runtime_api::http::Request;
342 use aws_smithy_types::error::display::DisplayErrorContext;
343 use aws_smithy_types::{date_time::Format, DateTime};
344 use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
345 use std::collections::HashMap;
346
347 #[test]
348 fn put_on_request_if_absent() {
349 let builder = http_02x::Request::builder().header("foo", "bar");
350 let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
351 let builder =
352 set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
353 let req = builder.body(()).expect("valid request");
354 assert_eq!(
355 req.headers().get_all("foo").iter().collect::<Vec<_>>(),
356 vec!["bar"]
357 );
358 assert_eq!(
359 req.headers().get_all("other").iter().collect::<Vec<_>>(),
360 vec!["value"]
361 );
362 }
363
364 #[test]
365 fn put_on_response_if_absent() {
366 let builder = http_02x::Response::builder().header("foo", "bar");
367 let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
368 let builder =
369 set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
370 let response = builder.body(()).expect("valid response");
371 assert_eq!(
372 response.headers().get_all("foo").iter().collect::<Vec<_>>(),
373 vec!["bar"]
374 );
375 assert_eq!(
376 response
377 .headers()
378 .get_all("other")
379 .iter()
380 .collect::<Vec<_>>(),
381 vec!["value"]
382 );
383 }
384
385 #[test]
386 fn parse_floats() {
387 let test_request = http_02x::Request::builder()
388 .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
389 .header("X-Float-Error", "notafloat")
390 .body(())
391 .unwrap();
392 assert_eq!(
393 read_many_primitive::<f32>(
394 test_request
395 .headers()
396 .get_all("X-Float-Multi")
397 .iter()
398 .map(|v| v.to_str().unwrap())
399 )
400 .expect("valid"),
401 vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
402 );
403 let message = format!(
404 "{}",
405 DisplayErrorContext(
406 read_many_primitive::<f32>(
407 test_request
408 .headers()
409 .get_all("X-Float-Error")
410 .iter()
411 .map(|v| v.to_str().unwrap())
412 )
413 .expect_err("invalid")
414 )
415 );
416 let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
417 assert!(
418 message.starts_with(expected),
419 "expected '{message}' to start with '{expected}'"
420 );
421 }
422
423 #[test]
424 fn test_many_dates() {
425 let test_request = http_02x::Request::builder()
426 .header("Empty", "")
427 .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
428 .header(
429 "MultipleHttpDates",
430 "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
431 )
432 .header("SingleEpochSeconds", "1234.5678")
433 .header("MultipleEpochSeconds", "1234.5678,9012.3456")
434 .body(())
435 .unwrap();
436 let read = |name: &str, format: Format| {
437 many_dates(
438 test_request
439 .headers()
440 .get_all(name)
441 .iter()
442 .map(|v| v.to_str().unwrap()),
443 format,
444 )
445 };
446 let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
447 assert_eq!(
448 read_valid("Empty", Format::DateTime),
449 Vec::<DateTime>::new()
450 );
451 assert_eq!(
452 read_valid("SingleHttpDate", Format::HttpDate),
453 vec![DateTime::from_secs_and_nanos(1445412480, 0)]
454 );
455 assert_eq!(
456 read_valid("MultipleHttpDates", Format::HttpDate),
457 vec![
458 DateTime::from_secs_and_nanos(1445412480, 0),
459 DateTime::from_secs_and_nanos(1445498880, 0)
460 ]
461 );
462 assert_eq!(
463 read_valid("SingleEpochSeconds", Format::EpochSeconds),
464 vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
465 );
466 assert_eq!(
467 read_valid("MultipleEpochSeconds", Format::EpochSeconds),
468 vec![
469 DateTime::from_secs_and_nanos(1234, 567_800_000),
470 DateTime::from_secs_and_nanos(9012, 345_600_000)
471 ]
472 );
473 }
474
475 #[test]
476 fn read_many_strings() {
477 let test_request = http_02x::Request::builder()
478 .header("Empty", "")
479 .header("Foo", " foo")
480 .header("FooTrailing", "foo ")
481 .header("FooInQuotes", "\" foo \"")
482 .header("CommaInQuotes", "\"foo,bar\",baz")
483 .header("CommaInQuotesTrailing", "\"foo,bar\",baz ")
484 .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
485 .header(
486 "QuoteInQuotesWithSpaces",
487 "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
488 )
489 .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
490 .header("EmptyQuotes", "\"\",baz")
491 .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
492 .body(())
493 .unwrap();
494 let read = |name: &str| {
495 read_many_from_str::<String>(
496 test_request
497 .headers()
498 .get_all(name)
499 .iter()
500 .map(|v| v.to_str().unwrap()),
501 )
502 };
503 let read_valid = |name: &str| read(name).expect("valid");
504 assert_eq!(read_valid("Empty"), Vec::<String>::new());
505 assert_eq!(read_valid("Foo"), vec!["foo"]);
506 assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
507 assert_eq!(read_valid("FooInQuotes"), vec![" foo "]);
508 assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
509 assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
510 assert_eq!(
511 read_valid("QuoteInQuotes"),
512 vec!["foo\",bar", "\"asdf\"", "baz"]
513 );
514 assert_eq!(
515 read_valid("QuoteInQuotesWithSpaces"),
516 vec!["foo\",bar", "\"asdf\"", "baz"]
517 );
518 assert!(read("JunkFollowingQuotes").is_err());
519 assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
520 assert_eq!(
521 read_valid("EscapedSlashesInQuotes"),
522 vec!["foo", "(foo\\bar)"]
523 );
524 }
525
526 #[test]
527 fn read_many_bools() {
528 let test_request = http_02x::Request::builder()
529 .header("X-Bool-Multi", "true,false")
530 .header("X-Bool-Multi", "true")
531 .header("X-Bool", "true")
532 .header("X-Bool-Invalid", "truth,falsy")
533 .header("X-Bool-Single", "true,false,true,true")
534 .header("X-Bool-Quoted", "true,\"false\",true,true")
535 .body(())
536 .unwrap();
537 assert_eq!(
538 read_many_primitive::<bool>(
539 test_request
540 .headers()
541 .get_all("X-Bool-Multi")
542 .iter()
543 .map(|v| v.to_str().unwrap())
544 )
545 .expect("valid"),
546 vec![true, false, true]
547 );
548
549 assert_eq!(
550 read_many_primitive::<bool>(
551 test_request
552 .headers()
553 .get_all("X-Bool")
554 .iter()
555 .map(|v| v.to_str().unwrap())
556 )
557 .unwrap(),
558 vec![true]
559 );
560 assert_eq!(
561 read_many_primitive::<bool>(
562 test_request
563 .headers()
564 .get_all("X-Bool-Single")
565 .iter()
566 .map(|v| v.to_str().unwrap())
567 )
568 .unwrap(),
569 vec![true, false, true, true]
570 );
571 assert_eq!(
572 read_many_primitive::<bool>(
573 test_request
574 .headers()
575 .get_all("X-Bool-Quoted")
576 .iter()
577 .map(|v| v.to_str().unwrap())
578 )
579 .unwrap(),
580 vec![true, false, true, true]
581 );
582 read_many_primitive::<bool>(
583 test_request
584 .headers()
585 .get_all("X-Bool-Invalid")
586 .iter()
587 .map(|v| v.to_str().unwrap()),
588 )
589 .expect_err("invalid");
590 }
591
592 #[test]
593 fn check_read_many_i16() {
594 let test_request = http_02x::Request::builder()
595 .header("X-Multi", "123,456")
596 .header("X-Multi", "789")
597 .header("X-Num", "777")
598 .header("X-Num-Invalid", "12ef3")
599 .header("X-Num-Single", "1,2,3,-4,5")
600 .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
601 .body(())
602 .unwrap();
603 assert_eq!(
604 read_many_primitive::<i16>(
605 test_request
606 .headers()
607 .get_all("X-Multi")
608 .iter()
609 .map(|v| v.to_str().unwrap())
610 )
611 .expect("valid"),
612 vec![123, 456, 789]
613 );
614
615 assert_eq!(
616 read_many_primitive::<i16>(
617 test_request
618 .headers()
619 .get_all("X-Num")
620 .iter()
621 .map(|v| v.to_str().unwrap())
622 )
623 .unwrap(),
624 vec![777]
625 );
626 assert_eq!(
627 read_many_primitive::<i16>(
628 test_request
629 .headers()
630 .get_all("X-Num-Single")
631 .iter()
632 .map(|v| v.to_str().unwrap())
633 )
634 .unwrap(),
635 vec![1, 2, 3, -4, 5]
636 );
637 assert_eq!(
638 read_many_primitive::<i16>(
639 test_request
640 .headers()
641 .get_all("X-Num-Quoted")
642 .iter()
643 .map(|v| v.to_str().unwrap())
644 )
645 .unwrap(),
646 vec![1, 2, 3, -4, 5]
647 );
648 read_many_primitive::<i16>(
649 test_request
650 .headers()
651 .get_all("X-Num-Invalid")
652 .iter()
653 .map(|v| v.to_str().unwrap()),
654 )
655 .expect_err("invalid");
656 }
657
658 #[test]
659 fn test_prefix_headers() {
660 let test_request = Request::try_from(
661 http_02x::Request::builder()
662 .header("X-Prefix-A", "123,456")
663 .header("X-Prefix-B", "789")
664 .header("X-Prefix-C", "777")
665 .header("X-Prefix-C", "777")
666 .body(())
667 .unwrap(),
668 )
669 .unwrap();
670 let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
671 headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
672 .map(|(key, header_name)| {
673 let values = test_request.headers().get_all(header_name);
674 read_many_primitive(values).map(|v| (key.to_string(), v))
675 })
676 .collect();
677 let resp = resp.expect("valid");
678 assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
679 }
680
681 #[test]
682 fn test_quote_header_value() {
683 assert_eq!("", "e_header_value(""));
684 assert_eq!("foo", "e_header_value("foo"));
685 assert_eq!("\" foo\"", "e_header_value(" foo"));
686 assert_eq!("foo bar", "e_header_value("foo bar"));
687 assert_eq!("\"foo,bar\"", "e_header_value("foo,bar"));
688 assert_eq!("\",\"", "e_header_value(","));
689 assert_eq!("\"\\\"foo\\\"\"", "e_header_value("\"foo\""));
690 assert_eq!("\"\\\"f\\\\oo\\\"\"", "e_header_value("\"f\\oo\""));
691 assert_eq!("\"(\"", "e_header_value("("));
692 assert_eq!("\")\"", "e_header_value(")"));
693 }
694
695 #[test]
696 fn test_append_merge_header_maps_with_shared_key() {
697 let header_name = HeaderName::from_static("some_key");
698 let left_header_value = HeaderValue::from_static("lhs value");
699 let right_header_value = HeaderValue::from_static("rhs value");
700
701 let mut left_hand_side_headers = HeaderMap::new();
702 left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
703
704 let mut right_hand_side_headers = HeaderMap::new();
705 right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
706
707 let merged_header_map =
708 append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
709 let actual_merged_values: Vec<_> =
710 merged_header_map.get_all(header_name).into_iter().collect();
711
712 let expected_merged_values = vec![left_header_value, right_header_value];
713
714 assert_eq!(actual_merged_values, expected_merged_values);
715 }
716
717 #[test]
718 fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
719 let header_name = HeaderName::from_static("some_key");
720 let left_header_value_1 = HeaderValue::from_static("lhs value 1");
721 let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
722 let right_header_value = HeaderValue::from_static("rhs value");
723
724 let mut left_hand_side_headers = HeaderMap::new();
725 left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
726 left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
727
728 let mut right_hand_side_headers = HeaderMap::new();
729 right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
730
731 let merged_header_map =
732 append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
733 let actual_merged_values: Vec<_> =
734 merged_header_map.get_all(header_name).into_iter().collect();
735
736 let expected_merged_values =
737 vec![left_header_value_1, left_header_value_2, right_header_value];
738
739 assert_eq!(actual_merged_values, expected_merged_values);
740 }
741
742 #[test]
743 fn test_append_merge_header_maps_with_empty_left_hand_map() {
744 let header_name = HeaderName::from_static("some_key");
745 let right_header_value_1 = HeaderValue::from_static("rhs value 1");
746 let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
747
748 let left_hand_side_headers = HeaderMap::new();
749
750 let mut right_hand_side_headers = HeaderMap::new();
751 right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
752 right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
753
754 let merged_header_map =
755 append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
756 let actual_merged_values: Vec<_> =
757 merged_header_map.get_all(header_name).into_iter().collect();
758
759 let expected_merged_values = vec![right_header_value_1, right_header_value_2];
760
761 assert_eq!(actual_merged_values, expected_merged_values);
762 }
763}