1use super::binding::HTTP_HEADER_NAME;
24use super::bound_value::{
25 HeaderValues, HttpHeaderValueDeserializer, HttpPrefixHeadersDeserializer,
26 HttpRawPayloadDeserializer, HttpStatusDeserializer,
27};
28use crate::serde::{SerdeError, ShapeDeserializer};
29use crate::{Schema, ShapeType};
30use aws_smithy_runtime_api::http::{Headers, NonUtf8HeaderHandling, Response};
31use aws_smithy_types::config_bag::ConfigBag;
32use aws_smithy_types::error::display::DisplayErrorContext;
33use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
34
35pub(crate) fn non_utf8_policy(cfg: &ConfigBag) -> NonUtf8HeaderHandling {
38 cfg.load::<NonUtf8HeaderHandling>()
39 .cloned()
40 .unwrap_or_default()
41}
42
43#[derive(Clone)]
50pub(crate) enum NonUtf8Policy<'a> {
51 Resolved(NonUtf8HeaderHandling),
53 Deferred(&'a ConfigBag),
55}
56
57impl NonUtf8Policy<'_> {
58 #[cold]
59 fn resolve(&self) -> NonUtf8HeaderHandling {
60 match self {
61 NonUtf8Policy::Resolved(policy) => policy.clone(),
62 NonUtf8Policy::Deferred(cfg) => non_utf8_policy(cfg),
63 }
64 }
65}
66
67impl From<NonUtf8HeaderHandling> for NonUtf8Policy<'_> {
68 fn from(policy: NonUtf8HeaderHandling) -> Self {
69 NonUtf8Policy::Resolved(policy)
70 }
71}
72
73impl<'a> From<&'a ConfigBag> for NonUtf8Policy<'a> {
74 fn from(cfg: &'a ConfigBag) -> Self {
75 NonUtf8Policy::Deferred(cfg)
76 }
77}
78
79pub fn http_output_deserializer<'a, D>(
103 body: D,
104 response: &'a Response,
105 output_schema: &Schema<'_>,
106 cfg: &'a ConfigBag,
107) -> Box<dyn ShapeDeserializer + 'a>
108where
109 D: ShapeDeserializer + 'a,
110{
111 if is_body_only(output_schema) {
112 return Box::new(body);
113 }
114 Box::new(HttpResponseDeserializer::for_output(
115 body,
116 response.headers(),
117 response.status().as_u16(),
118 response.body().bytes(),
119 cfg,
120 ))
121}
122
123pub fn http_error_deserializer<'a, D>(
137 body: D,
138 response: &'a Response,
139 cfg: &'a ConfigBag,
140) -> Box<dyn ShapeDeserializer + 'a>
141where
142 D: ShapeDeserializer + 'a,
143{
144 Box::new(HttpResponseDeserializer::for_error(
145 body,
146 response.headers(),
147 response.status().as_u16(),
148 response.body().bytes(),
149 cfg,
150 ))
151}
152
153fn is_body_only(schema: &Schema<'_>) -> bool {
159 !schema.response_bindings_need_scan()
160 && schema.response_binding_mask() == 0
161 && schema.has_response_body_members()
162}
163
164pub(crate) struct HttpResponseDeserializer<'a, D> {
170 body: D,
171 headers: &'a Headers,
172 status: u16,
173 body_bytes: Option<&'a [u8]>,
178 non_utf8: NonUtf8Policy<'a>,
179 allow_empty_body: bool,
184}
185
186#[derive(PartialEq, Eq)]
189enum Routed {
190 Binding,
192 Payload,
195}
196
197impl<'a, D> HttpResponseDeserializer<'a, D>
198where
199 D: ShapeDeserializer,
200{
201 pub(crate) fn for_output(
203 body: D,
204 headers: &'a Headers,
205 status: u16,
206 body_bytes: Option<&'a [u8]>,
207 non_utf8: impl Into<NonUtf8Policy<'a>>,
208 ) -> Self {
209 Self {
210 body,
211 headers,
212 status,
213 body_bytes,
214 non_utf8: non_utf8.into(),
215 allow_empty_body: false,
216 }
217 }
218
219 pub(crate) fn for_error(
221 body: D,
222 headers: &'a Headers,
223 status: u16,
224 body_bytes: Option<&'a [u8]>,
225 non_utf8: impl Into<NonUtf8Policy<'a>>,
226 ) -> Self {
227 Self {
228 body,
229 headers,
230 status,
231 body_bytes,
232 non_utf8: non_utf8.into(),
233 allow_empty_body: true,
234 }
235 }
236
237 fn skips(&self, has_unreadable_value: bool) -> bool {
243 has_unreadable_value && self.non_utf8.resolve() == NonUtf8HeaderHandling::Skip
245 }
246
247 #[inline]
249 fn route(
250 &mut self,
251 member: &Schema<'_>,
252 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
253 ) -> Result<Routed, SerdeError> {
254 if let Some(header) = member.http_header() {
255 self.route_header(member, header.value(), consumer)?;
256 return Ok(Routed::Binding);
257 }
258 if let Some(prefix) = member.http_prefix_headers() {
259 self.route_prefix(member, prefix.value(), consumer)?;
260 return Ok(Routed::Binding);
261 }
262 if member.http_response_code().is_some() {
263 let mut status = HttpStatusDeserializer::new(self.status);
266 consumer(member, &mut status)?;
267 return Ok(Routed::Binding);
268 }
269 debug_assert!(
270 member.http_payload().is_some(),
271 "route is only called for response-bound members"
272 );
273 self.route_payload(member, consumer)?;
274 Ok(Routed::Payload)
275 }
276
277 #[inline]
278 fn route_header(
279 &mut self,
280 member: &Schema<'_>,
281 name: &str,
282 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
283 ) -> Result<(), SerdeError> {
284 let values = match member.extension(&HTTP_HEADER_NAME) {
288 Some(parsed) => HeaderValues::by_name(self.headers, parsed),
289 None => HeaderValues::new(self.headers, name),
290 };
291 if !values.is_present() {
292 return Ok(());
295 }
296 let mut value = HttpHeaderValueDeserializer::new(values);
297 if value.parses_to_no_value(member) {
298 return Ok(());
301 }
302 match consumer(member, &mut value) {
303 Ok(()) => Ok(()),
304 Err(err) if self.skips(values.has_unreadable_value()) => {
305 let _ = err;
308 Ok(())
309 }
310 Err(err) => Err(header_error(member, "header", name, err)),
311 }
312 }
313
314 #[inline(never)]
316 fn route_prefix(
317 &mut self,
318 member: &Schema<'_>,
319 prefix: &str,
320 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
321 ) -> Result<(), SerdeError> {
322 let mut map = HttpPrefixHeadersDeserializer::new(self.headers, prefix);
324 match consumer(member, &mut map) {
325 Ok(()) => Ok(()),
326 Err(err) if self.skips(map.has_unreadable_value()) => {
327 let _ = err;
329 Ok(())
330 }
331 Err(err) => Err(header_error(member, "prefix header", prefix, err)),
332 }
333 }
334
335 #[inline(never)]
336 fn route_payload(
337 &mut self,
338 member: &Schema<'_>,
339 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
340 ) -> Result<(), SerdeError> {
341 if member.streaming() {
342 return Ok(());
345 }
346 let Some(body_bytes) = self.body_bytes else {
347 return Err(SerdeError::invalid_input(format!(
350 "member `{}` has a non-streaming @httpPayload but the response body was not buffered",
351 member_name(member)
352 )));
353 };
354 if body_bytes.is_empty() {
355 return Ok(());
358 }
359 match member.shape_type() {
360 ShapeType::Blob | ShapeType::String => {
362 let mut raw = HttpRawPayloadDeserializer::new(body_bytes);
363 consumer(member, &mut raw)
364 }
365 _ => consumer(member, &mut self.body),
368 }
369 }
370}
371
372#[cold]
378fn header_error(member: &Schema<'_>, kind: &str, name: &str, err: SerdeError) -> SerdeError {
379 SerdeError::invalid_input(format!(
380 "failed to parse {} from {kind} `{name}`: {}",
381 member_name(member),
382 DisplayErrorContext(&err),
383 ))
384}
385
386fn member_name<'d>(member: &Schema<'d>) -> &'d str {
387 member.member_name().unwrap_or("<unnamed member>")
388}
389
390impl<D> ShapeDeserializer for HttpResponseDeserializer<'_, D>
391where
392 D: ShapeDeserializer,
393{
394 fn read_struct(
395 &mut self,
396 schema: &Schema<'_>,
397 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
398 ) -> Result<(), SerdeError> {
399 let mut has_payload = false;
403 if schema.response_bindings_need_scan() {
404 for member in schema.members() {
407 if member.has_http_response_binding()
408 && self.route(member, consumer)? == Routed::Payload
409 {
410 has_payload = true;
411 }
412 }
413 } else {
414 let mut mask = schema.response_binding_mask();
415 while mask != 0 {
416 let index = mask.trailing_zeros() as usize;
417 mask &= mask - 1;
418 let member = schema
419 .member_schema_by_index(index)
420 .expect("the mask is derived from this schema's own member array");
421 if self.route(member, consumer)? == Routed::Payload {
422 has_payload = true;
423 }
424 }
425 }
426
427 if has_payload {
428 return Ok(());
430 }
431 if !schema.has_response_body_members() {
432 return Ok(());
435 }
436 if self.allow_empty_body && self.body_bytes.map(|b| b.is_empty()).unwrap_or(true) {
437 return Ok(());
440 }
441
442 self.body.read_struct(schema, &mut |member, deser| {
445 if member.member_index().is_some() && member.has_http_response_binding() {
448 deser.skip_value()
449 } else {
450 consumer(member, deser)
451 }
452 })
453 }
454
455 fn read_list(
461 &mut self,
462 schema: &Schema<'_>,
463 consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
464 ) -> Result<(), SerdeError> {
465 self.body.read_list(schema, consumer)
466 }
467
468 fn read_map(
469 &mut self,
470 schema: &Schema<'_>,
471 consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
472 ) -> Result<(), SerdeError> {
473 self.body.read_map(schema, consumer)
474 }
475
476 fn read_boolean(&mut self, schema: &Schema<'_>) -> Result<bool, SerdeError> {
477 self.body.read_boolean(schema)
478 }
479
480 fn read_byte(&mut self, schema: &Schema<'_>) -> Result<i8, SerdeError> {
481 self.body.read_byte(schema)
482 }
483
484 fn read_short(&mut self, schema: &Schema<'_>) -> Result<i16, SerdeError> {
485 self.body.read_short(schema)
486 }
487
488 fn read_integer(&mut self, schema: &Schema<'_>) -> Result<i32, SerdeError> {
489 self.body.read_integer(schema)
490 }
491
492 fn read_long(&mut self, schema: &Schema<'_>) -> Result<i64, SerdeError> {
493 self.body.read_long(schema)
494 }
495
496 fn read_float(&mut self, schema: &Schema<'_>) -> Result<f32, SerdeError> {
497 self.body.read_float(schema)
498 }
499
500 fn read_double(&mut self, schema: &Schema<'_>) -> Result<f64, SerdeError> {
501 self.body.read_double(schema)
502 }
503
504 fn read_big_integer(&mut self, schema: &Schema<'_>) -> Result<BigInteger, SerdeError> {
505 self.body.read_big_integer(schema)
506 }
507
508 fn read_big_decimal(&mut self, schema: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
509 self.body.read_big_decimal(schema)
510 }
511
512 fn read_string(&mut self, schema: &Schema<'_>) -> Result<String, SerdeError> {
513 self.body.read_string(schema)
514 }
515
516 fn read_blob(&mut self, schema: &Schema<'_>) -> Result<Blob, SerdeError> {
517 self.body.read_blob(schema)
518 }
519
520 fn read_timestamp(&mut self, schema: &Schema<'_>) -> Result<DateTime, SerdeError> {
521 self.body.read_timestamp(schema)
522 }
523
524 fn read_document(&mut self, schema: &Schema<'_>) -> Result<Document, SerdeError> {
525 self.body.read_document(schema)
526 }
527
528 fn is_null(&self) -> bool {
529 self.body.is_null()
530 }
531
532 fn read_null(&mut self) -> Result<(), SerdeError> {
533 self.body.read_null()
534 }
535
536 fn skip_value(&mut self) -> Result<(), SerdeError> {
537 self.body.skip_value()
538 }
539
540 fn container_size(&self) -> Option<usize> {
541 self.body.container_size()
542 }
543
544 fn read_string_list(&mut self, schema: &Schema<'_>) -> Result<Vec<String>, SerdeError> {
545 self.body.read_string_list(schema)
546 }
547
548 fn read_blob_list(&mut self, schema: &Schema<'_>) -> Result<Vec<Blob>, SerdeError> {
549 self.body.read_blob_list(schema)
550 }
551
552 fn read_integer_list(&mut self, schema: &Schema<'_>) -> Result<Vec<i32>, SerdeError> {
553 self.body.read_integer_list(schema)
554 }
555
556 fn read_long_list(&mut self, schema: &Schema<'_>) -> Result<Vec<i64>, SerdeError> {
557 self.body.read_long_list(schema)
558 }
559
560 fn read_string_string_map(
561 &mut self,
562 schema: &Schema<'_>,
563 ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
564 self.body.read_string_string_map(schema)
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571 use crate::document::DocumentShapeDeserializer;
572 use crate::traits::TimestampFormat;
573 use crate::{shape_id, ShapeId};
574 use std::cell::RefCell;
575 use std::collections::HashMap;
576
577 const ID: ShapeId<'static> = shape_id!("test", "S");
578
579 fn headers(pairs: &[(&str, &[u8])]) -> Headers {
580 let mut map = http::HeaderMap::new();
581 for (name, value) in pairs {
582 map.append(
583 http::HeaderName::from_bytes(name.as_bytes()).expect("valid header name"),
584 http::HeaderValue::from_bytes(value).expect("valid header value"),
585 );
586 }
587 Headers::try_from(map).expect("valid headers")
588 }
589
590 #[derive(Default, Debug, PartialEq)]
592 struct Populated {
593 strings: HashMap<String, String>,
594 integers: HashMap<String, i32>,
595 lists: HashMap<String, Vec<String>>,
596 maps: HashMap<String, HashMap<String, String>>,
597 blobs: HashMap<String, Vec<u8>>,
598 timestamps: HashMap<String, i64>,
599 floats: HashMap<String, f32>,
600 }
601
602 fn consume(
607 out: &mut Populated,
608 ) -> impl FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError> + '_ {
609 move |member, deser| {
610 let name = member.member_name().unwrap_or("?").to_string();
611 match member.shape_type() {
612 ShapeType::String => {
613 out.strings.insert(name, deser.read_string(member)?);
614 }
615 ShapeType::Integer => {
616 out.integers.insert(name, deser.read_integer(member)?);
617 }
618 ShapeType::Timestamp => {
619 out.timestamps
620 .insert(name, deser.read_timestamp(member)?.secs());
621 }
622 ShapeType::Blob => {
623 out.blobs
624 .insert(name, deser.read_blob(member)?.into_inner());
625 }
626 ShapeType::Float => {
627 out.floats.insert(name, deser.read_float(member)?);
628 }
629 ShapeType::List => {
630 out.lists.insert(name, deser.read_string_list(member)?);
631 }
632 ShapeType::Map => {
633 out.maps.insert(name, deser.read_string_string_map(member)?);
634 }
635 other => {
636 return Err(SerdeError::unsupported(format!(
637 "test consumer does not handle {other:?}"
638 )))
639 }
640 }
641 Ok(())
642 }
643 }
644
645 #[derive(Default, Debug)]
649 struct BodyLog {
650 read: bool,
652 skipped: Vec<String>,
654 root: Option<String>,
659 }
660
661 #[derive(Debug)]
664 struct StubBody<'s> {
665 members: &'s [&'s Schema<'s>],
666 log: &'s RefCell<BodyLog>,
667 }
668
669 impl<'s> StubBody<'s> {
670 fn new(members: &'s [&'s Schema<'s>], log: &'s RefCell<BodyLog>) -> Self {
671 Self { members, log }
672 }
673 }
674
675 const FROM_BODY: &str = "from-body";
678
679 impl ShapeDeserializer for StubBody<'_> {
680 fn read_struct(
681 &mut self,
682 schema: &Schema<'_>,
683 consumer: &mut dyn FnMut(
684 &Schema<'_>,
685 &mut dyn ShapeDeserializer,
686 ) -> Result<(), SerdeError>,
687 ) -> Result<(), SerdeError> {
688 {
689 let mut log = self.log.borrow_mut();
690 log.read = true;
691 log.root = Some(schema.shape_id().shape_name().to_string());
692 }
693 for member in self.members {
694 let mut value = StubValue {
695 name: member.member_name().unwrap_or("?").to_string(),
696 log: self.log,
697 };
698 consumer(member, &mut value)?;
699 }
700 Ok(())
701 }
702
703 fn read_list(
704 &mut self,
705 _schema: &Schema<'_>,
706 _consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
707 ) -> Result<(), SerdeError> {
708 Ok(())
709 }
710
711 fn read_map(
712 &mut self,
713 _schema: &Schema<'_>,
714 _consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
715 ) -> Result<(), SerdeError> {
716 Ok(())
717 }
718
719 fn read_boolean(&mut self, _s: &Schema<'_>) -> Result<bool, SerdeError> {
720 Ok(false)
721 }
722 fn read_byte(&mut self, _s: &Schema<'_>) -> Result<i8, SerdeError> {
723 Ok(0)
724 }
725 fn read_short(&mut self, _s: &Schema<'_>) -> Result<i16, SerdeError> {
726 Ok(0)
727 }
728 fn read_integer(&mut self, _s: &Schema<'_>) -> Result<i32, SerdeError> {
729 Ok(0)
730 }
731 fn read_long(&mut self, _s: &Schema<'_>) -> Result<i64, SerdeError> {
732 Ok(0)
733 }
734 fn read_float(&mut self, _s: &Schema<'_>) -> Result<f32, SerdeError> {
735 Ok(0.0)
736 }
737 fn read_double(&mut self, _s: &Schema<'_>) -> Result<f64, SerdeError> {
738 Ok(0.0)
739 }
740 fn read_big_integer(&mut self, _s: &Schema<'_>) -> Result<BigInteger, SerdeError> {
741 Err(SerdeError::unsupported("stub"))
742 }
743 fn read_big_decimal(&mut self, _s: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
744 Err(SerdeError::unsupported("stub"))
745 }
746 fn read_string(&mut self, _s: &Schema<'_>) -> Result<String, SerdeError> {
747 Ok(FROM_BODY.to_string())
748 }
749 fn read_blob(&mut self, _s: &Schema<'_>) -> Result<Blob, SerdeError> {
750 Ok(Blob::new(FROM_BODY.as_bytes().to_vec()))
751 }
752 fn read_timestamp(&mut self, _s: &Schema<'_>) -> Result<DateTime, SerdeError> {
753 Ok(DateTime::from_secs(0))
754 }
755 fn read_document(&mut self, _s: &Schema<'_>) -> Result<Document, SerdeError> {
756 Ok(Document::Null)
757 }
758 fn is_null(&self) -> bool {
759 false
760 }
761 fn container_size(&self) -> Option<usize> {
762 None
763 }
764 }
765
766 #[derive(Debug)]
768 struct StubValue<'s> {
769 name: String,
770 log: &'s RefCell<BodyLog>,
771 }
772
773 impl ShapeDeserializer for StubValue<'_> {
774 fn read_struct(
775 &mut self,
776 _schema: &Schema<'_>,
777 _consumer: &mut dyn FnMut(
778 &Schema<'_>,
779 &mut dyn ShapeDeserializer,
780 ) -> Result<(), SerdeError>,
781 ) -> Result<(), SerdeError> {
782 Ok(())
783 }
784 fn read_list(
785 &mut self,
786 _schema: &Schema<'_>,
787 _consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
788 ) -> Result<(), SerdeError> {
789 Ok(())
790 }
791 fn read_map(
792 &mut self,
793 _schema: &Schema<'_>,
794 _consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
795 ) -> Result<(), SerdeError> {
796 Ok(())
797 }
798 fn read_boolean(&mut self, _s: &Schema<'_>) -> Result<bool, SerdeError> {
799 Ok(true)
800 }
801 fn read_byte(&mut self, _s: &Schema<'_>) -> Result<i8, SerdeError> {
802 Ok(-9)
803 }
804 fn read_short(&mut self, _s: &Schema<'_>) -> Result<i16, SerdeError> {
805 Ok(-9)
806 }
807 fn read_integer(&mut self, _s: &Schema<'_>) -> Result<i32, SerdeError> {
808 Ok(-999)
809 }
810 fn read_long(&mut self, _s: &Schema<'_>) -> Result<i64, SerdeError> {
811 Ok(-999)
812 }
813 fn read_float(&mut self, _s: &Schema<'_>) -> Result<f32, SerdeError> {
814 Ok(-9.0)
815 }
816 fn read_double(&mut self, _s: &Schema<'_>) -> Result<f64, SerdeError> {
817 Ok(-9.0)
818 }
819 fn read_big_integer(&mut self, _s: &Schema<'_>) -> Result<BigInteger, SerdeError> {
820 Err(SerdeError::unsupported("stub"))
821 }
822 fn read_big_decimal(&mut self, _s: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
823 Err(SerdeError::unsupported("stub"))
824 }
825 fn read_string(&mut self, _s: &Schema<'_>) -> Result<String, SerdeError> {
826 Ok(FROM_BODY.to_string())
827 }
828 fn read_blob(&mut self, _s: &Schema<'_>) -> Result<Blob, SerdeError> {
829 Ok(Blob::new(FROM_BODY.as_bytes().to_vec()))
830 }
831 fn read_timestamp(&mut self, _s: &Schema<'_>) -> Result<DateTime, SerdeError> {
832 Ok(DateTime::from_secs(-1))
833 }
834 fn read_document(&mut self, _s: &Schema<'_>) -> Result<Document, SerdeError> {
835 Ok(Document::Null)
836 }
837 fn is_null(&self) -> bool {
838 false
839 }
840 fn skip_value(&mut self) -> Result<(), SerdeError> {
841 self.log.borrow_mut().skipped.push(self.name.clone());
842 Ok(())
843 }
844 fn container_size(&self) -> Option<usize> {
845 None
846 }
847 }
848
849 static NAME_HEADER: Schema<'static> =
852 Schema::new_member(ID, ShapeType::String, "name", 0).with_http_header("x-name");
853 static COUNT_HEADER: Schema<'static> =
854 Schema::new_member(ID, ShapeType::Integer, "count", 1).with_http_header("x-count");
855 static STATUS: Schema<'static> =
856 Schema::new_member(ID, ShapeType::Integer, "status", 2).with_http_response_code();
857 static BODY_FIELD: Schema<'static> = Schema::new_member(ID, ShapeType::String, "note", 3);
858
859 static MIXED: Schema<'static> = Schema::new_struct(
860 ID,
861 ShapeType::Structure,
862 &[&NAME_HEADER, &COUNT_HEADER, &STATUS, &BODY_FIELD],
863 );
864
865 static BOUND_NAME: Schema<'static> =
867 Schema::new_member(ID, ShapeType::String, "name", 0).with_http_header("x-name");
868 static BOUND_STATUS: Schema<'static> =
869 Schema::new_member(ID, ShapeType::Integer, "status", 1).with_http_response_code();
870 static FULLY_BOUND: Schema<'static> =
871 Schema::new_struct(ID, ShapeType::Structure, &[&BOUND_NAME, &BOUND_STATUS]);
872
873 static EMPTY_STRUCT: Schema<'static> = Schema::new_struct(ID, ShapeType::Structure, &[]);
874
875 static BODY_ONLY_FIELD: Schema<'static> = Schema::new_member(ID, ShapeType::String, "note", 0);
876 static BODY_ONLY: Schema<'static> =
877 Schema::new_struct(ID, ShapeType::Structure, &[&BODY_ONLY_FIELD]);
878
879 fn output<'a>(
881 members: &'a [&'a Schema<'a>],
882 log: &'a RefCell<BodyLog>,
883 h: &'a Headers,
884 status: u16,
885 body_bytes: Option<&'a [u8]>,
886 ) -> HttpResponseDeserializer<'a, StubBody<'a>> {
887 HttpResponseDeserializer::for_output(
888 StubBody::new(members, log),
889 h,
890 status,
891 body_bytes,
892 NonUtf8HeaderHandling::Reject,
893 )
894 }
895
896 const NO_BODY_MEMBERS: &[&Schema<'static>] = &[];
897
898 #[test]
903 fn headers_status_and_body_members_all_reach_the_consumer() {
904 let h = headers(&[("x-name", b"widget"), ("x-count", b"7")]);
905 let log = RefCell::new(BodyLog::default());
906 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
907 let mut out = Populated::default();
908 output(body_members, &log, &h, 201, Some(b"{}"))
909 .read_struct(&MIXED, &mut consume(&mut out))
910 .unwrap();
911 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
912 assert_eq!(out.integers.get("count"), Some(&7));
913 assert_eq!(out.integers.get("status"), Some(&201));
914 assert_eq!(out.strings.get("note"), Some(&FROM_BODY.to_string()));
915 assert!(
916 log.borrow().read,
917 "the body codec must be invoked for body members"
918 );
919 }
920
921 #[test]
922 fn header_members_are_found_by_their_cached_names() {
923 static MIXED_CASE: Schema<'static> =
926 Schema::new_member(ID, ShapeType::String, "md5", 0).with_http_header("Content-MD5");
927 static REPEATED: Schema<'static> = Schema::new_member(ID, ShapeType::List, "tags", 1)
928 .with_http_header("x-tags")
929 .with_list_member(&crate::prelude::STRING);
930 static INVALID: Schema<'static> =
931 Schema::new_member(ID, ShapeType::String, "bad", 2).with_http_header("bad name");
932 static NAMED: Schema<'static> = Schema::new_struct(
933 ID,
934 ShapeType::Structure,
935 &[&MIXED_CASE, &REPEATED, &INVALID],
936 );
937 assert!(MIXED_CASE.extension(&HTTP_HEADER_NAME).is_some());
938 assert!(INVALID.extension(&HTTP_HEADER_NAME).is_none());
939
940 let h = headers(&[
941 ("content-md5", b"abc=="),
942 ("x-tags", b"a"),
943 ("x-tags", b"b, c"),
944 ]);
945 let log = RefCell::new(BodyLog::default());
946 let mut out = Populated::default();
947 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
948 .read_struct(&NAMED, &mut consume(&mut out))
949 .unwrap();
950 assert_eq!(out.strings.get("md5"), Some(&"abc==".to_string()));
951 assert_eq!(
952 out.lists.get("tags"),
953 Some(&vec!["a".to_string(), "b".to_string(), "c".to_string()])
954 );
955 assert!(!out.strings.contains_key("bad"));
956 }
957
958 #[test]
959 fn an_absent_header_leaves_its_member_absent() {
960 let h = headers(&[]);
961 let log = RefCell::new(BodyLog::default());
962 let mut out = Populated::default();
963 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
964 .read_struct(&FULLY_BOUND, &mut consume(&mut out))
965 .unwrap();
966 assert!(
967 !out.strings.contains_key("name"),
968 "the consumer must not run for an absent header"
969 );
970 assert_eq!(out.integers.get("status"), Some(&200));
971 }
972
973 #[test]
974 fn a_present_but_valueless_header_leaves_its_member_absent() {
975 let h = headers(&[("x-name", b""), ("x-count", b"")]);
978 let log = RefCell::new(BodyLog::default());
979 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
980 let mut out = Populated::default();
981 output(body_members, &log, &h, 200, Some(b"{}"))
982 .read_struct(&MIXED, &mut consume(&mut out))
983 .unwrap();
984 assert!(
985 !out.integers.contains_key("count"),
986 "an empty integer header must not become Some(0)"
987 );
988 assert_eq!(
989 out.strings.get("name"),
990 Some(&String::new()),
991 "an empty plain-string header is the empty string, not absent"
992 );
993 }
994
995 #[test]
996 fn the_body_codec_is_skipped_when_no_member_belongs_to_it() {
997 let h = headers(&[("x-name", b"widget")]);
998 let log = RefCell::new(BodyLog::default());
999 let mut out = Populated::default();
1000 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1001 .read_struct(&FULLY_BOUND, &mut consume(&mut out))
1002 .unwrap();
1003 assert!(
1004 !log.borrow().read,
1005 "a fully bound structure must not invoke the body codec"
1006 );
1007 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
1008 }
1009
1010 #[test]
1011 fn an_empty_structure_invokes_nothing() {
1012 let h = headers(&[]);
1013 let log = RefCell::new(BodyLog::default());
1014 let mut out = Populated::default();
1015 output(NO_BODY_MEMBERS, &log, &h, 204, Some(b""))
1016 .read_struct(&EMPTY_STRUCT, &mut consume(&mut out))
1017 .unwrap();
1018 assert!(!log.borrow().read);
1019 assert_eq!(out, Populated::default());
1020 }
1021
1022 #[test]
1023 fn a_body_only_structure_goes_straight_to_the_codec() {
1024 let h = headers(&[]);
1025 let log = RefCell::new(BodyLog::default());
1026 let body_members: &[&Schema<'_>] = &[&BODY_ONLY_FIELD];
1027 let mut out = Populated::default();
1028 output(body_members, &log, &h, 200, Some(b"{}"))
1029 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1030 .unwrap();
1031 assert!(log.borrow().read);
1032 assert_eq!(out.strings.get("note"), Some(&FROM_BODY.to_string()));
1033 assert!(log.borrow().skipped.is_empty());
1034 }
1035
1036 #[test]
1041 fn a_body_copy_of_a_bound_member_is_consumed_and_discarded() {
1042 let h = headers(&[("x-name", b"from-header"), ("x-count", b"7")]);
1045 let log = RefCell::new(BodyLog::default());
1046 let body_members: &[&Schema<'_>] = &[&NAME_HEADER, &COUNT_HEADER, &STATUS, &BODY_FIELD];
1047 let mut out = Populated::default();
1048 output(body_members, &log, &h, 201, Some(b"{}"))
1049 .read_struct(&MIXED, &mut consume(&mut out))
1050 .unwrap();
1051 assert_eq!(out.strings.get("name"), Some(&"from-header".to_string()));
1052 assert_eq!(out.integers.get("count"), Some(&7));
1053 assert_eq!(out.integers.get("status"), Some(&201));
1054 assert_eq!(
1055 log.borrow().skipped,
1056 vec![
1057 "name".to_string(),
1058 "count".to_string(),
1059 "status".to_string()
1060 ],
1061 "each bound member's body copy must be skipped, in body order"
1062 );
1063 assert_eq!(
1064 out.strings.get("note"),
1065 Some(&FROM_BODY.to_string()),
1066 "only the unbound member may come from the body"
1067 );
1068 }
1069
1070 #[test]
1071 fn an_index_less_callback_is_forwarded_rather_than_filtered() {
1072 let body_members: &[&Schema<'_>] = &[&crate::prelude::DOCUMENT];
1075 let log = RefCell::new(BodyLog::default());
1076 let h = headers(&[]);
1077 let mut seen = Vec::new();
1078 output(body_members, &log, &h, 200, Some(b"{}"))
1079 .read_struct(&BODY_ONLY, &mut |member, _| {
1080 seen.push(member.member_index());
1081 Ok(())
1082 })
1083 .unwrap();
1084 assert_eq!(
1085 seen,
1086 vec![None],
1087 "the index-less callback must reach the consumer"
1088 );
1089 assert!(log.borrow().skipped.is_empty());
1090 }
1091
1092 static PREFIX_KEY: Schema<'static> = Schema::new(shape_id!("test", "K"), ShapeType::String);
1097 static PREFIX_VALUE: Schema<'static> = Schema::new(shape_id!("test", "V"), ShapeType::String);
1098 static PREFIX_MEMBER: Schema<'static> = Schema::new_member(ID, ShapeType::Map, "meta", 0)
1099 .with_http_prefix_headers("x-meta-")
1100 .with_map_members(&PREFIX_KEY, &PREFIX_VALUE);
1101 static PREFIX_SCHEMA: Schema<'static> =
1102 Schema::new_struct(ID, ShapeType::Structure, &[&PREFIX_MEMBER]);
1103
1104 #[test]
1105 fn a_prefix_map_is_populated_from_matching_headers() {
1106 let h = headers(&[("x-meta-a", b"1"), ("x-other", b"2")]);
1107 let log = RefCell::new(BodyLog::default());
1108 let mut out = Populated::default();
1109 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1110 .read_struct(&PREFIX_SCHEMA, &mut consume(&mut out))
1111 .unwrap();
1112 assert_eq!(
1113 out.maps.get("meta").and_then(|m| m.get("a")),
1114 Some(&"1".to_string())
1115 );
1116 }
1117
1118 #[test]
1119 fn a_prefix_with_no_matches_is_an_empty_map_not_an_absent_member() {
1120 let h = headers(&[("x-other", b"2")]);
1121 let log = RefCell::new(BodyLog::default());
1122 let mut out = Populated::default();
1123 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1124 .read_struct(&PREFIX_SCHEMA, &mut consume(&mut out))
1125 .unwrap();
1126 assert_eq!(
1127 out.maps.get("meta"),
1128 Some(&HashMap::new()),
1129 "existing Smithy behavior is Some(empty_map)"
1130 );
1131 }
1132
1133 static BLOB_PAYLOAD: Schema<'static> =
1138 Schema::new_member(ID, ShapeType::Blob, "payload", 0).with_http_payload();
1139 static PAYLOAD_HEADER: Schema<'static> =
1140 Schema::new_member(ID, ShapeType::String, "name", 1).with_http_header("x-name");
1141 static BLOB_PAYLOAD_SCHEMA: Schema<'static> =
1142 Schema::new_struct(ID, ShapeType::Structure, &[&BLOB_PAYLOAD, &PAYLOAD_HEADER]);
1143
1144 #[test]
1145 fn a_raw_payload_comes_from_the_body_bytes_and_bypasses_the_codec() {
1146 let h = headers(&[("x-name", b"widget")]);
1147 let log = RefCell::new(BodyLog::default());
1148 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
1149 let mut out = Populated::default();
1150 output(body_members, &log, &h, 200, Some(b"\x00\x01raw"))
1151 .read_struct(&BLOB_PAYLOAD_SCHEMA, &mut consume(&mut out))
1152 .unwrap();
1153 assert_eq!(
1154 out.blobs.get("payload").map(|b| b.as_slice()),
1155 Some(&b"\x00\x01raw"[..])
1156 );
1157 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
1158 assert!(
1159 !log.borrow().read,
1160 "a payload member means there is no outer document for the codec to read"
1161 );
1162 }
1163
1164 #[test]
1165 fn an_empty_payload_leaves_the_member_absent() {
1166 let h = headers(&[("x-name", b"widget")]);
1167 let log = RefCell::new(BodyLog::default());
1168 let mut out = Populated::default();
1169 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1170 .read_struct(&BLOB_PAYLOAD_SCHEMA, &mut consume(&mut out))
1171 .unwrap();
1172 assert!(
1173 !out.blobs.contains_key("payload"),
1174 "an empty payload must not become an empty blob"
1175 );
1176 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
1177 }
1178
1179 static STRUCT_PAYLOAD: Schema<'static> =
1180 Schema::new_member(ID, ShapeType::Structure, "payload", 0).with_http_payload();
1181 static STRUCT_PAYLOAD_SCHEMA: Schema<'static> =
1182 Schema::new_struct(ID, ShapeType::Structure, &[&STRUCT_PAYLOAD]);
1183
1184 #[test]
1185 fn a_structured_payload_is_handed_to_the_body_codec() {
1186 let h = headers(&[]);
1187 let log = RefCell::new(BodyLog::default());
1188 let mut saw_payload = false;
1189 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b"{}"))
1190 .read_struct(&STRUCT_PAYLOAD_SCHEMA, &mut |member, d| {
1191 assert_eq!(member.member_name(), Some("payload"));
1192 d.read_struct(member, &mut |_, _| Ok(()))?;
1195 saw_payload = true;
1196 Ok(())
1197 })
1198 .unwrap();
1199 assert!(saw_payload);
1200 assert!(
1201 log.borrow().read,
1202 "the structured payload must be read through the body codec"
1203 );
1204 }
1205
1206 static STREAM_PAYLOAD: Schema<'static> = Schema::new_member(ID, ShapeType::Blob, "payload", 0)
1207 .with_http_payload()
1208 .with_streaming();
1209 static STREAM_SCHEMA: Schema<'static> = Schema::new_struct(
1210 ID,
1211 ShapeType::Structure,
1212 &[&STREAM_PAYLOAD, &PAYLOAD_HEADER],
1213 );
1214
1215 #[test]
1216 fn a_streaming_payload_member_is_never_handed_to_a_consumer() {
1217 let h = headers(&[("x-name", b"widget")]);
1219 let log = RefCell::new(BodyLog::default());
1220 let mut out = Populated::default();
1221 output(NO_BODY_MEMBERS, &log, &h, 200, None)
1222 .read_struct(&STREAM_SCHEMA, &mut consume(&mut out))
1223 .unwrap();
1224 assert!(
1225 !out.blobs.contains_key("payload"),
1226 "the generated streaming path owns the live body and installs it itself"
1227 );
1228 assert_eq!(
1229 out.strings.get("name"),
1230 Some(&"widget".to_string()),
1231 "header members must still be populated for a streaming output"
1232 );
1233 assert!(
1234 !log.borrow().read,
1235 "the live stream must not be parsed as a document"
1236 );
1237 }
1238
1239 #[test]
1240 fn a_non_streaming_payload_without_a_buffered_body_is_an_error() {
1241 let h = headers(&[]);
1244 let log = RefCell::new(BodyLog::default());
1245 let err = output(NO_BODY_MEMBERS, &log, &h, 200, None)
1246 .read_struct(&BLOB_PAYLOAD_SCHEMA, &mut |_, _| Ok(()))
1247 .expect_err("no buffered body");
1248 assert!(
1249 format!("{err}").contains("was not buffered"),
1250 "unexpected error: {err}"
1251 );
1252 }
1253
1254 #[test]
1259 fn a_modeled_error_with_an_empty_body_still_gets_its_headers_and_status() {
1260 let h = headers(&[("x-name", b"widget")]);
1262 let log = RefCell::new(BodyLog::default());
1263 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
1264 let mut out = Populated::default();
1265 HttpResponseDeserializer::for_error(
1266 StubBody::new(body_members, &log),
1267 &h,
1268 404,
1269 Some(b""),
1270 NonUtf8HeaderHandling::Reject,
1271 )
1272 .read_struct(&MIXED, &mut consume(&mut out))
1273 .unwrap();
1274 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
1275 assert_eq!(out.integers.get("status"), Some(&404));
1276 assert!(
1277 !log.borrow().read,
1278 "an empty error body must not be handed to the codec"
1279 );
1280 }
1281
1282 #[test]
1283 fn a_modeled_error_with_a_body_still_reads_it() {
1284 let h = headers(&[]);
1285 let log = RefCell::new(BodyLog::default());
1286 let body_members: &[&Schema<'_>] = &[&BODY_ONLY_FIELD];
1287 let mut out = Populated::default();
1288 HttpResponseDeserializer::for_error(
1289 StubBody::new(body_members, &log),
1290 &h,
1291 400,
1292 Some(b"{}"),
1293 NonUtf8HeaderHandling::Reject,
1294 )
1295 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1296 .unwrap();
1297 assert!(log.borrow().read);
1298 assert_eq!(out.strings.get("note"), Some(&FROM_BODY.to_string()));
1299 }
1300
1301 #[test]
1302 fn a_successful_response_keeps_the_codecs_empty_body_strictness() {
1303 let h = headers(&[]);
1306 let log = RefCell::new(BodyLog::default());
1307 let body_members: &[&Schema<'_>] = &[&BODY_ONLY_FIELD];
1308 let mut out = Populated::default();
1309 output(body_members, &log, &h, 200, Some(b""))
1310 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1311 .unwrap();
1312 assert!(
1313 log.borrow().read,
1314 "the composite must not decide emptiness on behalf of a successful response"
1315 );
1316 }
1317
1318 fn with_policy<'a>(
1323 h: &'a Headers,
1324 policy: NonUtf8HeaderHandling,
1325 log: &'a RefCell<BodyLog>,
1326 ) -> HttpResponseDeserializer<'a, StubBody<'a>> {
1327 HttpResponseDeserializer::for_output(
1328 StubBody::new(NO_BODY_MEMBERS, log),
1329 h,
1330 200,
1331 Some(b""),
1332 policy,
1333 )
1334 }
1335
1336 #[test]
1337 fn an_unreadable_header_is_rejected_by_default_with_member_and_header_context() {
1338 let h = headers(&[("x-name", b"value-\xe9")]);
1339 let log = RefCell::new(BodyLog::default());
1340 let mut out = Populated::default();
1341 let err = with_policy(&h, NonUtf8HeaderHandling::Reject, &log)
1342 .read_struct(&FULLY_BOUND, &mut consume(&mut out))
1343 .expect_err("non-UTF-8 must be rejected");
1344 let message = format!("{err}");
1345 for expected in ["name", "x-name", "not valid utf-8"] {
1346 assert!(
1347 message.contains(expected),
1348 "expected {expected:?} in {message:?}"
1349 );
1350 }
1351 }
1352
1353 #[test]
1354 fn skip_leaves_an_unreadable_member_absent_and_the_header_intact() {
1355 let h = headers(&[("x-name", b"value-\xe9")]);
1356 let log = RefCell::new(BodyLog::default());
1357 let mut out = Populated::default();
1358 with_policy(&h, NonUtf8HeaderHandling::Skip, &log)
1359 .read_struct(&FULLY_BOUND, &mut consume(&mut out))
1360 .expect("Skip must not fail the response");
1361 assert!(!out.strings.contains_key("name"));
1362 assert_eq!(out.integers.get("status"), Some(&200));
1363 assert_eq!(
1364 h.get_bytes("x-name"),
1365 Some(&b"value-\xe9"[..]),
1366 "the raw octets must stay readable for interceptors"
1367 );
1368 }
1369
1370 #[test]
1371 fn skip_does_not_hide_a_readable_but_malformed_value() {
1372 let h = headers(&[("x-name", b"ok"), ("x-count", b"notanint")]);
1374 let log = RefCell::new(BodyLog::default());
1375 let mut out = Populated::default();
1376 let err = with_policy(&h, NonUtf8HeaderHandling::Skip, &log)
1377 .read_struct(&MIXED, &mut consume(&mut out))
1378 .expect_err("a malformed integer is still an error under Skip");
1379 assert!(format!("{err}").contains("x-count"), "unexpected: {err}");
1380 }
1381
1382 static LIST_ELEMENT: Schema<'static> = Schema::new(shape_id!("test", "E"), ShapeType::String);
1383 static LIST_HEADER: Schema<'static> = Schema::new_member(ID, ShapeType::List, "tags", 0)
1384 .with_http_header("x-tags")
1385 .with_list_member(&LIST_ELEMENT);
1386 static LIST_SCHEMA: Schema<'static> =
1387 Schema::new_struct(ID, ShapeType::Structure, &[&LIST_HEADER]);
1388
1389 #[test]
1390 fn skip_applies_to_the_whole_member_regardless_of_value_order() {
1391 for pairs in [
1394 vec![("x-tags", b"value-\xe9".as_slice()), ("x-tags", b"ok")],
1395 vec![("x-tags", b"ok".as_slice()), ("x-tags", b"value-\xe9")],
1396 ] {
1397 let h = headers(&pairs);
1398 let log = RefCell::new(BodyLog::default());
1399 let mut out = Populated::default();
1400 with_policy(&h, NonUtf8HeaderHandling::Skip, &log)
1401 .read_struct(&LIST_SCHEMA, &mut consume(&mut out))
1402 .expect("Skip must apply in both orders");
1403 assert!(!out.lists.contains_key("tags"));
1404 }
1405 }
1406
1407 #[test]
1408 fn a_list_header_is_populated_when_every_value_is_readable() {
1409 let h = headers(&[("x-tags", b"a,b"), ("x-tags", b"c")]);
1410 let log = RefCell::new(BodyLog::default());
1411 let mut out = Populated::default();
1412 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1413 .read_struct(&LIST_SCHEMA, &mut consume(&mut out))
1414 .unwrap();
1415 assert_eq!(
1416 out.lists.get("tags"),
1417 Some(&vec!["a".to_string(), "b".to_string(), "c".to_string()])
1418 );
1419 }
1420
1421 #[test]
1422 fn a_prefix_map_is_suppressed_whole_under_skip() {
1423 let h = headers(&[("x-meta-good", b"ok"), ("x-meta-bad", b"value-\xe9")]);
1424 let log = RefCell::new(BodyLog::default());
1425 let mut out = Populated::default();
1426 with_policy(&h, NonUtf8HeaderHandling::Skip, &log)
1427 .read_struct(&PREFIX_SCHEMA, &mut consume(&mut out))
1428 .expect("Skip must not fail the response");
1429 assert!(
1430 !out.maps.contains_key("meta"),
1431 "a partial map would conceal the dropped entry"
1432 );
1433 }
1434
1435 #[test]
1436 fn an_unreadable_prefix_entry_is_rejected_by_default_naming_the_prefix() {
1437 let h = headers(&[("x-meta-bad", b"value-\xe9")]);
1438 let log = RefCell::new(BodyLog::default());
1439 let mut out = Populated::default();
1440 let err = with_policy(&h, NonUtf8HeaderHandling::Reject, &log)
1441 .read_struct(&PREFIX_SCHEMA, &mut consume(&mut out))
1442 .expect_err("non-UTF-8 must be rejected");
1443 let message = format!("{err}");
1444 for expected in ["meta", "prefix header", "x-meta-"] {
1445 assert!(
1446 message.contains(expected),
1447 "expected {expected:?} in {message:?}"
1448 );
1449 }
1450 }
1451
1452 #[test]
1453 fn the_policy_defaults_to_reject_and_round_trips_through_the_config_bag() {
1454 assert_eq!(
1455 non_utf8_policy(&ConfigBag::base()),
1456 NonUtf8HeaderHandling::Reject,
1457 "an empty bag must read as Reject rather than requiring callers to default it"
1458 );
1459 let mut layer = aws_smithy_types::config_bag::CloneableLayer::new("test");
1460 layer.store_put(NonUtf8HeaderHandling::Skip);
1461 let bag = ConfigBag::of_layers(vec![layer.into()]);
1462 assert_eq!(non_utf8_policy(&bag), NonUtf8HeaderHandling::Skip);
1463 }
1464
1465 fn all_bound(count: usize) -> &'static Schema<'static> {
1472 let mut leaked: Vec<&'static Schema<'static>> = Vec::new();
1473 for index in 0..count {
1474 let name: &'static str = Box::leak(format!("m{index}").into_boxed_str());
1475 let header: &'static str = Box::leak(format!("x-m{index}").into_boxed_str());
1476 leaked.push(Box::leak(Box::new(
1477 Schema::new_member(ID, ShapeType::String, name, index).with_http_header(header),
1478 )));
1479 }
1480 let members: &'static [&'static Schema<'static>] = Box::leak(leaked.into_boxed_slice());
1481 Box::leak(Box::new(Schema::new_struct(
1482 ID,
1483 ShapeType::Structure,
1484 members,
1485 )))
1486 }
1487
1488 fn header_pairs(indices: impl Iterator<Item = usize>) -> Vec<(String, Vec<u8>)> {
1489 indices
1490 .map(|i| (format!("x-m{i}"), format!("v{i}").into_bytes()))
1491 .collect()
1492 }
1493
1494 #[test]
1495 fn the_scan_fallback_routes_the_same_members_as_the_mask() {
1496 let schema = all_bound(64);
1498 assert!(
1499 schema.response_bindings_need_scan(),
1500 "precondition: 64 bound members exceeds the mask"
1501 );
1502 let pairs = header_pairs(0..64);
1503 let borrowed: Vec<(&str, &[u8])> = pairs
1504 .iter()
1505 .map(|(n, v)| (n.as_str(), v.as_slice()))
1506 .collect();
1507 let h = headers(&borrowed);
1508 let log = RefCell::new(BodyLog::default());
1509 let mut out = Populated::default();
1510 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1511 .read_struct(schema, &mut consume(&mut out))
1512 .unwrap();
1513 assert_eq!(out.strings.len(), 64);
1514 assert_eq!(out.strings.get("m0"), Some(&"v0".to_string()));
1515 assert_eq!(out.strings.get("m63"), Some(&"v63".to_string()));
1516 }
1517
1518 #[test]
1519 fn the_mask_path_and_the_scan_path_agree_on_the_same_members() {
1520 let exact = all_bound(62);
1523 let scanned = all_bound(64);
1524 assert!(!exact.response_bindings_need_scan());
1525 assert!(scanned.response_bindings_need_scan());
1526
1527 let pairs = header_pairs(0..62);
1528 let borrowed: Vec<(&str, &[u8])> = pairs
1529 .iter()
1530 .map(|(n, v)| (n.as_str(), v.as_slice()))
1531 .collect();
1532 let h = headers(&borrowed);
1533
1534 let mut from_mask = Populated::default();
1535 let log = RefCell::new(BodyLog::default());
1536 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1537 .read_struct(exact, &mut consume(&mut from_mask))
1538 .unwrap();
1539
1540 let mut from_scan = Populated::default();
1541 let log = RefCell::new(BodyLog::default());
1542 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1543 .read_struct(scanned, &mut consume(&mut from_scan))
1544 .unwrap();
1545
1546 assert_eq!(
1547 from_mask, from_scan,
1548 "the two routing paths must produce identical results"
1549 );
1550 assert_eq!(from_mask.strings.len(), 62);
1551 }
1552
1553 #[test]
1554 fn a_sparse_binding_layout_visits_only_the_bound_members() {
1555 let mut leaked: Vec<&'static Schema<'static>> = Vec::new();
1556 for index in 0..50usize {
1557 let name: &'static str = Box::leak(format!("m{index}").into_boxed_str());
1558 let schema = if index % 12 == 0 {
1559 let header: &'static str = Box::leak(format!("x-m{index}").into_boxed_str());
1560 Schema::new_member(ID, ShapeType::String, name, index).with_http_header(header)
1561 } else {
1562 Schema::new_member(ID, ShapeType::String, name, index)
1563 };
1564 leaked.push(Box::leak(Box::new(schema)));
1565 }
1566 let members: &'static [&'static Schema<'static>] = Box::leak(leaked.into_boxed_slice());
1567 let schema = Schema::new_struct(ID, ShapeType::Structure, members);
1568 assert!(!schema.response_bindings_need_scan());
1569 assert_eq!(schema.response_binding_mask().count_ones(), 5);
1570
1571 let pairs = header_pairs((0..50).filter(|i| i % 12 == 0));
1572 let borrowed: Vec<(&str, &[u8])> = pairs
1573 .iter()
1574 .map(|(n, v)| (n.as_str(), v.as_slice()))
1575 .collect();
1576 let h = headers(&borrowed);
1577 let log = RefCell::new(BodyLog::default());
1578 let mut out = Populated::default();
1579 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b"{}"))
1580 .read_struct(&schema, &mut consume(&mut out))
1581 .unwrap();
1582 assert_eq!(out.strings.len(), 5);
1583 assert!(
1584 log.borrow().read,
1585 "the 45 unbound members still belong to the body"
1586 );
1587 }
1588
1589 static TS_HEADER: Schema<'static> = Schema::new_member(ID, ShapeType::Timestamp, "at", 0)
1590 .with_http_header("x-at")
1591 .with_timestamp_format(TimestampFormat::EpochSeconds);
1592 static TS_SCHEMA: Schema<'static> = Schema::new_struct(ID, ShapeType::Structure, &[&TS_HEADER]);
1593
1594 #[test]
1595 fn a_timestamp_header_uses_the_schemas_format() {
1596 let h = headers(&[("x-at", b"1445412480")]);
1597 let log = RefCell::new(BodyLog::default());
1598 let mut out = Populated::default();
1599 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1600 .read_struct(&TS_SCHEMA, &mut consume(&mut out))
1601 .unwrap();
1602 assert_eq!(out.timestamps.get("at"), Some(&1_445_412_480));
1603 }
1604
1605 #[test]
1610 fn non_structure_reads_fall_through_to_the_body() {
1611 let doc = Document::String("hello".to_string());
1613 let h = headers(&[]);
1614 let mut deser = HttpResponseDeserializer::for_output(
1615 DocumentShapeDeserializer::new(&doc),
1616 &h,
1617 200,
1618 Some(b""),
1619 NonUtf8HeaderHandling::Reject,
1620 );
1621 assert_eq!(deser.read_string(&crate::prelude::STRING).unwrap(), "hello");
1622 }
1623
1624 static NAN_HEADER: Schema<'static> =
1635 Schema::new_member(ID, ShapeType::Float, "nan", 0).with_http_header("x-nan");
1636 static INF_HEADER: Schema<'static> =
1637 Schema::new_member(ID, ShapeType::Float, "inf", 1).with_http_header("x-inf");
1638 static NEG_INF_HEADER: Schema<'static> =
1639 Schema::new_member(ID, ShapeType::Float, "neg", 2).with_http_header("x-neg");
1640 static FLOATS: Schema<'static> = Schema::new_struct(
1641 ID,
1642 ShapeType::Structure,
1643 &[&NAN_HEADER, &INF_HEADER, &NEG_INF_HEADER],
1644 );
1645
1646 #[test]
1647 fn smithy_special_float_values_survive_the_composite() {
1648 let h = headers(&[
1652 ("x-nan", b"NaN"),
1653 ("x-inf", b"Infinity"),
1654 ("x-neg", b"-Infinity"),
1655 ]);
1656 let log = RefCell::new(BodyLog::default());
1657 let mut out = Populated::default();
1658 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1659 .read_struct(&FLOATS, &mut consume(&mut out))
1660 .unwrap();
1661 assert!(out.floats["nan"].is_nan());
1662 assert_eq!(out.floats["inf"], f32::INFINITY);
1663 assert_eq!(out.floats["neg"], f32::NEG_INFINITY);
1664 }
1665
1666 static MEDIA_HEADER: Schema<'static> = Schema::new_member(ID, ShapeType::String, "config", 0)
1667 .with_http_header("x-config")
1668 .with_media_type("application/json");
1669 static MEDIA: Schema<'static> = Schema::new_struct(ID, ShapeType::Structure, &[&MEDIA_HEADER]);
1670
1671 #[test]
1672 fn a_media_type_header_is_base64_decoded_through_the_composite() {
1673 let h = headers(&[("x-config", b"eyJhIjoxfQ==")]);
1677 let log = RefCell::new(BodyLog::default());
1678 let mut out = Populated::default();
1679 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1680 .read_struct(&MEDIA, &mut consume(&mut out))
1681 .unwrap();
1682 assert_eq!(out.strings.get("config"), Some(&r#"{"a":1}"#.to_string()));
1683 }
1684
1685 #[test]
1686 fn a_media_type_header_that_decodes_to_invalid_utf8_is_an_error_not_a_skip() {
1687 let h = headers(&[("x-config", b"/w==")]); for policy in [NonUtf8HeaderHandling::Reject, NonUtf8HeaderHandling::Skip] {
1692 let log = RefCell::new(BodyLog::default());
1693 let mut out = Populated::default();
1694 let err = HttpResponseDeserializer::for_output(
1695 StubBody::new(NO_BODY_MEMBERS, &log),
1696 &h,
1697 200,
1698 Some(b""),
1699 policy.clone(),
1700 )
1701 .read_struct(&MEDIA, &mut consume(&mut out))
1702 .expect_err("invalid UTF-8 after base64 decoding is a parse error");
1703 let message = format!("{}", DisplayErrorContext(&err));
1704 assert!(
1705 message.contains("config") && message.contains("x-config"),
1706 "error must name the member and header, got: {message}"
1707 );
1708 assert!(
1709 !out.strings.contains_key("config"),
1710 "a failed parse must not populate the member"
1711 );
1712 }
1713 }
1714
1715 #[test]
1716 fn quoted_list_values_keep_their_commas_through_the_composite() {
1717 let h = headers(&[("x-tags", br#""a,b", c, "d\"e""#)]);
1720 let log = RefCell::new(BodyLog::default());
1721 let mut out = Populated::default();
1722 output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1723 .read_struct(&LIST_SCHEMA, &mut consume(&mut out))
1724 .unwrap();
1725 assert_eq!(
1726 out.lists.get("tags"),
1727 Some(&vec![
1728 "a,b".to_string(),
1729 "c".to_string(),
1730 "d\"e".to_string()
1731 ])
1732 );
1733 }
1734
1735 #[test]
1736 fn a_scalar_receiving_repeated_lines_is_a_cardinality_error() {
1737 let h = headers(&[("x-count", b"1"), ("x-count", b"2")]);
1740 let log = RefCell::new(BodyLog::default());
1741 let mut out = Populated::default();
1742 let err = output(NO_BODY_MEMBERS, &log, &h, 200, Some(b""))
1743 .read_struct(&MIXED, &mut consume(&mut out))
1744 .expect_err("two values for a scalar header is an error");
1745 let message = format!("{}", DisplayErrorContext(&err));
1746 assert!(
1747 message.contains("expected one item but found 2"),
1748 "the parser's cardinality message must survive, got: {message}"
1749 );
1750 assert!(
1751 message.contains("count") && message.contains("x-count"),
1752 "error must name the member and header, got: {message}"
1753 );
1754 }
1755
1756 const PAYLOAD_ID: ShapeId<'static> = shape_id!("test", "Payload");
1761
1762 static XML_NAMED_PAYLOAD: Schema<'static> =
1763 Schema::new_member(ID, ShapeType::Structure, "payload", 0)
1764 .with_http_payload()
1765 .with_xml_name("CustomRoot");
1766 static XML_NAMED_OUTPUT: Schema<'static> =
1767 Schema::new_struct(ID, ShapeType::Structure, &[&XML_NAMED_PAYLOAD]);
1768
1769 static PAYLOAD_FIELD: Schema<'static> =
1770 Schema::new_member(PAYLOAD_ID, ShapeType::String, "inner", 0);
1771 static PAYLOAD_TARGET: Schema<'static> =
1772 Schema::new_struct(PAYLOAD_ID, ShapeType::Structure, &[&PAYLOAD_FIELD]);
1773
1774 #[test]
1775 fn a_structured_payload_root_is_chosen_by_the_consumer_not_the_composite() {
1776 let h = headers(&[]);
1782 let log = RefCell::new(BodyLog::default());
1783 let body_members: &[&Schema<'_>] = &[&PAYLOAD_FIELD];
1784 let mut saw_member_xml_name = None;
1785 let mut inner = None;
1786 output(body_members, &log, &h, 200, Some(b"<CustomRoot/>"))
1787 .read_struct(&XML_NAMED_OUTPUT, &mut |member, deser| {
1788 saw_member_xml_name = member.xml_name().map(|n| n.value().to_string());
1789 deser.read_struct(&PAYLOAD_TARGET, &mut |field, value| {
1791 inner = Some((
1792 field.member_name().unwrap_or("?").to_string(),
1793 value.read_string(field)?,
1794 ));
1795 Ok(())
1796 })
1797 })
1798 .unwrap();
1799 assert_eq!(
1800 saw_member_xml_name.as_deref(),
1801 Some("CustomRoot"),
1802 "the member's traits must be reachable from the schema the composite passes"
1803 );
1804 assert_eq!(
1805 log.borrow().root.as_deref(),
1806 Some("Payload"),
1807 "the codec's root is the schema the consumer forwarded, not the member's xmlName"
1808 );
1809 assert_eq!(
1810 inner,
1811 Some(("inner".to_string(), FROM_BODY.to_string())),
1812 "the codec must be positioned at the payload root"
1813 );
1814 }
1815
1816 use aws_smithy_types::body::SdkBody;
1825
1826 fn http_response(status: u16, pairs: &[(&str, &[u8])], body: SdkBody) -> Response {
1827 let mut builder = http::Response::builder().status(status);
1828 for (name, value) in pairs {
1829 builder = builder.header(*name, http::HeaderValue::from_bytes(value).expect("value"));
1830 }
1831 Response::try_from(builder.body(body).expect("response")).expect("convertible")
1832 }
1833
1834 fn one_member_body<'a>(
1837 members: &'a [&'a Schema<'a>],
1838 log: &'a RefCell<BodyLog>,
1839 ) -> StubBody<'a> {
1840 StubBody::new(members, log)
1841 }
1842
1843 #[test]
1844 fn a_body_only_output_takes_the_bare_body_fast_path() {
1845 assert!(
1846 is_body_only(&BODY_ONLY),
1847 "an output with only body members has nothing for the composite to do"
1848 );
1849 let log = RefCell::new(BodyLog::default());
1853 let body_members: &[&Schema<'_>] = &[&BODY_ONLY_FIELD];
1854 let response = http_response(200, &[], SdkBody::from("{}"));
1855 let mut out = Populated::default();
1856 http_output_deserializer(
1857 one_member_body(body_members, &log),
1858 &response,
1859 &BODY_ONLY,
1860 &ConfigBag::base(),
1861 )
1862 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1863 .unwrap();
1864 assert_eq!(out.strings.get("note"), Some(&FROM_BODY.to_string()));
1865 }
1866
1867 #[test]
1868 fn an_empty_output_does_not_take_the_fast_path() {
1869 assert!(!is_body_only(&EMPTY_STRUCT));
1872 let log = RefCell::new(BodyLog::default());
1873 let response = http_response(204, &[], SdkBody::empty());
1874 let mut out = Populated::default();
1875 http_output_deserializer(
1876 one_member_body(NO_BODY_MEMBERS, &log),
1877 &response,
1878 &EMPTY_STRUCT,
1879 &ConfigBag::base(),
1880 )
1881 .read_struct(&EMPTY_STRUCT, &mut consume(&mut out))
1882 .unwrap();
1883 assert!(
1884 !log.borrow().read,
1885 "the codec must not be invoked for an empty output"
1886 );
1887 }
1888
1889 #[test]
1890 fn a_bound_output_does_not_take_the_fast_path() {
1891 assert!(!is_body_only(&MIXED));
1892 let log = RefCell::new(BodyLog::default());
1893 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
1894 let response = http_response(201, &[("x-name", b"widget")], SdkBody::from("{}"));
1895 let mut out = Populated::default();
1896 http_output_deserializer(
1897 one_member_body(body_members, &log),
1898 &response,
1899 &MIXED,
1900 &ConfigBag::base(),
1901 )
1902 .read_struct(&MIXED, &mut consume(&mut out))
1903 .unwrap();
1904 assert_eq!(out.strings.get("name"), Some(&"widget".to_string()));
1905 assert_eq!(
1906 out.integers.get("status"),
1907 Some(&201),
1908 "the status must come from the response the helper was given"
1909 );
1910 }
1911
1912 #[test]
1913 fn a_non_structure_schema_does_not_take_the_fast_path() {
1914 assert!(!is_body_only(&crate::prelude::DOCUMENT));
1917 }
1918
1919 #[test]
1933 fn the_error_path_never_takes_the_body_only_fast_path() {
1934 assert!(
1935 is_body_only(&BODY_ONLY),
1936 "precondition: this schema is the fast path's best case"
1937 );
1938 let body_members: &[&Schema<'_>] = &[&BODY_ONLY_FIELD];
1939 let response = || http_response(404, &[], SdkBody::empty());
1940
1941 let error_log = RefCell::new(BodyLog::default());
1943 let mut out = Populated::default();
1944 http_error_deserializer(
1945 one_member_body(body_members, &error_log),
1946 &response(),
1947 &ConfigBag::base(),
1948 )
1949 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1950 .unwrap();
1951 assert!(
1952 !error_log.borrow().read,
1953 "an error must go through the composite even for a body-only schema"
1954 );
1955
1956 let output_log = RefCell::new(BodyLog::default());
1960 let mut out = Populated::default();
1961 http_output_deserializer(
1962 one_member_body(body_members, &output_log),
1963 &response(),
1964 &BODY_ONLY,
1965 &ConfigBag::base(),
1966 )
1967 .read_struct(&BODY_ONLY, &mut consume(&mut out))
1968 .unwrap();
1969 assert!(
1970 output_log.borrow().read,
1971 "precondition: the success helper takes the fast path for this schema"
1972 );
1973 }
1974
1975 #[test]
1976 fn a_binding_beyond_the_mask_still_defeats_the_fast_path() {
1977 let mut leaked: Vec<&'static Schema<'static>> = Vec::new();
1981 for index in 0..64usize {
1982 let name: &'static str = Box::leak(format!("m{index}").into_boxed_str());
1983 let schema = if index == 63 {
1984 Schema::new_member(ID, ShapeType::String, name, index).with_http_header("x-late")
1985 } else {
1986 Schema::new_member(ID, ShapeType::String, name, index)
1987 };
1988 leaked.push(Box::leak(Box::new(schema)));
1989 }
1990 let members: &'static [&'static Schema<'static>] = Box::leak(leaked.into_boxed_slice());
1991 let schema: &'static Schema<'static> = Box::leak(Box::new(Schema::new_struct(
1992 ID,
1993 ShapeType::Structure,
1994 members,
1995 )));
1996 assert_eq!(
1997 schema.response_binding_mask(),
1998 0,
1999 "precondition: the only binding is outside the mask"
2000 );
2001 assert!(schema.response_bindings_need_scan());
2002 assert!(schema.has_response_body_members());
2003 assert!(
2004 !is_body_only(schema),
2005 "an inexact routing word must never be read as body-only"
2006 );
2007
2008 let log = RefCell::new(BodyLog::default());
2009 let response = http_response(200, &[("x-late", b"found")], SdkBody::from("{}"));
2010 let mut out = Populated::default();
2011 http_output_deserializer(
2012 one_member_body(NO_BODY_MEMBERS, &log),
2013 &response,
2014 schema,
2015 &ConfigBag::base(),
2016 )
2017 .read_struct(schema, &mut consume(&mut out))
2018 .unwrap();
2019 assert_eq!(out.strings.get("m63"), Some(&"found".to_string()));
2020 }
2021
2022 #[test]
2023 fn the_error_helper_tolerates_an_empty_body_while_still_reading_bindings() {
2024 let log = RefCell::new(BodyLog::default());
2027 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
2028 let response = http_response(404, &[("x-name", b"missing")], SdkBody::empty());
2029 let mut out = Populated::default();
2030 http_error_deserializer(
2031 one_member_body(body_members, &log),
2032 &response,
2033 &ConfigBag::base(),
2034 )
2035 .read_struct(&MIXED, &mut consume(&mut out))
2036 .unwrap();
2037 assert_eq!(out.strings.get("name"), Some(&"missing".to_string()));
2038 assert_eq!(out.integers.get("status"), Some(&404));
2039 assert!(
2040 !log.borrow().read,
2041 "an empty error body must not be handed to the codec"
2042 );
2043 }
2044
2045 #[test]
2046 fn the_success_helper_keeps_the_codecs_empty_body_strictness() {
2047 let log = RefCell::new(BodyLog::default());
2050 let body_members: &[&Schema<'_>] = &[&BODY_FIELD];
2051 let response = http_response(200, &[("x-name", b"here")], SdkBody::empty());
2052 let mut out = Populated::default();
2053 http_output_deserializer(
2054 one_member_body(body_members, &log),
2055 &response,
2056 &MIXED,
2057 &ConfigBag::base(),
2058 )
2059 .read_struct(&MIXED, &mut consume(&mut out))
2060 .unwrap();
2061 assert!(
2062 log.borrow().read,
2063 "a successful response must still hand an empty body to the codec"
2064 );
2065 }
2066
2067 #[test]
2068 fn the_helpers_read_the_policy_from_the_config_bag() {
2069 let response = http_response(200, &[("x-name", b"value-\xe9")], SdkBody::from("{}"));
2070 let mut cfg = ConfigBag::base();
2071 cfg.interceptor_state()
2072 .store_put(NonUtf8HeaderHandling::Skip);
2073
2074 for label in ["output", "error"] {
2075 let log = RefCell::new(BodyLog::default());
2076 let mut out = Populated::default();
2077 let mut deser = if label == "output" {
2078 http_output_deserializer(
2079 one_member_body(NO_BODY_MEMBERS, &log),
2080 &response,
2081 &FULLY_BOUND,
2082 &cfg,
2083 )
2084 } else {
2085 http_error_deserializer(one_member_body(NO_BODY_MEMBERS, &log), &response, &cfg)
2086 };
2087 deser
2088 .read_struct(&FULLY_BOUND, &mut consume(&mut out))
2089 .unwrap_or_else(|e| panic!("{label}: Skip must reach the composite: {e}"));
2090 assert!(
2091 !out.strings.contains_key("name"),
2092 "{label}: the unreadable member must be absent"
2093 );
2094 }
2095 }
2096
2097 #[test]
2098 fn an_unbuffered_body_is_reported_as_unbuffered_not_as_empty() {
2099 let log = RefCell::new(BodyLog::default());
2104 let response = http_response(200, &[], SdkBody::taken());
2105 assert!(
2106 response.body().bytes().is_none(),
2107 "precondition: the body is not buffered"
2108 );
2109 let mut out = Populated::default();
2110 let err = http_output_deserializer(
2111 one_member_body(NO_BODY_MEMBERS, &log),
2112 &response,
2113 &BLOB_PAYLOAD_SCHEMA,
2114 &ConfigBag::base(),
2115 )
2116 .read_struct(&BLOB_PAYLOAD_SCHEMA, &mut consume(&mut out))
2117 .expect_err("a non-streaming payload cannot be read from an unbuffered body");
2118 assert!(
2119 format!("{err}").contains("was not buffered"),
2120 "unexpected: {err}"
2121 );
2122 }
2123}