1use crate::codec::{Codec, FinishSerializer};
9use crate::protocol::{apply_http_endpoint, ClientProtocolInner};
10use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
11use crate::{Schema, ShapeId};
12use aws_smithy_runtime_api::http::{Headers, Request, Response};
13use aws_smithy_types::body::SdkBody;
14use aws_smithy_types::config_bag::ConfigBag;
15use std::borrow::Cow;
16
17#[derive(Debug)]
28pub struct HttpBindingProtocol<C> {
29 protocol_id: ShapeId,
30 codec: C,
31 content_type: &'static str,
32}
33
34impl<C: Codec> HttpBindingProtocol<C> {
35 pub fn new(protocol_id: ShapeId, codec: C, content_type: &'static str) -> Self {
37 Self {
38 protocol_id,
39 codec,
40 content_type,
41 }
42 }
43
44 pub fn codec(&self) -> &C {
49 &self.codec
50 }
51
52 pub fn serialize_request_with_body(
65 &self,
66 body: <C as Codec>::Serializer,
67 input: &dyn SerializableStruct,
68 input_schema: &Schema,
69 endpoint: &str,
70 cfg: &ConfigBag,
71 ) -> Result<Request, SerdeError> {
72 let mut request = Request::new(SdkBody::empty());
78
79 let has_struct_payload = input_schema.members().iter().any(|m| {
83 m.http_payload().is_some()
84 && matches!(
85 m.shape_type(),
86 crate::ShapeType::Structure | crate::ShapeType::Union
87 )
88 });
89 let skip_body_codec = !input_schema.has_body_members() && !has_struct_payload;
109
110 let (raw_payload, body_bytes, query_params, labels) = {
114 let mut binder =
115 HttpBindingSerializer::new(body, Some(input_schema), request.headers_mut());
116
117 if skip_body_codec || has_struct_payload {
118 binder.is_top_level = false;
125 input.serialize_members(&mut binder)?;
126 } else {
127 binder.write_struct(input_schema, input)?;
128 }
129 let raw_payload = binder.raw_payload;
130 let body_bytes = if raw_payload.is_some() || skip_body_codec {
131 Vec::new()
134 } else {
135 binder.body.finish()
136 };
137 (raw_payload, body_bytes, binder.query_params, binder.labels)
138 };
139
140 let has_blob_or_string_payload = raw_payload.is_some();
145 let has_body_members = has_struct_payload
149 || (input_schema.has_body_members()
150 && input_schema.members().iter().any(|m| {
151 m.http_header().is_none()
152 && m.http_query().is_none()
153 && m.http_label().is_none()
154 && m.http_prefix_headers().is_none()
155 && m.http_query_params().is_none()
156 && m.http_payload().is_none()
157 }));
158
159 let mut body_bytes = body_bytes;
160 let set_content_type = if has_blob_or_string_payload {
161 false
165 } else if has_body_members {
166 true
169 } else {
170 body_bytes = Vec::new();
172 false
173 };
174
175 let template_opt = input_schema.http().map(|h| h.uri());
183 let mut uri =
187 String::with_capacity(endpoint.len() + template_opt.map(|t| t.len()).unwrap_or(1) + 64);
188 match template_opt {
189 Some(template) => {
190 if !endpoint.is_empty() {
191 uri.push_str(endpoint);
192 }
193 append_uri_with_labels(template, &labels, &mut uri);
194 }
195 None => {
196 if endpoint.is_empty() {
197 uri.push('/');
198 } else {
199 append_uri_with_labels(endpoint, &labels, &mut uri);
203 }
204 }
205 }
206 if !query_params.is_empty() {
207 uri.push(if uri.contains('?') { '&' } else { '?' });
208 let mut first = true;
209 for (k, v) in &query_params {
210 if !first {
211 uri.push('&');
212 }
213 percent_encode_into(k, &mut uri);
214 uri.push('=');
215 percent_encode_into(v, &mut uri);
216 first = false;
217 }
218 }
219
220 *request.body_mut() = if let Some(payload) = raw_payload {
223 SdkBody::from(payload)
224 } else {
225 SdkBody::from(body_bytes)
226 };
227 if let Some(http) = input_schema.http() {
229 request
230 .set_method(http.method())
231 .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
232 }
233 request
234 .set_uri(uri.as_str())
235 .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
236 let omit = cfg.load::<crate::header_omit_settings::SharedHeaderOmitSettings>();
246 let omit_content_type = omit
247 .map(|s| s.should_omit_default_content_type())
248 .unwrap_or(false);
249 let omit_content_length = omit
250 .map(|s| s.should_omit_default_content_length())
251 .unwrap_or(false);
252 if !omit_content_type && set_content_type && request.headers().get("Content-Type").is_none()
253 {
254 request
255 .headers_mut()
256 .insert("Content-Type", self.content_type);
257 }
258 if !omit_content_length {
259 if let Some(len) = request.body().content_length() {
260 if (len > 0 || set_content_type)
261 && request.headers().get("Content-Length").is_none()
262 {
263 request
264 .headers_mut()
265 .insert("Content-Length", len.to_string());
266 }
267 }
268 }
269 Ok(request)
270 }
271}
272
273pub fn percent_encode(input: &str) -> String {
277 let mut out = String::with_capacity(input.len());
278 percent_encode_into(input, &mut out);
279 out
280}
281
282pub fn percent_encode_into(input: &str, out: &mut String) {
287 let bytes = input.as_bytes();
288 let mut start = 0usize;
289 for (i, &b) in bytes.iter().enumerate() {
290 let safe = matches!(
291 b,
292 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
293 );
294 if !safe {
295 if start < i {
300 out.push_str(&input[start..i]);
301 }
302 out.push('%');
303 out.push(char::from(HEX[(b >> 4) as usize]));
304 out.push(char::from(HEX[(b & 0x0f) as usize]));
305 start = i + 1;
306 }
307 }
308 if start < bytes.len() {
309 out.push_str(&input[start..]);
310 }
311}
312
313fn append_uri_with_labels(
324 template: &str,
325 labels: &[(Cow<'static, str>, String)],
326 out: &mut String,
327) {
328 let mut rem = template;
329 while let Some(open) = rem.find('{') {
330 out.push_str(&rem[..open]);
331 let after_open = &rem[open + 1..];
332 let close = match after_open.find('}') {
333 Some(c) => c,
334 None => {
335 out.push('{');
337 rem = after_open;
338 continue;
339 }
340 };
341 let label = &after_open[..close];
342 let (name, greedy) = match label.strip_suffix('+') {
343 Some(n) => (n, true),
344 None => (label, false),
345 };
346 let value = labels
348 .iter()
349 .find(|(n, _)| n.as_ref() == name)
350 .map(|(_, v)| v.as_str());
351 if let Some(v) = value {
352 if greedy {
353 let mut first = true;
355 for seg in v.split('/') {
356 if !first {
357 out.push('/');
358 }
359 percent_encode_into(seg, out);
360 first = false;
361 }
362 } else {
363 percent_encode_into(v, out);
364 }
365 }
366 rem = &after_open[close + 1..];
370 }
371 if !rem.is_empty() {
372 out.push_str(rem);
373 }
374}
375
376pub(crate) const HEX: &[u8; 16] = b"0123456789ABCDEF";
377
378struct HttpBindingSerializer<'a, S> {
384 body: S,
385 headers: &'a mut Headers,
390 query_params: Vec<(Cow<'static, str>, String)>,
391 labels: Vec<(Cow<'static, str>, String)>,
392 input_schema: Option<&'a Schema>,
396 is_top_level: bool,
399 raw_payload: Option<&'a [u8]>,
405 visited_bound_members: VisitedMembers,
418}
419
420#[derive(Debug)]
434struct VisitedMembers {
435 inline: [u32; Self::INLINE_CAPACITY],
436 inline_len: u8,
437 overflow: Vec<u32>,
438}
439
440impl VisitedMembers {
441 const INLINE_CAPACITY: usize = 24;
446
447 const fn new() -> Self {
448 Self {
449 inline: [0; Self::INLINE_CAPACITY],
450 inline_len: 0,
451 overflow: Vec::new(),
452 }
453 }
454
455 fn insert(&mut self, idx: usize) -> bool {
458 let idx = idx.min(u32::MAX as usize) as u32;
466 let len = self.inline_len as usize;
467 if self.inline[..len].contains(&idx) {
468 return false;
469 }
470 if !self.overflow.is_empty() && self.overflow.contains(&idx) {
471 return false;
472 }
473 if len < Self::INLINE_CAPACITY {
474 self.inline[len] = idx;
475 self.inline_len += 1;
476 } else {
477 self.overflow.push(idx);
478 }
479 true
480 }
481}
482
483impl<'a, S> HttpBindingSerializer<'a, S> {
484 fn new(body: S, input_schema: Option<&'a Schema>, headers: &'a mut Headers) -> Self {
485 Self {
486 body,
487 headers,
488 query_params: Vec::new(),
489 labels: Vec::new(),
490 input_schema,
491 is_top_level: true,
492 raw_payload: None,
493 visited_bound_members: VisitedMembers::new(),
494 }
495 }
496
497 fn should_route_binding(&mut self, schema: &Schema) -> bool {
509 schema
510 .member_index()
511 .map(|idx| self.visited_bound_members.insert(idx))
512 .unwrap_or(true)
513 }
514
515 fn resolve_member<'s>(&self, schema: &'s Schema) -> &'s Schema
519 where
520 'a: 's,
521 {
522 if let (Some(input_schema), Some(idx)) = (self.input_schema, schema.member_index()) {
523 input_schema.member_schema_by_index(idx).unwrap_or(schema)
524 } else if let (Some(input_schema), Some(name)) = (self.input_schema, schema.member_name()) {
525 input_schema.member_schema(name).unwrap_or(schema)
527 } else {
528 schema
529 }
530 }
531}
532
533impl<'a, S: ShapeSerializer> ShapeSerializer for HttpBindingSerializer<'a, S> {
534 fn write_struct(
535 &mut self,
536 schema: &Schema,
537 value: &dyn SerializableStruct,
538 ) -> Result<(), SerdeError> {
539 if self.is_top_level {
540 struct Proxy<'a, 'b, S> {
545 binder: &'a mut HttpBindingSerializer<'b, S>,
546 value: &'a dyn SerializableStruct,
547 }
548 impl<S: ShapeSerializer> SerializableStruct for Proxy<'_, '_, S> {
549 fn serialize_members(
550 &self,
551 _serializer: &mut dyn ShapeSerializer,
552 ) -> Result<(), SerdeError> {
553 let binder = self.binder as *const HttpBindingSerializer<'_, S>
554 as *mut HttpBindingSerializer<'_, S>;
555 self.value.serialize_members(unsafe { &mut *binder })
565 }
566 }
567 self.is_top_level = false;
571 let proxy = Proxy {
572 binder: self,
573 value,
574 };
575 let binder_ptr = &mut *proxy.binder as *mut HttpBindingSerializer<'_, S>;
576 unsafe { (*binder_ptr).body.write_struct(schema, &proxy) }
587 } else {
588 let schema = self.resolve_member(schema);
591 if schema.http_payload().is_some() {
592 self.body.write_struct(schema, value)?;
596 return Ok(());
597 }
598 self.body.write_struct(schema, value)
599 }
600 }
601
602 fn write_list(
603 &mut self,
604 schema: &Schema,
605 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
606 ) -> Result<(), SerdeError> {
607 let schema = self.resolve_member(schema);
608 if let Some(header) = schema.http_header() {
610 if !self.should_route_binding(schema) {
611 return Ok(());
612 }
613 let mut collector = ListElementCollector::for_header();
614 write_elements(&mut collector)?;
615 let header_val = collector
618 .values
619 .iter()
620 .zip(collector.quotable.iter())
621 .map(|(s, "able)| {
622 if quotable && (s.contains(',') || s.contains('"')) {
623 format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
624 } else {
625 s.clone()
626 }
627 })
628 .collect::<Vec<_>>()
629 .join(", ");
630 self.headers.insert(header.value(), header_val);
631 return Ok(());
632 }
633 if let Some(query) = schema.http_query() {
635 if !self.should_route_binding(schema) {
636 return Ok(());
637 }
638 let mut collector = ListElementCollector::for_query();
639 write_elements(&mut collector)?;
640 for val in collector.values {
641 self.query_params.push((Cow::Borrowed(query.value()), val));
642 }
643 return Ok(());
644 }
645 self.body.write_list(schema, write_elements)
646 }
647
648 fn write_map(
649 &mut self,
650 schema: &Schema,
651 write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
652 ) -> Result<(), SerdeError> {
653 let schema = self.resolve_member(schema);
654 if let Some(prefix) = schema.http_prefix_headers() {
656 if !self.should_route_binding(schema) {
657 return Ok(());
658 }
659 let mut collector = MapEntryCollector::new(prefix.value().to_string());
661 write_entries(&mut collector)?;
662 for (k, v) in collector.entries {
664 self.headers.insert(k, v);
665 }
666 return Ok(());
667 }
668 if schema.http_query_params().is_some() {
670 if !self.should_route_binding(schema) {
671 return Ok(());
672 }
673 let mut collector = MapEntryCollector::new(String::new());
674 write_entries(&mut collector)?;
675 let explicit_query_keys: Vec<&str> = self
678 .input_schema
679 .map(|s| {
680 s.members()
681 .iter()
682 .filter_map(|m| m.http_query().map(|q| q.value()))
683 .collect()
684 })
685 .unwrap_or_default();
686 for (k, v) in collector.entries {
687 if !explicit_query_keys.contains(&k.as_str()) {
688 self.query_params.push((Cow::Owned(k), v));
689 }
690 }
691 return Ok(());
692 }
693 self.body.write_map(schema, write_entries)
694 }
695
696 fn write_boolean(&mut self, schema: &Schema, value: bool) -> Result<(), SerdeError> {
697 let schema = self.resolve_member(schema);
698 if let Some(binding) = http_string_binding(schema) {
699 return self.add_binding(binding, schema, &value.to_string());
700 }
701 self.body.write_boolean(schema, value)
702 }
703
704 fn write_byte(&mut self, schema: &Schema, value: i8) -> Result<(), SerdeError> {
705 let schema = self.resolve_member(schema);
706 if let Some(binding) = http_string_binding(schema) {
707 return self.add_binding(binding, schema, &value.to_string());
708 }
709 self.body.write_byte(schema, value)
710 }
711
712 fn write_short(&mut self, schema: &Schema, value: i16) -> Result<(), SerdeError> {
713 let schema = self.resolve_member(schema);
714 if let Some(binding) = http_string_binding(schema) {
715 return self.add_binding(binding, schema, &value.to_string());
716 }
717 self.body.write_short(schema, value)
718 }
719
720 fn write_integer(&mut self, schema: &Schema, value: i32) -> Result<(), SerdeError> {
721 let schema = self.resolve_member(schema);
722 if let Some(binding) = http_string_binding(schema) {
723 return self.add_binding(binding, schema, &value.to_string());
724 }
725 self.body.write_integer(schema, value)
726 }
727
728 fn write_long(&mut self, schema: &Schema, value: i64) -> Result<(), SerdeError> {
729 let schema = self.resolve_member(schema);
730 if let Some(binding) = http_string_binding(schema) {
731 return self.add_binding(binding, schema, &value.to_string());
732 }
733 self.body.write_long(schema, value)
734 }
735
736 fn write_float(&mut self, schema: &Schema, value: f32) -> Result<(), SerdeError> {
737 let schema = self.resolve_member(schema);
738 if let Some(binding) = http_string_binding(schema) {
739 return self.add_binding(binding, schema, &format_float_f32(value));
740 }
741 self.body.write_float(schema, value)
742 }
743
744 fn write_double(&mut self, schema: &Schema, value: f64) -> Result<(), SerdeError> {
745 let schema = self.resolve_member(schema);
746 if let Some(binding) = http_string_binding(schema) {
747 return self.add_binding(binding, schema, &format_float_f64(value));
748 }
749 self.body.write_double(schema, value)
750 }
751
752 fn write_big_integer(
753 &mut self,
754 schema: &Schema,
755 value: &aws_smithy_types::BigInteger,
756 ) -> Result<(), SerdeError> {
757 let schema = self.resolve_member(schema);
758 if let Some(binding) = http_string_binding(schema) {
759 return self.add_binding(binding, schema, value.as_ref());
760 }
761 self.body.write_big_integer(schema, value)
762 }
763
764 fn write_big_decimal(
765 &mut self,
766 schema: &Schema,
767 value: &aws_smithy_types::BigDecimal,
768 ) -> Result<(), SerdeError> {
769 let schema = self.resolve_member(schema);
770 if let Some(binding) = http_string_binding(schema) {
771 return self.add_binding(binding, schema, value.as_ref());
772 }
773 self.body.write_big_decimal(schema, value)
774 }
775
776 fn write_string(&mut self, schema: &Schema, value: &str) -> Result<(), SerdeError> {
777 let schema = self.resolve_member(schema);
778 if let Some(binding) = http_string_binding(schema) {
779 if schema.media_type().is_some() {
781 let encoded = aws_smithy_types::base64::encode(value.as_bytes());
782 return self.add_binding(binding, schema, &encoded);
783 }
784 return self.add_binding(binding, schema, value);
785 }
786 if schema.http_payload().is_some() {
787 if !self.should_route_binding(schema) {
788 return Ok(());
789 }
790 self.raw_payload =
801 Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value.as_bytes()) });
802 return Ok(());
803 }
804 self.body.write_string(schema, value)
805 }
806
807 fn write_blob(&mut self, schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
808 let schema = self.resolve_member(schema);
809 if schema.http_header().is_some() {
810 if !self.should_route_binding(schema) {
811 return Ok(());
812 }
813 let encoded = aws_smithy_types::base64::encode(value);
814 self.headers
815 .insert(schema.http_header().unwrap().value(), encoded);
816 return Ok(());
817 }
818 if schema.http_payload().is_some() {
819 if !self.should_route_binding(schema) {
820 return Ok(());
821 }
822 self.raw_payload = Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value) });
833 return Ok(());
834 }
835 self.body.write_blob(schema, value)
836 }
837
838 fn write_timestamp(
839 &mut self,
840 schema: &Schema,
841 value: &aws_smithy_types::DateTime,
842 ) -> Result<(), SerdeError> {
843 let schema = self.resolve_member(schema);
844 if let Some(binding) = http_string_binding(schema) {
845 let format = if let Some(ts_trait) = schema.timestamp_format() {
847 match ts_trait.format() {
848 crate::traits::TimestampFormat::EpochSeconds => {
849 aws_smithy_types::date_time::Format::EpochSeconds
850 }
851 crate::traits::TimestampFormat::HttpDate => {
852 aws_smithy_types::date_time::Format::HttpDate
853 }
854 crate::traits::TimestampFormat::DateTime => {
855 aws_smithy_types::date_time::Format::DateTime
856 }
857 }
858 } else {
859 match binding {
860 HttpBinding::Header(_) => aws_smithy_types::date_time::Format::HttpDate,
861 _ => aws_smithy_types::date_time::Format::DateTime,
862 }
863 };
864 let formatted = value
865 .fmt(format)
866 .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?;
867 return self.add_binding(binding, schema, &formatted);
868 }
869 self.body.write_timestamp(schema, value)
870 }
871
872 fn write_document(
873 &mut self,
874 schema: &Schema,
875 value: &aws_smithy_types::Document,
876 ) -> Result<(), SerdeError> {
877 self.body.write_document(schema, value)
878 }
879
880 fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError> {
881 self.body.write_null(schema)
882 }
883}
884
885enum HttpBinding {
887 Header(&'static str),
888 Query(&'static str),
889 Label,
890}
891
892fn http_string_binding(schema: &Schema) -> Option<HttpBinding> {
894 if let Some(h) = schema.http_header() {
895 return Some(HttpBinding::Header(h.value()));
896 }
897 if let Some(q) = schema.http_query() {
898 return Some(HttpBinding::Query(q.value()));
899 }
900 if schema.http_label().is_some() {
901 return Some(HttpBinding::Label);
902 }
903 None
904}
905
906impl<'a, S> HttpBindingSerializer<'a, S> {
907 fn add_binding(
908 &mut self,
909 binding: HttpBinding,
910 schema: &Schema,
911 value: &str,
912 ) -> Result<(), SerdeError> {
913 if !self.should_route_binding(schema) {
917 return Ok(());
918 }
919 match binding {
920 HttpBinding::Header(name) => {
921 self.headers.insert(name, value.to_string());
922 }
923 HttpBinding::Query(name) => {
924 self.query_params
925 .push((Cow::Borrowed(name), value.to_string()));
926 }
927 HttpBinding::Label => {
928 let name = schema
929 .member_name()
930 .ok_or_else(|| SerdeError::custom("httpLabel on non-member schema"))?;
931 self.labels.push((Cow::Borrowed(name), value.to_string()));
932 }
933 }
934 Ok(())
935 }
936}
937
938#[derive(Copy, Clone)]
941enum HttpListTarget {
942 Header,
943 Query,
944}
945
946struct ListElementCollector {
948 values: Vec<String>,
949 quotable: Vec<bool>,
951 target: HttpListTarget,
952}
953
954impl ListElementCollector {
955 fn for_header() -> Self {
956 Self::new(HttpListTarget::Header)
957 }
958
959 fn for_query() -> Self {
960 Self::new(HttpListTarget::Query)
961 }
962
963 fn new(target: HttpListTarget) -> Self {
964 Self {
965 values: Vec::new(),
966 quotable: Vec::new(),
967 target,
968 }
969 }
970
971 fn push(&mut self, value: String) {
972 self.quotable.push(true);
973 self.values.push(value);
974 }
975
976 fn push_unquotable(&mut self, value: String) {
977 self.quotable.push(false);
978 self.values.push(value);
979 }
980}
981
982impl ShapeSerializer for ListElementCollector {
983 fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
984 self.push(value.to_string());
985 Ok(())
986 }
987 fn write_boolean(&mut self, _: &Schema, value: bool) -> Result<(), SerdeError> {
988 self.push(value.to_string());
989 Ok(())
990 }
991 fn write_byte(&mut self, _: &Schema, value: i8) -> Result<(), SerdeError> {
992 self.push(value.to_string());
993 Ok(())
994 }
995 fn write_short(&mut self, _: &Schema, value: i16) -> Result<(), SerdeError> {
996 self.push(value.to_string());
997 Ok(())
998 }
999 fn write_integer(&mut self, _: &Schema, value: i32) -> Result<(), SerdeError> {
1000 self.push(value.to_string());
1001 Ok(())
1002 }
1003 fn write_long(&mut self, _: &Schema, value: i64) -> Result<(), SerdeError> {
1004 self.push(value.to_string());
1005 Ok(())
1006 }
1007 fn write_float(&mut self, _: &Schema, value: f32) -> Result<(), SerdeError> {
1008 self.push(format_float_f32(value));
1009 Ok(())
1010 }
1011 fn write_double(&mut self, _: &Schema, value: f64) -> Result<(), SerdeError> {
1012 self.push(format_float_f64(value));
1013 Ok(())
1014 }
1015 fn write_timestamp(
1016 &mut self,
1017 schema: &Schema,
1018 value: &aws_smithy_types::DateTime,
1019 ) -> Result<(), SerdeError> {
1020 let format = match schema.timestamp_format() {
1021 Some(ts) => match ts.format() {
1022 crate::traits::TimestampFormat::EpochSeconds => {
1023 aws_smithy_types::date_time::Format::EpochSeconds
1024 }
1025 crate::traits::TimestampFormat::HttpDate => {
1026 aws_smithy_types::date_time::Format::HttpDate
1027 }
1028 crate::traits::TimestampFormat::DateTime => {
1029 aws_smithy_types::date_time::Format::DateTime
1030 }
1031 },
1032 None => match self.target {
1034 HttpListTarget::Header => aws_smithy_types::date_time::Format::HttpDate,
1035 HttpListTarget::Query => aws_smithy_types::date_time::Format::DateTime,
1036 },
1037 };
1038 self.push_unquotable(
1039 value
1040 .fmt(format)
1041 .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?,
1042 );
1043 Ok(())
1044 }
1045 fn write_blob(&mut self, _schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
1046 self.push(aws_smithy_types::base64::encode(value));
1047 Ok(())
1048 }
1049 fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
1051 Ok(())
1052 }
1053 fn write_list(
1054 &mut self,
1055 _: &Schema,
1056 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1057 ) -> Result<(), SerdeError> {
1058 Ok(())
1059 }
1060 fn write_map(
1061 &mut self,
1062 _: &Schema,
1063 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1064 ) -> Result<(), SerdeError> {
1065 Ok(())
1066 }
1067 fn write_big_integer(
1068 &mut self,
1069 _: &Schema,
1070 _: &aws_smithy_types::BigInteger,
1071 ) -> Result<(), SerdeError> {
1072 Ok(())
1073 }
1074 fn write_big_decimal(
1075 &mut self,
1076 _: &Schema,
1077 _: &aws_smithy_types::BigDecimal,
1078 ) -> Result<(), SerdeError> {
1079 Ok(())
1080 }
1081 fn write_document(
1082 &mut self,
1083 _: &Schema,
1084 _: &aws_smithy_types::Document,
1085 ) -> Result<(), SerdeError> {
1086 Ok(())
1087 }
1088 fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1089 Ok(())
1090 }
1091}
1092
1093fn format_float_f32(value: f32) -> String {
1096 if value.is_infinite() {
1097 if value.is_sign_positive() {
1098 "Infinity".to_string()
1099 } else {
1100 "-Infinity".to_string()
1101 }
1102 } else if value.is_nan() {
1103 "NaN".to_string()
1104 } else {
1105 value.to_string()
1106 }
1107}
1108
1109fn format_float_f64(value: f64) -> String {
1110 if value.is_infinite() {
1111 if value.is_sign_positive() {
1112 "Infinity".to_string()
1113 } else {
1114 "-Infinity".to_string()
1115 }
1116 } else if value.is_nan() {
1117 "NaN".to_string()
1118 } else {
1119 value.to_string()
1120 }
1121}
1122
1123struct MapEntryCollector {
1126 prefix: String,
1127 entries: Vec<(String, String)>,
1128 pending_key: Option<String>,
1129}
1130
1131impl MapEntryCollector {
1132 fn new(prefix: String) -> Self {
1133 Self {
1134 prefix,
1135 entries: Vec::new(),
1136 pending_key: None,
1137 }
1138 }
1139}
1140
1141impl ShapeSerializer for MapEntryCollector {
1142 fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
1143 if let Some(key) = self.pending_key.take() {
1144 self.entries
1145 .push((format!("{}{}", self.prefix, key), value.to_string()));
1146 } else {
1147 self.pending_key = Some(value.to_string());
1148 }
1149 Ok(())
1150 }
1151
1152 fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
1155 Ok(())
1156 }
1157 fn write_list(
1158 &mut self,
1159 _: &Schema,
1160 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1161 ) -> Result<(), SerdeError> {
1162 if let Some(key) = self.pending_key.take() {
1165 let mut collector = ListElementCollector::for_query(); write_elements(&mut collector)?;
1167 for val in collector.values {
1168 self.entries.push((format!("{}{}", self.prefix, key), val));
1169 }
1170 }
1171 Ok(())
1172 }
1173 fn write_map(
1174 &mut self,
1175 _: &Schema,
1176 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1177 ) -> Result<(), SerdeError> {
1178 Ok(())
1179 }
1180 fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1181 Ok(())
1182 }
1183 fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1184 Ok(())
1185 }
1186 fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1187 Ok(())
1188 }
1189 fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1190 Ok(())
1191 }
1192 fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1193 Ok(())
1194 }
1195 fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1196 Ok(())
1197 }
1198 fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1199 Ok(())
1200 }
1201 fn write_big_integer(
1202 &mut self,
1203 _: &Schema,
1204 _: &aws_smithy_types::BigInteger,
1205 ) -> Result<(), SerdeError> {
1206 Ok(())
1207 }
1208 fn write_big_decimal(
1209 &mut self,
1210 _: &Schema,
1211 _: &aws_smithy_types::BigDecimal,
1212 ) -> Result<(), SerdeError> {
1213 Ok(())
1214 }
1215 fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1216 Ok(())
1217 }
1218 fn write_timestamp(
1219 &mut self,
1220 _: &Schema,
1221 _: &aws_smithy_types::DateTime,
1222 ) -> Result<(), SerdeError> {
1223 Ok(())
1224 }
1225 fn write_document(
1226 &mut self,
1227 _: &Schema,
1228 _: &aws_smithy_types::Document,
1229 ) -> Result<(), SerdeError> {
1230 Ok(())
1231 }
1232 fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1233 Ok(())
1234 }
1235}
1236
1237impl<C> ClientProtocolInner for HttpBindingProtocol<C>
1238where
1239 C: Codec + Send + Sync + std::fmt::Debug + 'static,
1240 for<'a> C::Deserializer<'a>: ShapeDeserializer,
1241{
1242 type Request = Request;
1243 type Response = Response;
1244
1245 fn protocol_id(&self) -> &ShapeId {
1246 &self.protocol_id
1247 }
1248
1249 fn serialize_request(
1250 &self,
1251 input: &dyn SerializableStruct,
1252 input_schema: &Schema,
1253 endpoint: &str,
1254 cfg: &ConfigBag,
1255 ) -> Result<Request, SerdeError> {
1256 let body = self.codec.create_serializer();
1257 self.serialize_request_with_body(body, input, input_schema, endpoint, cfg)
1258 }
1259
1260 fn deserialize_response<'a>(
1261 &self,
1262 response: &'a Response,
1263 _output_schema: &Schema,
1264 _cfg: &ConfigBag,
1265 ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
1266 let body = response.body().bytes().unwrap_or(&[]);
1279 Ok(Box::new(self.codec.create_deserializer(body)))
1280 }
1281
1282 fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
1283 Some(&self.codec)
1284 }
1285
1286 fn update_endpoint(
1287 &self,
1288 request: &mut Request,
1289 endpoint: &aws_smithy_types::endpoint::Endpoint,
1290 cfg: &ConfigBag,
1291 ) -> Result<(), SerdeError> {
1292 apply_http_endpoint(request, endpoint, cfg)
1293 }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299 use crate::serde::SerializableStruct;
1300 use crate::{prelude::*, ShapeType};
1301
1302 #[test]
1303 fn visited_members_inline_dedup() {
1304 let mut v = VisitedMembers::new();
1305 assert!(v.insert(3));
1307 assert!(!v.insert(3));
1308 assert!(v.insert(7));
1310 assert!(v.insert(0));
1311 assert!(!v.insert(3));
1313 assert!(!v.insert(7));
1314 assert!(!v.insert(0));
1315 }
1316
1317 #[test]
1318 fn visited_members_spills_to_overflow() {
1319 let mut v = VisitedMembers::new();
1322 let n = VisitedMembers::INLINE_CAPACITY;
1323 for i in 0..n {
1325 assert!(v.insert(i), "fresh inline insert at {i} must return true");
1326 }
1327 for i in 0..n {
1329 assert!(
1330 !v.insert(i),
1331 "duplicate inline insert at {i} must return false"
1332 );
1333 }
1334 assert!(v.insert(n));
1336 assert!(v.insert(n + 5));
1337 assert!(!v.insert(n));
1339 assert!(!v.insert(n + 5));
1340 assert!(!v.insert(0));
1343 }
1344
1345 struct TestSerializer {
1346 output: Vec<u8>,
1347 }
1348
1349 impl FinishSerializer for TestSerializer {
1350 fn finish(self) -> Vec<u8> {
1351 self.output
1352 }
1353 }
1354
1355 impl ShapeSerializer for TestSerializer {
1356 fn write_struct(
1357 &mut self,
1358 _: &Schema,
1359 value: &dyn SerializableStruct,
1360 ) -> Result<(), SerdeError> {
1361 self.output.push(b'{');
1362 value.serialize_members(self)?;
1363 self.output.push(b'}');
1364 Ok(())
1365 }
1366 fn write_list(
1367 &mut self,
1368 _: &Schema,
1369 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1370 ) -> Result<(), SerdeError> {
1371 Ok(())
1372 }
1373 fn write_map(
1374 &mut self,
1375 _: &Schema,
1376 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1377 ) -> Result<(), SerdeError> {
1378 Ok(())
1379 }
1380 fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1381 Ok(())
1382 }
1383 fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1384 Ok(())
1385 }
1386 fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1387 Ok(())
1388 }
1389 fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1390 Ok(())
1391 }
1392 fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1393 Ok(())
1394 }
1395 fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1396 Ok(())
1397 }
1398 fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1399 Ok(())
1400 }
1401 fn write_big_integer(
1402 &mut self,
1403 _: &Schema,
1404 _: &aws_smithy_types::BigInteger,
1405 ) -> Result<(), SerdeError> {
1406 Ok(())
1407 }
1408 fn write_big_decimal(
1409 &mut self,
1410 _: &Schema,
1411 _: &aws_smithy_types::BigDecimal,
1412 ) -> Result<(), SerdeError> {
1413 Ok(())
1414 }
1415 fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
1416 self.output.extend_from_slice(v.as_bytes());
1417 Ok(())
1418 }
1419 fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1420 Ok(())
1421 }
1422 fn write_timestamp(
1423 &mut self,
1424 _: &Schema,
1425 _: &aws_smithy_types::DateTime,
1426 ) -> Result<(), SerdeError> {
1427 Ok(())
1428 }
1429 fn write_document(
1430 &mut self,
1431 _: &Schema,
1432 _: &aws_smithy_types::Document,
1433 ) -> Result<(), SerdeError> {
1434 Ok(())
1435 }
1436 fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1437 Ok(())
1438 }
1439 }
1440
1441 struct TestDeserializer<'a> {
1442 input: &'a [u8],
1443 }
1444
1445 impl ShapeDeserializer for TestDeserializer<'_> {
1446 fn read_struct(
1447 &mut self,
1448 _: &Schema,
1449 _: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1450 ) -> Result<(), SerdeError> {
1451 Ok(())
1452 }
1453 fn read_list(
1454 &mut self,
1455 _: &Schema,
1456 _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1457 ) -> Result<(), SerdeError> {
1458 Ok(())
1459 }
1460 fn read_map(
1461 &mut self,
1462 _: &Schema,
1463 _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1464 ) -> Result<(), SerdeError> {
1465 Ok(())
1466 }
1467 fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
1468 Ok(false)
1469 }
1470 fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
1471 Ok(0)
1472 }
1473 fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
1474 Ok(0)
1475 }
1476 fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
1477 Ok(0)
1478 }
1479 fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
1480 Ok(0)
1481 }
1482 fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
1483 Ok(0.0)
1484 }
1485 fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
1486 Ok(0.0)
1487 }
1488 fn read_big_integer(
1489 &mut self,
1490 _: &Schema,
1491 ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
1492 use std::str::FromStr;
1493 Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
1494 }
1495 fn read_big_decimal(
1496 &mut self,
1497 _: &Schema,
1498 ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
1499 use std::str::FromStr;
1500 Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
1501 }
1502 fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
1503 Ok(String::from_utf8_lossy(self.input).into_owned())
1504 }
1505 fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
1506 Ok(aws_smithy_types::Blob::new(vec![]))
1507 }
1508 fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
1509 Ok(aws_smithy_types::DateTime::from_secs(0))
1510 }
1511 fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
1512 Ok(aws_smithy_types::Document::Null)
1513 }
1514 fn is_null(&self) -> bool {
1515 false
1516 }
1517 fn container_size(&self) -> Option<usize> {
1518 None
1519 }
1520 }
1521
1522 #[derive(Debug)]
1523 struct TestCodec;
1524
1525 impl Codec for TestCodec {
1526 type Serializer = TestSerializer;
1527 type Deserializer<'a> = TestDeserializer<'a>;
1528 fn create_serializer(&self) -> Self::Serializer {
1529 TestSerializer { output: Vec::new() }
1530 }
1531 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
1532 TestDeserializer { input }
1533 }
1534 }
1535
1536 static TEST_SCHEMA: Schema =
1537 Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
1538
1539 struct EmptyStruct;
1540 impl SerializableStruct for EmptyStruct {
1541 fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1542 Ok(())
1543 }
1544 }
1545
1546 static NAME_MEMBER: Schema = Schema::new_member(
1547 crate::shape_id!("test", "TestStruct"),
1548 ShapeType::String,
1549 "name",
1550 0,
1551 );
1552 static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
1553 static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
1554 crate::shape_id!("test", "TestStruct"),
1555 ShapeType::Structure,
1556 MEMBERS,
1557 );
1558
1559 struct NameStruct;
1560 impl SerializableStruct for NameStruct {
1561 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1562 s.write_string(&NAME_MEMBER, "Alice")
1563 }
1564 }
1565
1566 fn make_protocol() -> HttpBindingProtocol<TestCodec> {
1567 HttpBindingProtocol::new(
1568 crate::shape_id!("test", "proto"),
1569 TestCodec,
1570 "application/test",
1571 )
1572 }
1573
1574 #[test]
1575 fn serialize_sets_content_type() {
1576 let request = make_protocol()
1578 .serialize_request(
1579 &EmptyStruct,
1580 &STRUCT_WITH_MEMBER,
1581 "https://example.com",
1582 &ConfigBag::base(),
1583 )
1584 .unwrap();
1585 assert_eq!(
1586 request.headers().get("Content-Type").unwrap(),
1587 "application/test"
1588 );
1589 }
1590
1591 #[test]
1592 fn serialize_no_body_members_omits_content_type() {
1593 let request = make_protocol()
1595 .serialize_request(
1596 &EmptyStruct,
1597 &TEST_SCHEMA,
1598 "https://example.com",
1599 &ConfigBag::base(),
1600 )
1601 .unwrap();
1602 assert!(request.headers().get("Content-Type").is_none());
1603 }
1604
1605 #[test]
1611 fn presigning_omit_settings_suppress_default_content_headers() {
1612 use crate::header_omit_settings::{HeaderOmitSettings, SharedHeaderOmitSettings};
1613 use aws_smithy_types::config_bag::Layer;
1614
1615 #[derive(Debug)]
1616 struct OmitBoth;
1617 impl HeaderOmitSettings for OmitBoth {
1618 fn should_omit_default_content_type(&self) -> bool {
1619 true
1620 }
1621 fn should_omit_default_content_length(&self) -> bool {
1622 true
1623 }
1624 }
1625
1626 let mut layer = Layer::new("test_omit");
1627 layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
1628 let cfg = ConfigBag::of_layers(vec![layer]);
1629
1630 let request = make_protocol()
1631 .serialize_request(
1632 &NameStruct,
1633 &STRUCT_WITH_MEMBER,
1634 "https://example.com",
1635 &cfg,
1636 )
1637 .unwrap();
1638 assert!(
1639 request.headers().get("Content-Type").is_none(),
1640 "presigning omit suppresses default Content-Type"
1641 );
1642 assert!(
1643 request.headers().get("Content-Length").is_none(),
1644 "presigning omit suppresses default Content-Length"
1645 );
1646 }
1647
1648 #[test]
1655 fn default_content_headers_inserted_when_omit_settings_absent() {
1656 let request = make_protocol()
1657 .serialize_request(
1658 &NameStruct,
1659 &STRUCT_WITH_MEMBER,
1660 "https://example.com",
1661 &ConfigBag::base(),
1662 )
1663 .unwrap();
1664 assert_eq!(
1665 request
1666 .headers()
1667 .get("Content-Type")
1668 .expect("Content-Type set"),
1669 "application/test"
1670 );
1671 assert_eq!(
1672 request
1673 .headers()
1674 .get("Content-Length")
1675 .expect("Content-Length set"),
1676 "7"
1677 );
1678 }
1679
1680 #[test]
1686 fn serialize_skips_body_codec_when_no_body_members() {
1687 use std::sync::atomic::{AtomicUsize, Ordering};
1688
1689 static WRITE_CALLS: AtomicUsize = AtomicUsize::new(0);
1690
1691 struct PanicSerializer;
1692 impl FinishSerializer for PanicSerializer {
1693 fn finish(self) -> Vec<u8> {
1694 panic!("body codec finish() called — short-circuit failed");
1695 }
1696 }
1697 impl ShapeSerializer for PanicSerializer {
1698 fn write_struct(
1699 &mut self,
1700 _: &Schema,
1701 _: &dyn SerializableStruct,
1702 ) -> Result<(), SerdeError> {
1703 WRITE_CALLS.fetch_add(1, Ordering::SeqCst);
1704 panic!("body codec write_struct() called — short-circuit failed");
1705 }
1706 fn write_list(
1707 &mut self,
1708 _: &Schema,
1709 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1710 ) -> Result<(), SerdeError> {
1711 panic!("body codec write_list() called");
1712 }
1713 fn write_map(
1714 &mut self,
1715 _: &Schema,
1716 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1717 ) -> Result<(), SerdeError> {
1718 panic!("body codec write_map() called");
1719 }
1720 fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1721 panic!("body codec write_boolean() called");
1722 }
1723 fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1724 panic!("body codec write_byte() called");
1725 }
1726 fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1727 panic!("body codec write_short() called");
1728 }
1729 fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1730 panic!("body codec write_integer() called");
1731 }
1732 fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1733 panic!("body codec write_long() called");
1734 }
1735 fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1736 panic!("body codec write_float() called");
1737 }
1738 fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1739 panic!("body codec write_double() called");
1740 }
1741 fn write_big_integer(
1742 &mut self,
1743 _: &Schema,
1744 _: &aws_smithy_types::BigInteger,
1745 ) -> Result<(), SerdeError> {
1746 panic!("body codec write_big_integer() called");
1747 }
1748 fn write_big_decimal(
1749 &mut self,
1750 _: &Schema,
1751 _: &aws_smithy_types::BigDecimal,
1752 ) -> Result<(), SerdeError> {
1753 panic!("body codec write_big_decimal() called");
1754 }
1755 fn write_string(&mut self, _: &Schema, _: &str) -> Result<(), SerdeError> {
1756 panic!("body codec write_string() called");
1757 }
1758 fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1759 panic!("body codec write_blob() called");
1760 }
1761 fn write_timestamp(
1762 &mut self,
1763 _: &Schema,
1764 _: &aws_smithy_types::DateTime,
1765 ) -> Result<(), SerdeError> {
1766 panic!("body codec write_timestamp() called");
1767 }
1768 fn write_document(
1769 &mut self,
1770 _: &Schema,
1771 _: &aws_smithy_types::Document,
1772 ) -> Result<(), SerdeError> {
1773 panic!("body codec write_document() called");
1774 }
1775 fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1776 panic!("body codec write_null() called");
1777 }
1778 }
1779
1780 #[derive(Debug)]
1781 struct PanicCodec;
1782 impl Codec for PanicCodec {
1783 type Serializer = PanicSerializer;
1784 type Deserializer<'a> = TestDeserializer<'a>;
1785 fn create_serializer(&self) -> Self::Serializer {
1786 PanicSerializer
1787 }
1788 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
1789 TestDeserializer { input }
1790 }
1791 }
1792
1793 static HEADER_MEMBER: Schema = Schema::new_member(
1797 crate::shape_id!("test", "HeaderOnlyStruct"),
1798 ShapeType::String,
1799 "x_header",
1800 0,
1801 )
1802 .with_http_header("X-Header");
1803 static HEADER_MEMBERS: &[&Schema] = &[&HEADER_MEMBER];
1804 static HEADER_ONLY_SCHEMA: Schema = Schema::new_struct(
1805 crate::shape_id!("test", "HeaderOnlyStruct"),
1806 ShapeType::Structure,
1807 HEADER_MEMBERS,
1808 )
1809 .with_no_body_members();
1810
1811 struct HeaderOnlyStruct;
1812 impl SerializableStruct for HeaderOnlyStruct {
1813 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1814 s.write_string(&HEADER_MEMBER, "hello")
1815 }
1816 }
1817
1818 let protocol = HttpBindingProtocol::new(
1819 crate::shape_id!("test", "testProtocol"),
1820 PanicCodec,
1821 "application/test",
1822 );
1823 let request = protocol
1824 .serialize_request(
1825 &HeaderOnlyStruct,
1826 &HEADER_ONLY_SCHEMA,
1827 "https://example.com",
1828 &ConfigBag::base(),
1829 )
1830 .unwrap();
1831
1832 assert_eq!(request.body().bytes().unwrap_or(&[]), b"");
1834 assert!(request.headers().get("Content-Type").is_none());
1835 assert_eq!(request.headers().get("X-Header").unwrap(), "hello");
1836 assert_eq!(WRITE_CALLS.load(Ordering::SeqCst), 0);
1838 }
1839
1840 #[test]
1845 fn serialize_invokes_body_codec_when_has_body_members() {
1846 let request = make_protocol()
1847 .serialize_request(
1848 &NameStruct,
1849 &STRUCT_WITH_MEMBER,
1850 "https://example.com",
1851 &ConfigBag::base(),
1852 )
1853 .unwrap();
1854 assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
1857 assert_eq!(
1858 request.headers().get("Content-Type").unwrap(),
1859 "application/test"
1860 );
1861 }
1862
1863 #[test]
1864 fn serialize_sets_uri() {
1865 let request = make_protocol()
1866 .serialize_request(
1867 &EmptyStruct,
1868 &TEST_SCHEMA,
1869 "https://example.com/path",
1870 &ConfigBag::base(),
1871 )
1872 .unwrap();
1873 assert_eq!(request.uri(), "https://example.com/path");
1874 }
1875
1876 #[test]
1877 fn serialize_body() {
1878 let request = make_protocol()
1879 .serialize_request(
1880 &NameStruct,
1881 &STRUCT_WITH_MEMBER,
1882 "https://example.com",
1883 &ConfigBag::base(),
1884 )
1885 .unwrap();
1886 assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
1887 }
1888
1889 #[test]
1890 fn deserialize_response() {
1891 let response = Response::new(
1892 200u16.try_into().unwrap(),
1893 SdkBody::from(r#"{"name":"Bob"}"#),
1894 );
1895 let mut deser = make_protocol()
1896 .deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
1897 .unwrap();
1898 assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"name":"Bob"}"#);
1899 }
1900
1901 #[test]
1902 fn update_endpoint() {
1903 let mut request = make_protocol()
1904 .serialize_request(
1905 &EmptyStruct,
1906 &TEST_SCHEMA,
1907 "https://old.example.com",
1908 &ConfigBag::base(),
1909 )
1910 .unwrap();
1911 let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
1912 .url("https://new.example.com")
1913 .build();
1914 make_protocol()
1915 .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
1916 .unwrap();
1917 assert_eq!(request.uri(), "https://new.example.com/");
1918 }
1919
1920 #[test]
1921 fn protocol_id() {
1922 let protocol = HttpBindingProtocol::new(
1923 crate::shape_id!("aws.protocols", "restJson1"),
1924 TestCodec,
1925 "application/json",
1926 );
1927 assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#restJson1");
1928 }
1929
1930 #[test]
1931 fn invalid_uri_returns_error() {
1932 assert!(make_protocol()
1933 .serialize_request(
1934 &EmptyStruct,
1935 &TEST_SCHEMA,
1936 "not a valid uri\n\n",
1937 &ConfigBag::base()
1938 )
1939 .is_err());
1940 }
1941
1942 static HEADER_MEMBER: Schema = Schema::new_member(
1945 crate::shape_id!("test", "S"),
1946 ShapeType::String,
1947 "xToken",
1948 0,
1949 )
1950 .with_http_header("X-Token");
1951
1952 static HEADER_SCHEMA: Schema = Schema::new_struct(
1953 crate::shape_id!("test", "S"),
1954 ShapeType::Structure,
1955 &[&HEADER_MEMBER],
1956 );
1957
1958 struct HeaderStruct;
1959 impl SerializableStruct for HeaderStruct {
1960 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1961 s.write_string(&HEADER_MEMBER, "my-token-value")
1962 }
1963 }
1964
1965 #[test]
1966 fn http_header_string() {
1967 let request = make_protocol()
1968 .serialize_request(
1969 &HeaderStruct,
1970 &HEADER_SCHEMA,
1971 "https://example.com",
1972 &ConfigBag::base(),
1973 )
1974 .unwrap();
1975 assert_eq!(request.headers().get("X-Token").unwrap(), "my-token-value");
1976 }
1977
1978 static INT_HEADER_MEMBER: Schema = Schema::new_member(
1979 crate::shape_id!("test", "S"),
1980 ShapeType::Integer,
1981 "retryCount",
1982 0,
1983 )
1984 .with_http_header("X-Retry-Count");
1985
1986 static INT_HEADER_SCHEMA: Schema = Schema::new_struct(
1987 crate::shape_id!("test", "S"),
1988 ShapeType::Structure,
1989 &[&INT_HEADER_MEMBER],
1990 );
1991
1992 struct IntHeaderStruct;
1993 impl SerializableStruct for IntHeaderStruct {
1994 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1995 s.write_integer(&INT_HEADER_MEMBER, 3)
1996 }
1997 }
1998
1999 #[test]
2000 fn http_header_integer() {
2001 let request = make_protocol()
2002 .serialize_request(
2003 &IntHeaderStruct,
2004 &INT_HEADER_SCHEMA,
2005 "https://example.com",
2006 &ConfigBag::base(),
2007 )
2008 .unwrap();
2009 assert_eq!(request.headers().get("X-Retry-Count").unwrap(), "3");
2010 }
2011
2012 static BOOL_HEADER_MEMBER: Schema = Schema::new_member(
2013 crate::shape_id!("test", "S"),
2014 ShapeType::Boolean,
2015 "verbose",
2016 0,
2017 )
2018 .with_http_header("X-Verbose");
2019
2020 static BOOL_HEADER_SCHEMA: Schema = Schema::new_struct(
2021 crate::shape_id!("test", "S"),
2022 ShapeType::Structure,
2023 &[&BOOL_HEADER_MEMBER],
2024 );
2025
2026 struct BoolHeaderStruct;
2027 impl SerializableStruct for BoolHeaderStruct {
2028 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2029 s.write_boolean(&BOOL_HEADER_MEMBER, true)
2030 }
2031 }
2032
2033 #[test]
2034 fn http_header_boolean() {
2035 let request = make_protocol()
2036 .serialize_request(
2037 &BoolHeaderStruct,
2038 &BOOL_HEADER_SCHEMA,
2039 "https://example.com",
2040 &ConfigBag::base(),
2041 )
2042 .unwrap();
2043 assert_eq!(request.headers().get("X-Verbose").unwrap(), "true");
2044 }
2045
2046 static QUERY_MEMBER: Schema =
2049 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "color", 0)
2050 .with_http_query("color");
2051
2052 static QUERY_SCHEMA: Schema = Schema::new_struct(
2053 crate::shape_id!("test", "S"),
2054 ShapeType::Structure,
2055 &[&QUERY_MEMBER],
2056 );
2057
2058 struct QueryStruct;
2059 impl SerializableStruct for QueryStruct {
2060 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2061 s.write_string(&QUERY_MEMBER, "blue")
2062 }
2063 }
2064
2065 #[test]
2066 fn http_query_string() {
2067 let request = make_protocol()
2068 .serialize_request(
2069 &QueryStruct,
2070 &QUERY_SCHEMA,
2071 "https://example.com/things",
2072 &ConfigBag::base(),
2073 )
2074 .unwrap();
2075 assert_eq!(request.uri(), "https://example.com/things?color=blue");
2076 }
2077
2078 static INT_QUERY_MEMBER: Schema =
2079 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Integer, "size", 0)
2080 .with_http_query("size");
2081
2082 static INT_QUERY_SCHEMA: Schema = Schema::new_struct(
2083 crate::shape_id!("test", "S"),
2084 ShapeType::Structure,
2085 &[&INT_QUERY_MEMBER],
2086 );
2087
2088 struct IntQueryStruct;
2089 impl SerializableStruct for IntQueryStruct {
2090 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2091 s.write_integer(&INT_QUERY_MEMBER, 42)
2092 }
2093 }
2094
2095 #[test]
2096 fn http_query_integer() {
2097 let request = make_protocol()
2098 .serialize_request(
2099 &IntQueryStruct,
2100 &INT_QUERY_SCHEMA,
2101 "https://example.com/things",
2102 &ConfigBag::base(),
2103 )
2104 .unwrap();
2105 assert_eq!(request.uri(), "https://example.com/things?size=42");
2106 }
2107
2108 static Q1: Schema =
2111 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "a", 0)
2112 .with_http_query("a");
2113 static Q2: Schema =
2114 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "b", 1)
2115 .with_http_query("b");
2116 static MULTI_QUERY_SCHEMA: Schema = Schema::new_struct(
2117 crate::shape_id!("test", "S"),
2118 ShapeType::Structure,
2119 &[&Q1, &Q2],
2120 );
2121
2122 struct MultiQueryStruct;
2123 impl SerializableStruct for MultiQueryStruct {
2124 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2125 s.write_string(&Q1, "x")?;
2126 s.write_string(&Q2, "y")
2127 }
2128 }
2129
2130 #[test]
2131 fn http_query_multiple_params() {
2132 let request = make_protocol()
2133 .serialize_request(
2134 &MultiQueryStruct,
2135 &MULTI_QUERY_SCHEMA,
2136 "https://example.com",
2137 &ConfigBag::base(),
2138 )
2139 .unwrap();
2140 assert_eq!(request.uri(), "https://example.com?a=x&b=y");
2141 }
2142
2143 #[test]
2146 fn http_query_percent_encodes_values() {
2147 struct SpaceQueryStruct;
2148 impl SerializableStruct for SpaceQueryStruct {
2149 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2150 s.write_string(&QUERY_MEMBER, "hello world")
2151 }
2152 }
2153 let request = make_protocol()
2154 .serialize_request(
2155 &SpaceQueryStruct,
2156 &QUERY_SCHEMA,
2157 "https://example.com",
2158 &ConfigBag::base(),
2159 )
2160 .unwrap();
2161 assert_eq!(request.uri(), "https://example.com?color=hello%20world");
2162 }
2163
2164 static LABEL_MEMBER: Schema = Schema::new_member(
2167 crate::shape_id!("test", "S"),
2168 ShapeType::String,
2169 "bucketName",
2170 0,
2171 )
2172 .with_http_label();
2173
2174 static LABEL_SCHEMA: Schema = Schema::new_struct(
2175 crate::shape_id!("test", "S"),
2176 ShapeType::Structure,
2177 &[&LABEL_MEMBER],
2178 );
2179
2180 struct LabelStruct;
2181 impl SerializableStruct for LabelStruct {
2182 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2183 s.write_string(&LABEL_MEMBER, "my-bucket")
2184 }
2185 }
2186
2187 #[test]
2188 fn http_label_substitution() {
2189 let request = make_protocol()
2190 .serialize_request(
2191 &LabelStruct,
2192 &LABEL_SCHEMA,
2193 "https://example.com/{bucketName}/objects",
2194 &ConfigBag::base(),
2195 )
2196 .unwrap();
2197 assert_eq!(request.uri(), "https://example.com/my-bucket/objects");
2198 }
2199
2200 #[test]
2201 fn http_label_percent_encodes() {
2202 struct SpecialLabelStruct;
2203 impl SerializableStruct for SpecialLabelStruct {
2204 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2205 s.write_string(&LABEL_MEMBER, "my bucket/name")
2206 }
2207 }
2208 let request = make_protocol()
2209 .serialize_request(
2210 &SpecialLabelStruct,
2211 &LABEL_SCHEMA,
2212 "https://example.com/{bucketName}",
2213 &ConfigBag::base(),
2214 )
2215 .unwrap();
2216 assert!(request.uri().contains("my%20bucket%2Fname"));
2217 }
2218
2219 static INT_LABEL_MEMBER: Schema = Schema::new_member(
2220 crate::shape_id!("test", "S"),
2221 ShapeType::Integer,
2222 "itemId",
2223 0,
2224 )
2225 .with_http_label();
2226
2227 static INT_LABEL_SCHEMA: Schema = Schema::new_struct(
2228 crate::shape_id!("test", "S"),
2229 ShapeType::Structure,
2230 &[&INT_LABEL_MEMBER],
2231 );
2232
2233 struct IntLabelStruct;
2234 impl SerializableStruct for IntLabelStruct {
2235 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2236 s.write_integer(&INT_LABEL_MEMBER, 123)
2237 }
2238 }
2239
2240 #[test]
2241 fn http_label_integer() {
2242 let request = make_protocol()
2243 .serialize_request(
2244 &IntLabelStruct,
2245 &INT_LABEL_SCHEMA,
2246 "https://example.com/items/{itemId}",
2247 &ConfigBag::base(),
2248 )
2249 .unwrap();
2250 assert_eq!(request.uri(), "https://example.com/items/123");
2251 }
2252
2253 static COMBINED_LABEL: Schema =
2256 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "id", 0)
2257 .with_http_label();
2258 static COMBINED_HEADER: Schema =
2259 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "token", 1)
2260 .with_http_header("X-Token");
2261 static COMBINED_QUERY: Schema = Schema::new_member(
2262 crate::shape_id!("test", "S"),
2263 ShapeType::String,
2264 "filter",
2265 2,
2266 )
2267 .with_http_query("filter");
2268 static COMBINED_BODY: Schema =
2269 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "data", 3);
2270 static COMBINED_SCHEMA: Schema = Schema::new_struct(
2271 crate::shape_id!("test", "S"),
2272 ShapeType::Structure,
2273 &[
2274 &COMBINED_LABEL,
2275 &COMBINED_HEADER,
2276 &COMBINED_QUERY,
2277 &COMBINED_BODY,
2278 ],
2279 );
2280
2281 struct CombinedStruct;
2282 impl SerializableStruct for CombinedStruct {
2283 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2284 s.write_string(&COMBINED_LABEL, "item-42")?;
2285 s.write_string(&COMBINED_HEADER, "secret")?;
2286 s.write_string(&COMBINED_QUERY, "active")?;
2287 s.write_string(&COMBINED_BODY, "payload-data")
2288 }
2289 }
2290
2291 #[test]
2292 fn combined_bindings() {
2293 let request = make_protocol()
2294 .serialize_request(
2295 &CombinedStruct,
2296 &COMBINED_SCHEMA,
2297 "https://example.com/{id}/details",
2298 &ConfigBag::base(),
2299 )
2300 .unwrap();
2301 assert_eq!(
2302 request.uri(),
2303 "https://example.com/item-42/details?filter=active"
2304 );
2305 assert_eq!(request.headers().get("X-Token").unwrap(), "secret");
2307 let body = request.body().bytes().unwrap();
2309 assert!(body
2310 .windows(b"payload-data".len())
2311 .any(|w| w == b"payload-data"));
2312 }
2313
2314 static PREFIX_MEMBER: Schema =
2317 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "metadata", 0)
2318 .with_http_prefix_headers("X-Meta-");
2319
2320 static PREFIX_SCHEMA: Schema = Schema::new_struct(
2321 crate::shape_id!("test", "S"),
2322 ShapeType::Structure,
2323 &[&PREFIX_MEMBER],
2324 );
2325
2326 struct PrefixHeaderStruct;
2327 impl SerializableStruct for PrefixHeaderStruct {
2328 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2329 s.write_map(&PREFIX_MEMBER, &|s| {
2330 s.write_string(&STRING, "Color")?;
2331 s.write_string(&STRING, "red")?;
2332 s.write_string(&STRING, "Size")?;
2333 s.write_string(&STRING, "large")?;
2334 Ok(())
2335 })
2336 }
2337 }
2338
2339 #[test]
2340 fn http_prefix_headers() {
2341 let request = make_protocol()
2342 .serialize_request(
2343 &PrefixHeaderStruct,
2344 &PREFIX_SCHEMA,
2345 "https://example.com",
2346 &ConfigBag::base(),
2347 )
2348 .unwrap();
2349 assert_eq!(request.headers().get("X-Meta-Color").unwrap(), "red");
2350 assert_eq!(request.headers().get("X-Meta-Size").unwrap(), "large");
2351 }
2352
2353 static QUERY_PARAMS_MEMBER: Schema =
2356 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "params", 0)
2357 .with_http_query_params();
2358
2359 static QUERY_PARAMS_SCHEMA: Schema = Schema::new_struct(
2360 crate::shape_id!("test", "S"),
2361 ShapeType::Structure,
2362 &[&QUERY_PARAMS_MEMBER],
2363 );
2364
2365 struct QueryParamsStruct;
2366 impl SerializableStruct for QueryParamsStruct {
2367 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2368 s.write_map(&QUERY_PARAMS_MEMBER, &|s| {
2369 s.write_string(&STRING, "page")?;
2370 s.write_string(&STRING, "2")?;
2371 s.write_string(&STRING, "limit")?;
2372 s.write_string(&STRING, "50")?;
2373 Ok(())
2374 })
2375 }
2376 }
2377
2378 #[test]
2379 fn http_query_params() {
2380 let request = make_protocol()
2381 .serialize_request(
2382 &QueryParamsStruct,
2383 &QUERY_PARAMS_SCHEMA,
2384 "https://example.com",
2385 &ConfigBag::base(),
2386 )
2387 .unwrap();
2388 assert_eq!(request.uri(), "https://example.com?page=2&limit=50");
2389 }
2390
2391 static TS_HEADER_MEMBER: Schema = Schema::new_member(
2394 crate::shape_id!("test", "S"),
2395 ShapeType::Timestamp,
2396 "ifModified",
2397 0,
2398 )
2399 .with_http_header("If-Modified-Since");
2400
2401 static TS_HEADER_SCHEMA: Schema = Schema::new_struct(
2402 crate::shape_id!("test", "S"),
2403 ShapeType::Structure,
2404 &[&TS_HEADER_MEMBER],
2405 );
2406
2407 struct TimestampHeaderStruct;
2408 impl SerializableStruct for TimestampHeaderStruct {
2409 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2410 s.write_timestamp(&TS_HEADER_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
2411 }
2412 }
2413
2414 #[test]
2415 fn timestamp_header_uses_http_date() {
2416 let request = make_protocol()
2417 .serialize_request(
2418 &TimestampHeaderStruct,
2419 &TS_HEADER_SCHEMA,
2420 "https://example.com",
2421 &ConfigBag::base(),
2422 )
2423 .unwrap();
2424 let value = request.headers().get("If-Modified-Since").unwrap();
2425 assert!(value.contains("1970"), "expected http-date, got: {value}");
2427 }
2428
2429 static TS_QUERY_MEMBER: Schema = Schema::new_member(
2432 crate::shape_id!("test", "S"),
2433 ShapeType::Timestamp,
2434 "since",
2435 0,
2436 )
2437 .with_http_query("since");
2438
2439 static TS_QUERY_SCHEMA: Schema = Schema::new_struct(
2440 crate::shape_id!("test", "S"),
2441 ShapeType::Structure,
2442 &[&TS_QUERY_MEMBER],
2443 );
2444
2445 struct TimestampQueryStruct;
2446 impl SerializableStruct for TimestampQueryStruct {
2447 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2448 s.write_timestamp(&TS_QUERY_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
2449 }
2450 }
2451
2452 #[test]
2453 fn timestamp_query_uses_date_time() {
2454 let request = make_protocol()
2455 .serialize_request(
2456 &TimestampQueryStruct,
2457 &TS_QUERY_SCHEMA,
2458 "https://example.com",
2459 &ConfigBag::base(),
2460 )
2461 .unwrap();
2462 assert_eq!(
2463 request.uri(),
2464 "https://example.com?since=1970-01-01T00%3A00%3A00Z"
2465 );
2466 }
2467
2468 static BOUND_MEMBER: Schema = Schema::new_member(
2471 crate::shape_id!("test", "S"),
2472 ShapeType::String,
2473 "headerVal",
2474 0,
2475 )
2476 .with_http_header("X-Val");
2477 static UNBOUND_MEMBER: Schema = Schema::new_member(
2478 crate::shape_id!("test", "S"),
2479 ShapeType::String,
2480 "bodyVal",
2481 1,
2482 );
2483 static MIXED_SCHEMA: Schema = Schema::new_struct(
2484 crate::shape_id!("test", "S"),
2485 ShapeType::Structure,
2486 &[&BOUND_MEMBER, &UNBOUND_MEMBER],
2487 );
2488
2489 struct MixedStruct;
2490 impl SerializableStruct for MixedStruct {
2491 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2492 s.write_string(&BOUND_MEMBER, "in-header")?;
2493 s.write_string(&UNBOUND_MEMBER, "in-body")
2494 }
2495 }
2496
2497 #[test]
2498 fn bound_members_not_in_body() {
2499 let request = make_protocol()
2500 .serialize_request(
2501 &MixedStruct,
2502 &MIXED_SCHEMA,
2503 "https://example.com",
2504 &ConfigBag::base(),
2505 )
2506 .unwrap();
2507 let body = std::str::from_utf8(request.body().bytes().unwrap()).unwrap();
2508 assert!(
2509 body.contains("in-body"),
2510 "body should contain unbound member"
2511 );
2512 assert!(
2513 !body.contains("in-header"),
2514 "body should NOT contain header-bound member"
2515 );
2516 assert_eq!(request.headers().get("X-Val").unwrap(), "in-header");
2517 }
2518}