1use super::response::{http_error_deserializer, http_output_deserializer};
9use crate::codec::{Codec, FinishSerializer};
10use crate::protocol::{apply_http_endpoint, ClientProtocolInner};
11use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
12use crate::{Schema, ShapeId};
13use aws_smithy_runtime_api::http::{Headers, Request, Response};
14use aws_smithy_types::body::SdkBody;
15use aws_smithy_types::config_bag::ConfigBag;
16use std::borrow::Cow;
17use std::cell::Cell;
18
19#[derive(Debug)]
30pub struct HttpBindingProtocol<C> {
31 protocol_id: ShapeId<'static>,
32 codec: C,
33 content_type: &'static str,
34}
35
36impl<C: Codec> HttpBindingProtocol<C> {
37 pub fn new(protocol_id: ShapeId<'static>, codec: C, content_type: &'static str) -> Self {
39 Self {
40 protocol_id,
41 codec,
42 content_type,
43 }
44 }
45
46 pub fn codec(&self) -> &C {
51 &self.codec
52 }
53
54 pub fn content_type(&self) -> &'static str {
58 self.content_type
59 }
60
61 pub fn with_codec(self, codec: C) -> Self {
65 Self {
66 protocol_id: self.protocol_id,
67 codec,
68 content_type: self.content_type,
69 }
70 }
71
72 pub fn serialize_request_with_body(
85 &self,
86 body: <C as Codec>::Serializer,
87 input: &dyn SerializableStruct,
88 input_schema: &Schema<'_>,
89 endpoint: &str,
90 cfg: &ConfigBag,
91 ) -> Result<Request, SerdeError> {
92 let mut request = Request::new(SdkBody::empty());
98
99 let has_struct_payload = match input_schema.payload_hint() {
111 crate::PayloadHint::StructPayload => true,
112 crate::PayloadHint::NoStructPayload => false,
113 _ => input_schema.members().iter().any(|m| {
114 m.http_payload().is_some()
115 && matches!(
116 m.shape_type(),
117 crate::ShapeType::Structure | crate::ShapeType::Union
118 )
119 }),
120 };
121 let skip_body_codec = !input_schema.has_body_members() && !has_struct_payload;
141
142 let (raw_payload, body_bytes, query_params, labels) = {
146 let mut body = body;
147 let mut state = BindingState::new(Some(input_schema), request.headers_mut());
148
149 if skip_body_codec || has_struct_payload {
150 let mut router = BindingRouter {
160 state: &mut state,
161 body: &mut body,
162 };
163 input.serialize_members(&mut router)?;
164 } else {
165 let proxy = Proxy {
175 state: Cell::new(Some(&mut state)),
176 value: input,
177 };
178 body.write_struct(input_schema, &proxy)?;
179 }
180 let raw_payload = state.raw_payload;
181 let body_bytes = if raw_payload.is_some() || skip_body_codec {
182 Vec::new()
185 } else {
186 body.finish()
187 };
188 (raw_payload, body_bytes, state.query_params, state.labels)
189 };
190
191 let has_blob_or_string_payload = raw_payload.is_some();
196 let has_body_members = has_struct_payload
200 || (input_schema.has_body_members()
201 && input_schema.members().iter().any(|m| {
202 m.http_header().is_none()
203 && m.http_query().is_none()
204 && m.http_label().is_none()
205 && m.http_prefix_headers().is_none()
206 && m.http_query_params().is_none()
207 && m.http_payload().is_none()
208 }));
209
210 let mut body_bytes = body_bytes;
211 let set_content_type = if has_blob_or_string_payload {
212 false
216 } else if has_body_members {
217 true
220 } else {
221 body_bytes = Vec::new();
223 false
224 };
225
226 let template_opt = input_schema.http().map(|h| h.uri());
234 let mut uri =
239 String::with_capacity(endpoint.len() + template_opt.map(|t| t.len()).unwrap_or(1) + 64);
240 match template_opt {
241 Some(template) => {
242 append_uri_with_labels(template, &labels, &mut uri)?;
252 }
253 None => {
254 if endpoint.is_empty() {
255 uri.push('/');
256 } else {
257 append_uri_with_labels(endpoint, &labels, &mut uri)?;
261 }
262 }
263 }
264 if !query_params.is_empty() {
265 uri.push(if uri.contains('?') { '&' } else { '?' });
266 let mut first = true;
267 for (k, v) in &query_params {
268 if !first {
269 uri.push('&');
270 }
271 percent_encode_into(k, &mut uri);
272 uri.push('=');
273 percent_encode_into(v, &mut uri);
274 first = false;
275 }
276 }
277
278 *request.body_mut() = if let Some(payload) = raw_payload {
281 SdkBody::from(payload.into_bytes())
284 } else {
285 SdkBody::from(body_bytes)
286 };
287 if let Some(http) = input_schema.http() {
289 request
290 .set_method(http.method())
291 .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
292 }
293 request
294 .set_uri(uri.as_str())
295 .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
296 let omit = cfg.load::<crate::header_omit_settings::SharedHeaderOmitSettings>();
306 let omit_content_type = omit
307 .map(|s| s.should_omit_default_content_type())
308 .unwrap_or(false);
309 let omit_content_length = omit
310 .map(|s| s.should_omit_default_content_length())
311 .unwrap_or(false);
312 if !omit_content_type && set_content_type && request.headers().get("Content-Type").is_none()
313 {
314 request
315 .headers_mut()
316 .insert("Content-Type", self.content_type);
317 }
318 if !omit_content_length {
319 if let Some(len) = request.body().content_length() {
320 if (len > 0 || set_content_type)
321 && request.headers().get("Content-Length").is_none()
322 {
323 request
324 .headers_mut()
325 .insert("Content-Length", len.to_string());
326 }
327 }
328 }
329 Ok(request)
330 }
331}
332
333pub fn percent_encode(input: &str) -> String {
337 let mut out = String::with_capacity(input.len());
338 percent_encode_into(input, &mut out);
339 out
340}
341
342pub(crate) fn percent_encode_into(input: &str, out: &mut String) {
347 let bytes = input.as_bytes();
348 let mut start = 0usize;
349 for (i, &b) in bytes.iter().enumerate() {
350 let safe = matches!(
351 b,
352 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
353 );
354 if !safe {
355 if start < i {
360 out.push_str(&input[start..i]);
361 }
362 out.push('%');
363 out.push(char::from(HEX[(b >> 4) as usize]));
364 out.push(char::from(HEX[(b & 0x0f) as usize]));
365 start = i + 1;
366 }
367 }
368 if start < bytes.len() {
369 out.push_str(&input[start..]);
370 }
371}
372
373fn append_uri_with_labels<'sc>(
389 template: &str,
390 labels: &[(Cow<'sc, str>, String)],
391 out: &mut String,
392) -> Result<(), SerdeError> {
393 let mut rem = template;
394 while let Some(open) = rem.find('{') {
395 out.push_str(&rem[..open]);
396 let after_open = &rem[open + 1..];
397 let close = match after_open.find('}') {
398 Some(c) => c,
399 None => {
400 out.push('{');
402 rem = after_open;
403 continue;
404 }
405 };
406 let label = &after_open[..close];
407 let (name, greedy) = match label.strip_suffix('+') {
408 Some(n) => (n, true),
409 None => (label, false),
410 };
411 let value = labels
413 .iter()
414 .find(|(n, _)| n.as_ref() == name)
415 .map(|(_, v)| v.as_str());
416 let v = match value {
417 Some(v) if !v.is_empty() => v,
418 _ => {
419 return Err(SerdeError::invalid_input(format!(
420 "{name} was missing: cannot be empty or unset"
421 )))
422 }
423 };
424 if greedy {
425 let mut first = true;
427 for seg in v.split('/') {
428 if !first {
429 out.push('/');
430 }
431 percent_encode_into(seg, out);
432 first = false;
433 }
434 } else {
435 percent_encode_into(v, out);
436 }
437 rem = &after_open[close + 1..];
438 }
439 if !rem.is_empty() {
440 out.push_str(rem);
441 }
442 Ok(())
443}
444
445pub(crate) const HEX: &[u8; 16] = b"0123456789ABCDEF";
446
447struct BindingState<'a> {
462 headers: &'a mut Headers,
467 query_params: Vec<(Cow<'a, str>, String)>,
468 labels: Vec<(Cow<'a, str>, String)>,
469 input_schema: Option<&'a Schema<'a>>,
473 raw_payload: Option<aws_smithy_types::Blob>,
490 visited_bound_members: VisitedMembers,
503}
504
505#[derive(Debug)]
519struct VisitedMembers {
520 inline: [u32; Self::INLINE_CAPACITY],
521 inline_len: u8,
522 overflow: Vec<u32>,
523}
524
525impl VisitedMembers {
526 const INLINE_CAPACITY: usize = 24;
531
532 const fn new() -> Self {
533 Self {
534 inline: [0; Self::INLINE_CAPACITY],
535 inline_len: 0,
536 overflow: Vec::new(),
537 }
538 }
539
540 fn insert(&mut self, idx: usize) -> bool {
543 let idx = idx.min(u32::MAX as usize) as u32;
551 let len = self.inline_len as usize;
552 if self.inline[..len].contains(&idx) {
553 return false;
554 }
555 if !self.overflow.is_empty() && self.overflow.contains(&idx) {
556 return false;
557 }
558 if len < Self::INLINE_CAPACITY {
559 self.inline[len] = idx;
560 self.inline_len += 1;
561 } else {
562 self.overflow.push(idx);
563 }
564 true
565 }
566}
567
568impl<'a> BindingState<'a> {
569 fn new(input_schema: Option<&'a Schema<'a>>, headers: &'a mut Headers) -> Self {
570 Self {
571 headers,
572 query_params: Vec::new(),
573 labels: Vec::new(),
574 input_schema,
575 raw_payload: None,
576 visited_bound_members: VisitedMembers::new(),
577 }
578 }
579
580 fn should_route_binding(&mut self, schema: &Schema<'_>) -> bool {
592 schema
593 .member_index()
594 .map(|idx| self.visited_bound_members.insert(idx))
595 .unwrap_or(true)
596 }
597
598 fn resolve_member<'s>(&self, schema: &'s Schema<'s>) -> &'s Schema<'s>
602 where
603 'a: 's,
604 {
605 if let (Some(input_schema), Some(idx)) = (self.input_schema, schema.member_index()) {
606 input_schema.member_schema_by_index(idx).unwrap_or(schema)
607 } else if let (Some(input_schema), Some(name)) = (self.input_schema, schema.member_name()) {
608 input_schema.member_schema(name).unwrap_or(schema)
610 } else {
611 schema
612 }
613 }
614
615 fn resolve_to_input_schema(&self, schema: &Schema<'_>) -> Option<&'a Schema<'a>> {
622 let input_schema = self.input_schema?;
623 if let Some(idx) = schema.member_index() {
624 if let Some(s) = input_schema.member_schema_by_index(idx) {
625 return Some(s);
626 }
627 }
628 if let Some(name) = schema.member_name() {
629 return input_schema.member_schema(name);
630 }
631 None
632 }
633
634 fn query_param_name(
644 &self,
645 schema: &Schema<'_>,
646 query: &crate::traits::HttpQueryTrait<'_>,
647 ) -> Cow<'a, str> {
648 match self
649 .resolve_to_input_schema(schema)
650 .and_then(|resolved| resolved.http_query())
651 {
652 Some(resolved_query) => Cow::Borrowed(resolved_query.value()),
653 None => Cow::Owned(query.value().to_string()),
654 }
655 }
656}
657
658fn insert_header(
666 headers: &mut Headers,
667 name: Cow<'static, str>,
668 value: String,
669 member: &Schema<'_>,
670 location: std::fmt::Arguments<'_>,
671) -> Result<(), SerdeError> {
672 headers
673 .try_insert(name, value)
674 .map(|_| ())
675 .map_err(|err| header_error(member, location, err))
676}
677
678#[cold]
679fn header_error(
680 member: &Schema<'_>,
681 location: std::fmt::Arguments<'_>,
682 err: aws_smithy_runtime_api::http::HttpError,
683) -> SerdeError {
684 SerdeError::invalid_input(format!(
685 "cannot serialize {} to {location}: {err}",
686 member.member_name().unwrap_or("member"),
687 ))
688}
689
690pub(crate) static HTTP_HEADER_NAME: crate::extension::SchemaExtensionKey<Option<http::HeaderName>> =
701 crate::extension::SchemaExtensionKey::new(|member| {
702 member
703 .http_header()
704 .and_then(|header| http::HeaderName::try_from(header.value()).ok())
705 });
706
707fn insert_bound_header(
710 headers: &mut Headers,
711 member: &Schema<'_>,
712 header: &crate::traits::HttpHeaderTrait<'_>,
713 value: String,
714) -> Result<(), SerdeError> {
715 match member.extension(&HTTP_HEADER_NAME) {
716 Some(name) => headers
717 .try_insert(name.clone(), value)
718 .map(|_| ())
719 .map_err(|err| header_error(member, format_args!("header `{}`", header.value()), err)),
720 None => insert_header(
721 headers,
722 header_name(header),
723 value,
724 member,
725 format_args!("header `{}`", header.value()),
726 ),
727 }
728}
729
730fn header_name(header: &crate::traits::HttpHeaderTrait<'_>) -> Cow<'static, str> {
739 match header.value_static() {
740 Some(name) => Cow::Borrowed(name),
741 None => Cow::Owned(header.value().to_string()),
742 }
743}
744
745struct BindingRouter<'s, 'b, 'a> {
760 state: &'s mut BindingState<'a>,
761 body: &'b mut dyn ShapeSerializer,
762}
763
764struct Proxy<'p, 'a> {
777 state: Cell<Option<&'p mut BindingState<'a>>>,
778 value: &'p dyn SerializableStruct,
779}
780
781impl<'p, 'a> SerializableStruct for Proxy<'p, 'a> {
782 fn schema(&self) -> &Schema<'_> {
783 self.value.schema()
784 }
785
786 fn serialize_members(&self, serializer: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
787 let state = self.state.take().ok_or_else(|| {
795 SerdeError::custom(
796 "HTTP binding state is already borrowed: the body codec re-entered \
797 serialize_members on the same struct before the previous call returned",
798 )
799 })?;
800 let mut router = BindingRouter {
801 state,
802 body: serializer,
803 };
804 let result = self.value.serialize_members(&mut router);
805 self.state.set(Some(router.state));
808 result
809 }
810}
811
812impl<'s, 'b, 'a> ShapeSerializer for BindingRouter<'s, 'b, 'a> {
813 fn write_struct(
814 &mut self,
815 schema: &Schema<'_>,
816 value: &dyn SerializableStruct,
817 ) -> Result<(), SerdeError> {
818 let schema = self.state.resolve_member(schema);
823 self.body.write_struct(schema, value)
828 }
829
830 fn write_list(
831 &mut self,
832 schema: &Schema<'_>,
833 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
834 ) -> Result<(), SerdeError> {
835 let schema = self.state.resolve_member(schema);
836 if let Some(header) = schema.http_header() {
838 if !self.state.should_route_binding(schema) {
839 return Ok(());
840 }
841 let mut collector = ListElementCollector::for_header();
842 write_elements(&mut collector)?;
843 let header_val = collector
847 .values
848 .iter()
849 .zip(collector.quotable.iter())
850 .map(|(s, "able)| {
851 if quotable {
852 aws_smithy_runtime_api::http::header_parse::quote_header_value(s.as_str())
853 .into_owned()
854 } else {
855 s.clone()
856 }
857 })
858 .collect::<Vec<_>>()
859 .join(", ");
860 insert_bound_header(self.state.headers, schema, header, header_val)?;
861 return Ok(());
862 }
863 if let Some(query) = schema.http_query() {
865 if !self.state.should_route_binding(schema) {
866 return Ok(());
867 }
868 let mut collector = ListElementCollector::for_query();
869 write_elements(&mut collector)?;
870 let name = self.state.query_param_name(schema, query);
876 for val in collector.values {
877 self.state.query_params.push((name.clone(), val));
878 }
879 return Ok(());
880 }
881 self.body.write_list(schema, write_elements)
882 }
883
884 fn write_map(
885 &mut self,
886 schema: &Schema<'_>,
887 write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
888 ) -> Result<(), SerdeError> {
889 let schema = self.state.resolve_member(schema);
890 if let Some(prefix) = schema.http_prefix_headers() {
892 if !self.state.should_route_binding(schema) {
893 return Ok(());
894 }
895 let mut collector = MapEntryCollector::new(prefix.value().to_string());
897 write_entries(&mut collector)?;
898 for (k, v) in collector.entries {
900 insert_header(
901 self.state.headers,
902 Cow::Owned(k),
903 v,
904 schema,
905 format_args!("headers with prefix `{}`", prefix.value()),
906 )?;
907 }
908 return Ok(());
909 }
910 if schema.http_query_params().is_some() {
912 if !self.state.should_route_binding(schema) {
913 return Ok(());
914 }
915 let mut collector = MapEntryCollector::new(String::new());
916 write_entries(&mut collector)?;
917 let explicit_query_keys: Vec<&str> = self
920 .state
921 .input_schema
922 .map(|s| {
923 s.members()
924 .iter()
925 .filter_map(|m| m.http_query().map(|q| q.value()))
926 .collect()
927 })
928 .unwrap_or_default();
929 for (k, v) in collector.entries {
930 if !explicit_query_keys.contains(&k.as_str()) {
931 self.state.query_params.push((Cow::Owned(k), v));
932 }
933 }
934 return Ok(());
935 }
936 self.body.write_map(schema, write_entries)
937 }
938
939 fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
940 let schema = self.state.resolve_member(schema);
941 if let Some(binding) = http_string_binding(schema) {
942 return self.state.add_binding(binding, schema, &value.to_string());
943 }
944 self.body.write_boolean(schema, value)
945 }
946
947 fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
948 let schema = self.state.resolve_member(schema);
949 if let Some(binding) = http_string_binding(schema) {
950 return self.state.add_binding(binding, schema, &value.to_string());
951 }
952 self.body.write_byte(schema, value)
953 }
954
955 fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
956 let schema = self.state.resolve_member(schema);
957 if let Some(binding) = http_string_binding(schema) {
958 return self.state.add_binding(binding, schema, &value.to_string());
959 }
960 self.body.write_short(schema, value)
961 }
962
963 fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
964 let schema = self.state.resolve_member(schema);
965 if let Some(binding) = http_string_binding(schema) {
966 return self.state.add_binding(binding, schema, &value.to_string());
967 }
968 self.body.write_integer(schema, value)
969 }
970
971 fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
972 let schema = self.state.resolve_member(schema);
973 if let Some(binding) = http_string_binding(schema) {
974 return self.state.add_binding(binding, schema, &value.to_string());
975 }
976 self.body.write_long(schema, value)
977 }
978
979 fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
980 let schema = self.state.resolve_member(schema);
981 if let Some(binding) = http_string_binding(schema) {
982 return self
983 .state
984 .add_binding(binding, schema, &format_float_f32(value));
985 }
986 self.body.write_float(schema, value)
987 }
988
989 fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
990 let schema = self.state.resolve_member(schema);
991 if let Some(binding) = http_string_binding(schema) {
992 return self
993 .state
994 .add_binding(binding, schema, &format_float_f64(value));
995 }
996 self.body.write_double(schema, value)
997 }
998
999 fn write_big_integer(
1000 &mut self,
1001 schema: &Schema<'_>,
1002 value: &aws_smithy_types::BigInteger,
1003 ) -> Result<(), SerdeError> {
1004 let schema = self.state.resolve_member(schema);
1005 if let Some(binding) = http_string_binding(schema) {
1006 return self.state.add_binding(binding, schema, value.as_ref());
1007 }
1008 self.body.write_big_integer(schema, value)
1009 }
1010
1011 fn write_big_decimal(
1012 &mut self,
1013 schema: &Schema<'_>,
1014 value: &aws_smithy_types::BigDecimal,
1015 ) -> Result<(), SerdeError> {
1016 let schema = self.state.resolve_member(schema);
1017 if let Some(binding) = http_string_binding(schema) {
1018 return self.state.add_binding(binding, schema, value.as_ref());
1019 }
1020 self.body.write_big_decimal(schema, value)
1021 }
1022
1023 fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
1024 let schema = self.state.resolve_member(schema);
1025 if let Some(binding) = http_string_binding(schema) {
1026 if schema.media_type().is_some() {
1028 let encoded = aws_smithy_types::base64::encode(value.as_bytes());
1029 return self.state.add_binding(binding, schema, &encoded);
1030 }
1031 return self.state.add_binding(binding, schema, value);
1032 }
1033 if schema.http_payload().is_some() {
1034 if !self.state.should_route_binding(schema) {
1035 return Ok(());
1036 }
1037 self.state.raw_payload = Some(aws_smithy_types::Blob::new(value));
1046 return Ok(());
1047 }
1048 self.body.write_string(schema, value)
1049 }
1050
1051 fn write_blob(
1052 &mut self,
1053 schema: &Schema<'_>,
1054 value: aws_smithy_types::Blob,
1055 ) -> Result<(), SerdeError> {
1056 let schema = self.state.resolve_member(schema);
1057 if schema.http_header().is_some() {
1058 if !self.state.should_route_binding(schema) {
1059 return Ok(());
1060 }
1061 let encoded = aws_smithy_types::base64::encode(value.as_ref());
1062 let header = schema.http_header().unwrap();
1063 insert_bound_header(self.state.headers, schema, header, encoded)?;
1064 return Ok(());
1065 }
1066 if schema.http_payload().is_some() {
1067 if !self.state.should_route_binding(schema) {
1068 return Ok(());
1069 }
1070 self.state.raw_payload = Some(value);
1075 return Ok(());
1076 }
1077 self.body.write_blob(schema, value)
1078 }
1079
1080 fn write_timestamp(
1081 &mut self,
1082 schema: &Schema<'_>,
1083 value: &aws_smithy_types::DateTime,
1084 ) -> Result<(), SerdeError> {
1085 let schema = self.state.resolve_member(schema);
1086 if let Some(binding) = http_string_binding(schema) {
1087 let format = super::timestamp_format_or(
1089 schema,
1090 match binding {
1091 HttpBinding::Header => aws_smithy_types::date_time::Format::HttpDate,
1092 _ => aws_smithy_types::date_time::Format::DateTime,
1093 },
1094 );
1095 let formatted = value
1096 .fmt(format)
1097 .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?;
1098 return self.state.add_binding(binding, schema, &formatted);
1099 }
1100 self.body.write_timestamp(schema, value)
1101 }
1102
1103 fn write_document(
1104 &mut self,
1105 schema: &Schema<'_>,
1106 value: &aws_smithy_types::Document,
1107 ) -> Result<(), SerdeError> {
1108 self.body.write_document(schema, value)
1109 }
1110
1111 fn write_null(&mut self, schema: &Schema<'_>) -> Result<(), SerdeError> {
1112 self.body.write_null(schema)
1113 }
1114}
1115
1116enum HttpBinding {
1122 Header,
1123 Query,
1124 Label,
1125}
1126
1127fn http_string_binding(schema: &Schema<'_>) -> Option<HttpBinding> {
1129 if schema.http_header().is_some() {
1130 return Some(HttpBinding::Header);
1131 }
1132 if schema.http_query().is_some() {
1133 return Some(HttpBinding::Query);
1134 }
1135 if schema.http_label().is_some() {
1136 return Some(HttpBinding::Label);
1137 }
1138 None
1139}
1140
1141impl<'a> BindingState<'a> {
1142 fn add_binding(
1143 &mut self,
1144 binding: HttpBinding,
1145 schema: &Schema<'_>,
1146 value: &str,
1147 ) -> Result<(), SerdeError> {
1148 if !self.should_route_binding(schema) {
1152 return Ok(());
1153 }
1154 match binding {
1155 HttpBinding::Header => {
1156 if let Some(header) = schema.http_header() {
1157 insert_bound_header(self.headers, schema, header, value.to_string())?;
1158 }
1159 }
1160 HttpBinding::Query => {
1161 if let Some(query) = schema.http_query() {
1162 let name = self.query_param_name(schema, query);
1163 self.query_params.push((name, value.to_string()));
1164 }
1165 }
1166 HttpBinding::Label => {
1167 let cow_name = if let Some(resolved) = self.resolve_to_input_schema(schema) {
1174 let name = resolved
1175 .member_name()
1176 .ok_or_else(|| SerdeError::custom("httpLabel on non-member schema"))?;
1177 Cow::Borrowed(name)
1178 } else {
1179 let name = schema
1180 .member_name()
1181 .ok_or_else(|| SerdeError::custom("httpLabel on non-member schema"))?;
1182 Cow::Owned(name.to_string())
1183 };
1184 self.labels.push((cow_name, value.to_string()));
1185 }
1186 }
1187 Ok(())
1188 }
1189}
1190
1191macro_rules! noop_writes {
1199 ($($method:ident($($arg:ty),*)),+ $(,)?) => {
1200 $(
1201 fn $method(&mut self, _: &Schema<'_>, $(_: $arg),*) -> Result<(), SerdeError> {
1202 Ok(())
1203 }
1204 )+
1205 };
1206}
1207
1208#[derive(Copy, Clone)]
1211enum HttpListTarget {
1212 Header,
1213 Query,
1214}
1215
1216struct ListElementCollector {
1218 values: Vec<String>,
1219 quotable: Vec<bool>,
1221 target: HttpListTarget,
1222}
1223
1224impl ListElementCollector {
1225 fn for_header() -> Self {
1226 Self::new(HttpListTarget::Header)
1227 }
1228
1229 fn for_query() -> Self {
1230 Self::new(HttpListTarget::Query)
1231 }
1232
1233 fn new(target: HttpListTarget) -> Self {
1234 Self {
1235 values: Vec::new(),
1236 quotable: Vec::new(),
1237 target,
1238 }
1239 }
1240
1241 fn push(&mut self, value: String) {
1242 self.quotable.push(true);
1243 self.values.push(value);
1244 }
1245
1246 fn push_unquotable(&mut self, value: String) {
1247 self.quotable.push(false);
1248 self.values.push(value);
1249 }
1250}
1251
1252impl ShapeSerializer for ListElementCollector {
1253 fn write_string(&mut self, _schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
1254 self.push(value.to_string());
1255 Ok(())
1256 }
1257 fn write_boolean(&mut self, _: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
1258 self.push(value.to_string());
1259 Ok(())
1260 }
1261 fn write_byte(&mut self, _: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
1262 self.push(value.to_string());
1263 Ok(())
1264 }
1265 fn write_short(&mut self, _: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
1266 self.push(value.to_string());
1267 Ok(())
1268 }
1269 fn write_integer(&mut self, _: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
1270 self.push(value.to_string());
1271 Ok(())
1272 }
1273 fn write_long(&mut self, _: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
1274 self.push(value.to_string());
1275 Ok(())
1276 }
1277 fn write_float(&mut self, _: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
1278 self.push(format_float_f32(value));
1279 Ok(())
1280 }
1281 fn write_double(&mut self, _: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
1282 self.push(format_float_f64(value));
1283 Ok(())
1284 }
1285 fn write_timestamp(
1286 &mut self,
1287 schema: &Schema<'_>,
1288 value: &aws_smithy_types::DateTime,
1289 ) -> Result<(), SerdeError> {
1290 let format = super::timestamp_format_or(
1291 schema,
1292 match self.target {
1294 HttpListTarget::Header => aws_smithy_types::date_time::Format::HttpDate,
1295 HttpListTarget::Query => aws_smithy_types::date_time::Format::DateTime,
1296 },
1297 );
1298 self.push_unquotable(
1299 value
1300 .fmt(format)
1301 .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?,
1302 );
1303 Ok(())
1304 }
1305 fn write_blob(
1306 &mut self,
1307 _schema: &Schema<'_>,
1308 value: aws_smithy_types::Blob,
1309 ) -> Result<(), SerdeError> {
1310 self.push(aws_smithy_types::base64::encode(value.as_ref()));
1311 Ok(())
1312 }
1313 noop_writes! {
1315 write_struct(&dyn SerializableStruct),
1316 write_list(&dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>),
1317 write_map(&dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>),
1318 write_big_integer(&aws_smithy_types::BigInteger),
1319 write_big_decimal(&aws_smithy_types::BigDecimal),
1320 write_document(&aws_smithy_types::Document),
1321 write_null(),
1322 }
1323}
1324
1325fn format_float_f32(value: f32) -> String {
1328 if value.is_infinite() {
1329 if value.is_sign_positive() {
1330 "Infinity".to_string()
1331 } else {
1332 "-Infinity".to_string()
1333 }
1334 } else if value.is_nan() {
1335 "NaN".to_string()
1336 } else {
1337 value.to_string()
1338 }
1339}
1340
1341fn format_float_f64(value: f64) -> String {
1342 if value.is_infinite() {
1343 if value.is_sign_positive() {
1344 "Infinity".to_string()
1345 } else {
1346 "-Infinity".to_string()
1347 }
1348 } else if value.is_nan() {
1349 "NaN".to_string()
1350 } else {
1351 value.to_string()
1352 }
1353}
1354
1355struct MapEntryCollector {
1358 prefix: String,
1359 entries: Vec<(String, String)>,
1360 pending_key: Option<String>,
1361}
1362
1363impl MapEntryCollector {
1364 fn new(prefix: String) -> Self {
1365 Self {
1366 prefix,
1367 entries: Vec::new(),
1368 pending_key: None,
1369 }
1370 }
1371}
1372
1373impl ShapeSerializer for MapEntryCollector {
1374 fn write_string(&mut self, _schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
1375 if let Some(key) = self.pending_key.take() {
1376 self.entries
1377 .push((format!("{}{}", self.prefix, key), value.to_string()));
1378 } else {
1379 self.pending_key = Some(value.to_string());
1380 }
1381 Ok(())
1382 }
1383
1384 fn write_list(
1385 &mut self,
1386 _: &Schema<'_>,
1387 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1388 ) -> Result<(), SerdeError> {
1389 if let Some(key) = self.pending_key.take() {
1392 let mut collector = ListElementCollector::for_query(); write_elements(&mut collector)?;
1394 for val in collector.values {
1395 self.entries.push((format!("{}{}", self.prefix, key), val));
1396 }
1397 }
1398 Ok(())
1399 }
1400 noop_writes! {
1404 write_struct(&dyn SerializableStruct),
1405 write_map(&dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>),
1406 write_boolean(bool),
1407 write_byte(i8),
1408 write_short(i16),
1409 write_integer(i32),
1410 write_long(i64),
1411 write_float(f32),
1412 write_double(f64),
1413 write_big_integer(&aws_smithy_types::BigInteger),
1414 write_big_decimal(&aws_smithy_types::BigDecimal),
1415 write_blob(aws_smithy_types::Blob),
1416 write_timestamp(&aws_smithy_types::DateTime),
1417 write_document(&aws_smithy_types::Document),
1418 write_null(),
1419 }
1420}
1421
1422impl<C> ClientProtocolInner for HttpBindingProtocol<C>
1423where
1424 C: Codec + Send + Sync + std::fmt::Debug + 'static,
1425 for<'a> C::Deserializer<'a>: ShapeDeserializer,
1426{
1427 type Request = Request;
1428 type Response = Response;
1429
1430 fn protocol_id(&self) -> &ShapeId<'static> {
1431 &self.protocol_id
1432 }
1433
1434 fn serialize_request(
1435 &self,
1436 input: &dyn SerializableStruct,
1437 input_schema: &Schema<'_>,
1438 endpoint: &str,
1439 cfg: &ConfigBag,
1440 ) -> Result<Request, SerdeError> {
1441 let body = self.codec.create_serializer();
1442 self.serialize_request_with_body(body, input, input_schema, endpoint, cfg)
1443 }
1444
1445 fn deserialize_response<'a>(
1466 &self,
1467 response: &'a Response,
1468 output_schema: &Schema<'_>,
1469 cfg: &'a ConfigBag,
1470 ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
1471 let body = response.body().bytes().unwrap_or(&[]);
1472 Ok(http_output_deserializer(
1473 self.codec.create_deserializer(body),
1474 response,
1475 output_schema,
1476 cfg,
1477 ))
1478 }
1479
1480 fn deserialize_error_response<'a>(
1492 &self,
1493 response: &'a Response,
1494 cfg: &'a ConfigBag,
1495 ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
1496 let body = response.body().bytes().unwrap_or(&[]);
1497 Ok(http_error_deserializer(
1498 self.codec.create_deserializer(body),
1499 response,
1500 cfg,
1501 ))
1502 }
1503
1504 fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
1505 Some(&self.codec)
1506 }
1507
1508 fn update_endpoint(
1509 &self,
1510 request: &mut Request,
1511 endpoint: &aws_smithy_types::endpoint::Endpoint,
1512 cfg: &ConfigBag,
1513 ) -> Result<(), SerdeError> {
1514 apply_http_endpoint(request, endpoint, cfg)
1515 }
1516}
1517
1518#[cfg(test)]
1519mod tests {
1520 use super::*;
1521 use crate::serde::SerializableStruct;
1522 use crate::{prelude::*, ShapeType};
1523
1524 #[test]
1525 fn visited_members_inline_dedup() {
1526 let mut v = VisitedMembers::new();
1527 assert!(v.insert(3));
1529 assert!(!v.insert(3));
1530 assert!(v.insert(7));
1532 assert!(v.insert(0));
1533 assert!(!v.insert(3));
1535 assert!(!v.insert(7));
1536 assert!(!v.insert(0));
1537 }
1538
1539 #[test]
1540 fn visited_members_spills_to_overflow() {
1541 let mut v = VisitedMembers::new();
1544 let n = VisitedMembers::INLINE_CAPACITY;
1545 for i in 0..n {
1547 assert!(v.insert(i), "fresh inline insert at {i} must return true");
1548 }
1549 for i in 0..n {
1551 assert!(
1552 !v.insert(i),
1553 "duplicate inline insert at {i} must return false"
1554 );
1555 }
1556 assert!(v.insert(n));
1558 assert!(v.insert(n + 5));
1559 assert!(!v.insert(n));
1561 assert!(!v.insert(n + 5));
1562 assert!(!v.insert(0));
1565 }
1566
1567 struct TestSerializer {
1568 output: Vec<u8>,
1569 }
1570
1571 impl FinishSerializer for TestSerializer {
1572 fn finish(self) -> Vec<u8> {
1573 self.output
1574 }
1575 }
1576
1577 impl ShapeSerializer for TestSerializer {
1578 fn write_struct(
1579 &mut self,
1580 _: &Schema<'_>,
1581 value: &dyn SerializableStruct,
1582 ) -> Result<(), SerdeError> {
1583 self.output.push(b'{');
1584 value.serialize_members(self)?;
1585 self.output.push(b'}');
1586 Ok(())
1587 }
1588 fn write_list(
1589 &mut self,
1590 _: &Schema<'_>,
1591 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1592 ) -> Result<(), SerdeError> {
1593 Ok(())
1594 }
1595 fn write_map(
1596 &mut self,
1597 _: &Schema<'_>,
1598 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1599 ) -> Result<(), SerdeError> {
1600 Ok(())
1601 }
1602 fn write_boolean(&mut self, _: &Schema<'_>, _: bool) -> Result<(), SerdeError> {
1603 Ok(())
1604 }
1605 fn write_byte(&mut self, _: &Schema<'_>, _: i8) -> Result<(), SerdeError> {
1606 Ok(())
1607 }
1608 fn write_short(&mut self, _: &Schema<'_>, _: i16) -> Result<(), SerdeError> {
1609 Ok(())
1610 }
1611 fn write_integer(&mut self, _: &Schema<'_>, _: i32) -> Result<(), SerdeError> {
1612 Ok(())
1613 }
1614 fn write_long(&mut self, _: &Schema<'_>, _: i64) -> Result<(), SerdeError> {
1615 Ok(())
1616 }
1617 fn write_float(&mut self, _: &Schema<'_>, _: f32) -> Result<(), SerdeError> {
1618 Ok(())
1619 }
1620 fn write_double(&mut self, _: &Schema<'_>, _: f64) -> Result<(), SerdeError> {
1621 Ok(())
1622 }
1623 fn write_big_integer(
1624 &mut self,
1625 _: &Schema<'_>,
1626 _: &aws_smithy_types::BigInteger,
1627 ) -> Result<(), SerdeError> {
1628 Ok(())
1629 }
1630 fn write_big_decimal(
1631 &mut self,
1632 _: &Schema<'_>,
1633 _: &aws_smithy_types::BigDecimal,
1634 ) -> Result<(), SerdeError> {
1635 Ok(())
1636 }
1637 fn write_string(&mut self, _: &Schema<'_>, v: &str) -> Result<(), SerdeError> {
1638 self.output.extend_from_slice(v.as_bytes());
1639 Ok(())
1640 }
1641 fn write_blob(
1642 &mut self,
1643 _: &Schema<'_>,
1644 _: aws_smithy_types::Blob,
1645 ) -> Result<(), SerdeError> {
1646 Ok(())
1647 }
1648 fn write_timestamp(
1649 &mut self,
1650 _: &Schema<'_>,
1651 _: &aws_smithy_types::DateTime,
1652 ) -> Result<(), SerdeError> {
1653 Ok(())
1654 }
1655 fn write_document(
1656 &mut self,
1657 _: &Schema<'_>,
1658 _: &aws_smithy_types::Document,
1659 ) -> Result<(), SerdeError> {
1660 Ok(())
1661 }
1662 fn write_null(&mut self, _: &Schema<'_>) -> Result<(), SerdeError> {
1663 Ok(())
1664 }
1665 }
1666
1667 struct TestDeserializer<'a> {
1668 input: &'a [u8],
1669 }
1670
1671 impl ShapeDeserializer for TestDeserializer<'_> {
1672 fn read_struct(
1676 &mut self,
1677 _: &Schema<'_>,
1678 _: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1679 ) -> Result<(), SerdeError> {
1680 if self.input.is_empty() {
1681 return Err(SerdeError::invalid_input("expected a body, found none"));
1682 }
1683 Ok(())
1684 }
1685 fn read_list(
1686 &mut self,
1687 _: &Schema<'_>,
1688 _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1689 ) -> Result<(), SerdeError> {
1690 Ok(())
1691 }
1692 fn read_map(
1693 &mut self,
1694 _: &Schema<'_>,
1695 _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1696 ) -> Result<(), SerdeError> {
1697 Ok(())
1698 }
1699 fn read_boolean(&mut self, _: &Schema<'_>) -> Result<bool, SerdeError> {
1700 Ok(false)
1701 }
1702 fn read_byte(&mut self, _: &Schema<'_>) -> Result<i8, SerdeError> {
1703 Ok(0)
1704 }
1705 fn read_short(&mut self, _: &Schema<'_>) -> Result<i16, SerdeError> {
1706 Ok(0)
1707 }
1708 fn read_integer(&mut self, _: &Schema<'_>) -> Result<i32, SerdeError> {
1709 Ok(0)
1710 }
1711 fn read_long(&mut self, _: &Schema<'_>) -> Result<i64, SerdeError> {
1712 Ok(0)
1713 }
1714 fn read_float(&mut self, _: &Schema<'_>) -> Result<f32, SerdeError> {
1715 Ok(0.0)
1716 }
1717 fn read_double(&mut self, _: &Schema<'_>) -> Result<f64, SerdeError> {
1718 Ok(0.0)
1719 }
1720 fn read_big_integer(
1721 &mut self,
1722 _: &Schema<'_>,
1723 ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
1724 use std::str::FromStr;
1725 Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
1726 }
1727 fn read_big_decimal(
1728 &mut self,
1729 _: &Schema<'_>,
1730 ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
1731 use std::str::FromStr;
1732 Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
1733 }
1734 fn read_string(&mut self, _: &Schema<'_>) -> Result<String, SerdeError> {
1735 Ok(String::from_utf8_lossy(self.input).into_owned())
1736 }
1737 fn read_blob(&mut self, _: &Schema<'_>) -> Result<aws_smithy_types::Blob, SerdeError> {
1738 Ok(aws_smithy_types::Blob::new(vec![]))
1739 }
1740 fn read_timestamp(
1741 &mut self,
1742 _: &Schema<'_>,
1743 ) -> Result<aws_smithy_types::DateTime, SerdeError> {
1744 Ok(aws_smithy_types::DateTime::from_secs(0))
1745 }
1746 fn read_document(
1747 &mut self,
1748 _: &Schema<'_>,
1749 ) -> Result<aws_smithy_types::Document, SerdeError> {
1750 Ok(aws_smithy_types::Document::Null)
1751 }
1752 fn is_null(&self) -> bool {
1753 false
1754 }
1755 fn container_size(&self) -> Option<usize> {
1756 None
1757 }
1758 }
1759
1760 #[derive(Debug)]
1761 struct TestCodec;
1762
1763 impl Codec for TestCodec {
1764 type Serializer = TestSerializer;
1765 type Deserializer<'a> = TestDeserializer<'a>;
1766 fn create_serializer(&self) -> Self::Serializer {
1767 TestSerializer { output: Vec::new() }
1768 }
1769 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
1770 TestDeserializer { input }
1771 }
1772 }
1773
1774 static TEST_SCHEMA: Schema<'static> =
1775 Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
1776
1777 struct EmptyStruct;
1778 impl SerializableStruct for EmptyStruct {
1779 fn schema(&self) -> &Schema<'_> {
1780 &TEST_SCHEMA
1781 }
1782
1783 fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1784 Ok(())
1785 }
1786 }
1787
1788 static NAME_MEMBER: Schema<'static> = Schema::new_member(
1789 crate::shape_id!("test", "TestStruct"),
1790 ShapeType::String,
1791 "name",
1792 0,
1793 );
1794 static MEMBERS: &[&Schema<'_>] = &[&NAME_MEMBER];
1795 static STRUCT_WITH_MEMBER: Schema<'static> = Schema::new_struct(
1796 crate::shape_id!("test", "TestStruct"),
1797 ShapeType::Structure,
1798 MEMBERS,
1799 );
1800
1801 struct NameStruct;
1802 impl SerializableStruct for NameStruct {
1803 fn schema(&self) -> &Schema<'_> {
1804 &STRUCT_WITH_MEMBER
1805 }
1806
1807 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1808 s.write_string(&NAME_MEMBER, "Alice")
1809 }
1810 }
1811
1812 fn make_protocol() -> HttpBindingProtocol<TestCodec> {
1813 HttpBindingProtocol::new(
1814 crate::shape_id!("test", "proto"),
1815 TestCodec,
1816 "application/test",
1817 )
1818 }
1819
1820 #[test]
1829 fn serialize_request_ignores_a_route_computed_for_another_protocol() {
1830 static HTTP_SCHEMA: Schema<'static> = Schema::new_struct(
1831 crate::shape_id!("test", "GetStatsRequest"),
1832 ShapeType::Structure,
1833 &[],
1834 )
1835 .with_http(crate::traits::HttpTrait::new("PUT", "/stats", None));
1836
1837 for foreign_route in [
1838 "/service/Svc/operation/GetStats",
1840 "/",
1842 ] {
1843 let request = make_protocol()
1844 .serialize_request(
1845 &EmptyStruct,
1846 &HTTP_SCHEMA,
1847 foreign_route,
1848 &ConfigBag::base(),
1849 )
1850 .unwrap();
1851 assert_eq!(
1852 "/stats",
1853 request.uri(),
1854 "the `@http` template is authoritative; the route {foreign_route} was computed for \
1855 another protocol and must be ignored",
1856 );
1857 assert_eq!("PUT", request.method());
1858 }
1859 }
1860
1861 #[test]
1864 fn serialize_request_uses_endpoint_as_template_without_an_http_trait() {
1865 let request = make_protocol()
1866 .serialize_request(&EmptyStruct, &TEST_SCHEMA, "/some/path", &ConfigBag::base())
1867 .unwrap();
1868 assert_eq!("/some/path", request.uri());
1869 }
1870
1871 #[test]
1872 fn serialize_sets_content_type() {
1873 let request = make_protocol()
1875 .serialize_request(
1876 &EmptyStruct,
1877 &STRUCT_WITH_MEMBER,
1878 "https://example.com",
1879 &ConfigBag::base(),
1880 )
1881 .unwrap();
1882 assert_eq!(
1883 request.headers().get("Content-Type").unwrap(),
1884 "application/test"
1885 );
1886 }
1887
1888 #[test]
1889 fn serialize_no_body_members_omits_content_type() {
1890 let request = make_protocol()
1892 .serialize_request(
1893 &EmptyStruct,
1894 &TEST_SCHEMA,
1895 "https://example.com",
1896 &ConfigBag::base(),
1897 )
1898 .unwrap();
1899 assert!(request.headers().get("Content-Type").is_none());
1900 }
1901
1902 #[test]
1908 fn presigning_omit_settings_suppress_default_content_headers() {
1909 use crate::header_omit_settings::{HeaderOmitSettings, SharedHeaderOmitSettings};
1910 use aws_smithy_types::config_bag::Layer;
1911
1912 #[derive(Debug)]
1913 struct OmitBoth;
1914 impl HeaderOmitSettings for OmitBoth {
1915 fn should_omit_default_content_type(&self) -> bool {
1916 true
1917 }
1918 fn should_omit_default_content_length(&self) -> bool {
1919 true
1920 }
1921 }
1922
1923 let mut layer = Layer::new("test_omit");
1924 layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
1925 let cfg = ConfigBag::of_layers(vec![layer]);
1926
1927 let request = make_protocol()
1928 .serialize_request(
1929 &NameStruct,
1930 &STRUCT_WITH_MEMBER,
1931 "https://example.com",
1932 &cfg,
1933 )
1934 .unwrap();
1935 assert!(
1936 request.headers().get("Content-Type").is_none(),
1937 "presigning omit suppresses default Content-Type"
1938 );
1939 assert!(
1940 request.headers().get("Content-Length").is_none(),
1941 "presigning omit suppresses default Content-Length"
1942 );
1943 }
1944
1945 #[test]
1952 fn default_content_headers_inserted_when_omit_settings_absent() {
1953 let request = make_protocol()
1954 .serialize_request(
1955 &NameStruct,
1956 &STRUCT_WITH_MEMBER,
1957 "https://example.com",
1958 &ConfigBag::base(),
1959 )
1960 .unwrap();
1961 assert_eq!(
1962 request
1963 .headers()
1964 .get("Content-Type")
1965 .expect("Content-Type set"),
1966 "application/test"
1967 );
1968 assert_eq!(
1969 request
1970 .headers()
1971 .get("Content-Length")
1972 .expect("Content-Length set"),
1973 "7"
1974 );
1975 }
1976
1977 #[test]
1983 fn serialize_skips_body_codec_when_no_body_members() {
1984 use std::sync::atomic::{AtomicUsize, Ordering};
1985
1986 static WRITE_CALLS: AtomicUsize = AtomicUsize::new(0);
1987
1988 struct PanicSerializer;
1989 impl FinishSerializer for PanicSerializer {
1990 fn finish(self) -> Vec<u8> {
1991 panic!("body codec finish() called — short-circuit failed");
1992 }
1993 }
1994 impl ShapeSerializer for PanicSerializer {
1995 fn write_struct(
1996 &mut self,
1997 _: &Schema<'_>,
1998 _: &dyn SerializableStruct,
1999 ) -> Result<(), SerdeError> {
2000 WRITE_CALLS.fetch_add(1, Ordering::SeqCst);
2001 panic!("body codec write_struct() called — short-circuit failed");
2002 }
2003 fn write_list(
2004 &mut self,
2005 _: &Schema<'_>,
2006 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
2007 ) -> Result<(), SerdeError> {
2008 panic!("body codec write_list() called");
2009 }
2010 fn write_map(
2011 &mut self,
2012 _: &Schema<'_>,
2013 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
2014 ) -> Result<(), SerdeError> {
2015 panic!("body codec write_map() called");
2016 }
2017 fn write_boolean(&mut self, _: &Schema<'_>, _: bool) -> Result<(), SerdeError> {
2018 panic!("body codec write_boolean() called");
2019 }
2020 fn write_byte(&mut self, _: &Schema<'_>, _: i8) -> Result<(), SerdeError> {
2021 panic!("body codec write_byte() called");
2022 }
2023 fn write_short(&mut self, _: &Schema<'_>, _: i16) -> Result<(), SerdeError> {
2024 panic!("body codec write_short() called");
2025 }
2026 fn write_integer(&mut self, _: &Schema<'_>, _: i32) -> Result<(), SerdeError> {
2027 panic!("body codec write_integer() called");
2028 }
2029 fn write_long(&mut self, _: &Schema<'_>, _: i64) -> Result<(), SerdeError> {
2030 panic!("body codec write_long() called");
2031 }
2032 fn write_float(&mut self, _: &Schema<'_>, _: f32) -> Result<(), SerdeError> {
2033 panic!("body codec write_float() called");
2034 }
2035 fn write_double(&mut self, _: &Schema<'_>, _: f64) -> Result<(), SerdeError> {
2036 panic!("body codec write_double() called");
2037 }
2038 fn write_big_integer(
2039 &mut self,
2040 _: &Schema<'_>,
2041 _: &aws_smithy_types::BigInteger,
2042 ) -> Result<(), SerdeError> {
2043 panic!("body codec write_big_integer() called");
2044 }
2045 fn write_big_decimal(
2046 &mut self,
2047 _: &Schema<'_>,
2048 _: &aws_smithy_types::BigDecimal,
2049 ) -> Result<(), SerdeError> {
2050 panic!("body codec write_big_decimal() called");
2051 }
2052 fn write_string(&mut self, _: &Schema<'_>, _: &str) -> Result<(), SerdeError> {
2053 panic!("body codec write_string() called");
2054 }
2055 fn write_blob(
2056 &mut self,
2057 _: &Schema<'_>,
2058 _: aws_smithy_types::Blob,
2059 ) -> Result<(), SerdeError> {
2060 panic!("body codec write_blob() called");
2061 }
2062 fn write_timestamp(
2063 &mut self,
2064 _: &Schema<'_>,
2065 _: &aws_smithy_types::DateTime,
2066 ) -> Result<(), SerdeError> {
2067 panic!("body codec write_timestamp() called");
2068 }
2069 fn write_document(
2070 &mut self,
2071 _: &Schema<'_>,
2072 _: &aws_smithy_types::Document,
2073 ) -> Result<(), SerdeError> {
2074 panic!("body codec write_document() called");
2075 }
2076 fn write_null(&mut self, _: &Schema<'_>) -> Result<(), SerdeError> {
2077 panic!("body codec write_null() called");
2078 }
2079 }
2080
2081 #[derive(Debug)]
2082 struct PanicCodec;
2083 impl Codec for PanicCodec {
2084 type Serializer = PanicSerializer;
2085 type Deserializer<'a> = TestDeserializer<'a>;
2086 fn create_serializer(&self) -> Self::Serializer {
2087 PanicSerializer
2088 }
2089 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
2090 TestDeserializer { input }
2091 }
2092 }
2093
2094 static HEADER_MEMBER: Schema<'static> = Schema::new_member(
2098 crate::shape_id!("test", "HeaderOnlyStruct"),
2099 ShapeType::String,
2100 "x_header",
2101 0,
2102 )
2103 .with_http_header("X-Header");
2104 static HEADER_MEMBERS: &[&Schema<'_>] = &[&HEADER_MEMBER];
2105 static HEADER_ONLY_SCHEMA: Schema<'static> = Schema::new_struct(
2106 crate::shape_id!("test", "HeaderOnlyStruct"),
2107 ShapeType::Structure,
2108 HEADER_MEMBERS,
2109 )
2110 .with_no_body_members();
2111
2112 struct HeaderOnlyStruct;
2113 impl SerializableStruct for HeaderOnlyStruct {
2114 fn schema(&self) -> &Schema<'_> {
2115 &HEADER_ONLY_SCHEMA
2116 }
2117
2118 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2119 s.write_string(&HEADER_MEMBER, "hello")
2120 }
2121 }
2122
2123 let protocol = HttpBindingProtocol::new(
2124 crate::shape_id!("test", "testProtocol"),
2125 PanicCodec,
2126 "application/test",
2127 );
2128 let request = protocol
2129 .serialize_request(
2130 &HeaderOnlyStruct,
2131 &HEADER_ONLY_SCHEMA,
2132 "https://example.com",
2133 &ConfigBag::base(),
2134 )
2135 .unwrap();
2136
2137 assert_eq!(request.body().bytes().unwrap_or(&[]), b"");
2139 assert!(request.headers().get("Content-Type").is_none());
2140 assert_eq!(request.headers().get("X-Header").unwrap(), "hello");
2141 assert_eq!(WRITE_CALLS.load(Ordering::SeqCst), 0);
2143 }
2144
2145 static STRUCT_PAYLOAD_MEMBER: Schema<'static> = Schema::new_member(
2153 crate::shape_id!("test", "StructPayloadStruct"),
2154 ShapeType::Structure,
2155 "body",
2156 0,
2157 )
2158 .with_http_payload();
2159 static STRUCT_PAYLOAD_MEMBERS: &[&Schema<'_>] = &[&STRUCT_PAYLOAD_MEMBER];
2160
2161 static STRUCT_PAYLOAD_UNKNOWN: Schema<'static> = Schema::new_struct(
2163 crate::shape_id!("test", "StructPayloadStruct"),
2164 ShapeType::Structure,
2165 STRUCT_PAYLOAD_MEMBERS,
2166 );
2167 static STRUCT_PAYLOAD_HINTED: Schema<'static> = Schema::new_struct(
2169 crate::shape_id!("test", "StructPayloadStruct"),
2170 ShapeType::Structure,
2171 STRUCT_PAYLOAD_MEMBERS,
2172 )
2173 .with_payload_hint(crate::PayloadHint::StructPayload);
2174 static STRUCT_PAYLOAD_MIS_HINTED: Schema<'static> = Schema::new_struct(
2176 crate::shape_id!("test", "StructPayloadStruct"),
2177 ShapeType::Structure,
2178 STRUCT_PAYLOAD_MEMBERS,
2179 )
2180 .with_payload_hint(crate::PayloadHint::NoStructPayload);
2181
2182 struct StructPayloadStruct;
2187 impl SerializableStruct for StructPayloadStruct {
2188 fn schema(&self) -> &Schema<'_> {
2189 &STRUCT_PAYLOAD_UNKNOWN
2190 }
2191
2192 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2193 s.write_struct(&STRUCT_PAYLOAD_MEMBER, &NameStruct)
2194 }
2195 }
2196
2197 fn body_for(schema: &Schema<'_>, input: &dyn SerializableStruct) -> Vec<u8> {
2198 make_protocol()
2199 .serialize_request(input, schema, "https://example.com", &ConfigBag::base())
2200 .unwrap()
2201 .body()
2202 .bytes()
2203 .unwrap_or(&[])
2204 .to_vec()
2205 }
2206
2207 #[test]
2208 fn struct_payload_hint_agrees_with_derived_scan() {
2209 let derived = body_for(&STRUCT_PAYLOAD_UNKNOWN, &StructPayloadStruct);
2210 let hinted = body_for(&STRUCT_PAYLOAD_HINTED, &StructPayloadStruct);
2211
2212 assert_eq!(derived, b"{Alice}");
2214 assert_eq!(hinted, derived);
2215 }
2216
2217 #[test]
2218 fn no_struct_payload_hint_agrees_with_derived_scan() {
2219 static HINTED: Schema<'static> = Schema::new_struct(
2220 crate::shape_id!("test", "TestStruct"),
2221 ShapeType::Structure,
2222 MEMBERS,
2223 )
2224 .with_payload_hint(crate::PayloadHint::NoStructPayload);
2225
2226 let derived = body_for(&STRUCT_WITH_MEMBER, &NameStruct);
2227 let hinted = body_for(&HINTED, &NameStruct);
2228
2229 assert_eq!(derived, b"{Alice}");
2231 assert_eq!(hinted, derived);
2232 }
2233
2234 #[test]
2247 fn wrong_struct_payload_hint_changes_request() {
2248 assert_eq!(
2249 body_for(&STRUCT_PAYLOAD_MIS_HINTED, &StructPayloadStruct),
2250 b"",
2251 "a mis-recorded hint must be observable, otherwise the agreement \
2252 tests above prove nothing"
2253 );
2254 }
2255
2256 #[test]
2257 fn payload_hint_defaults_to_unknown() {
2258 assert_eq!(
2259 STRUCT_PAYLOAD_UNKNOWN.payload_hint(),
2260 crate::PayloadHint::Unknown
2261 );
2262 assert_eq!(TEST_SCHEMA.payload_hint(), crate::PayloadHint::Unknown);
2263 }
2264
2265 #[test]
2270 fn serialize_invokes_body_codec_when_has_body_members() {
2271 let request = make_protocol()
2272 .serialize_request(
2273 &NameStruct,
2274 &STRUCT_WITH_MEMBER,
2275 "https://example.com",
2276 &ConfigBag::base(),
2277 )
2278 .unwrap();
2279 assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
2282 assert_eq!(
2283 request.headers().get("Content-Type").unwrap(),
2284 "application/test"
2285 );
2286 }
2287
2288 #[test]
2289 fn serialize_sets_uri() {
2290 let request = make_protocol()
2291 .serialize_request(
2292 &EmptyStruct,
2293 &TEST_SCHEMA,
2294 "https://example.com/path",
2295 &ConfigBag::base(),
2296 )
2297 .unwrap();
2298 assert_eq!(request.uri(), "https://example.com/path");
2299 }
2300
2301 #[test]
2302 fn serialize_body() {
2303 let request = make_protocol()
2304 .serialize_request(
2305 &NameStruct,
2306 &STRUCT_WITH_MEMBER,
2307 "https://example.com",
2308 &ConfigBag::base(),
2309 )
2310 .unwrap();
2311 assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
2312 }
2313
2314 static BLOB_PAYLOAD_MEMBER: Schema<'static> = Schema::new_member(
2317 crate::shape_id!("test", "PayloadStruct"),
2318 ShapeType::Blob,
2319 "data",
2320 0,
2321 )
2322 .with_http_payload();
2323 static BLOB_PAYLOAD_MEMBERS: &[&Schema<'_>] = &[&BLOB_PAYLOAD_MEMBER];
2324 static BLOB_PAYLOAD_STRUCT: Schema<'static> = Schema::new_struct(
2325 crate::shape_id!("test", "PayloadStruct"),
2326 ShapeType::Structure,
2327 BLOB_PAYLOAD_MEMBERS,
2328 );
2329
2330 static STRING_PAYLOAD_MEMBER: Schema<'static> = Schema::new_member(
2331 crate::shape_id!("test", "PayloadStruct"),
2332 ShapeType::String,
2333 "data",
2334 0,
2335 )
2336 .with_http_payload();
2337 static STRING_PAYLOAD_MEMBERS: &[&Schema<'_>] = &[&STRING_PAYLOAD_MEMBER];
2338 static STRING_PAYLOAD_STRUCT: Schema<'static> = Schema::new_struct(
2339 crate::shape_id!("test", "PayloadStruct"),
2340 ShapeType::Structure,
2341 STRING_PAYLOAD_MEMBERS,
2342 );
2343
2344 struct LocallyComputedPayload {
2351 blob: bool,
2352 }
2353
2354 impl SerializableStruct for LocallyComputedPayload {
2355 fn schema(&self) -> &Schema<'_> {
2356 &STRING_PAYLOAD_STRUCT
2357 }
2358
2359 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2360 if self.blob {
2361 let computed: Vec<u8> = (0u8..64).collect();
2363 s.write_blob(&BLOB_PAYLOAD_MEMBER, aws_smithy_types::Blob::new(computed))
2364 } else {
2365 let computed: String = (0..16).map(|i| char::from(b'a' + (i % 26) as u8)).collect();
2366 s.write_string(&STRING_PAYLOAD_MEMBER, &computed)
2367 }
2368 }
2369 }
2370
2371 #[test]
2387 fn http_payload_from_a_local_does_not_dangle() {
2388 let request = make_protocol()
2389 .serialize_request(
2390 &LocallyComputedPayload { blob: true },
2391 &BLOB_PAYLOAD_STRUCT,
2392 "https://example.com",
2393 &ConfigBag::base(),
2394 )
2395 .unwrap();
2396 let expected: Vec<u8> = (0u8..64).collect();
2398 assert_eq!(request.body().bytes().unwrap(), &expected[..]);
2399
2400 let request = make_protocol()
2401 .serialize_request(
2402 &LocallyComputedPayload { blob: false },
2403 &STRING_PAYLOAD_STRUCT,
2404 "https://example.com",
2405 &ConfigBag::base(),
2406 )
2407 .unwrap();
2408 assert_eq!(request.body().bytes().unwrap(), b"abcdefghijklmnop");
2409 }
2410
2411 #[test]
2417 fn blob_payload_reaches_the_body_without_copying() {
2418 struct OwnedBlobPayload(aws_smithy_types::Blob);
2419 impl SerializableStruct for OwnedBlobPayload {
2420 fn schema(&self) -> &Schema<'_> {
2421 &BLOB_PAYLOAD_STRUCT
2422 }
2423
2424 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2425 s.write_blob(&BLOB_PAYLOAD_MEMBER, self.0.clone())
2426 }
2427 }
2428
2429 let payload = aws_smithy_types::Blob::new(vec![7u8; 4096]);
2430 let src_ptr = payload.as_ref().as_ptr();
2431
2432 let request = make_protocol()
2433 .serialize_request(
2434 &OwnedBlobPayload(payload),
2435 &BLOB_PAYLOAD_STRUCT,
2436 "https://example.com",
2437 &ConfigBag::base(),
2438 )
2439 .unwrap();
2440
2441 let body = request.body().bytes().expect("payload body is in memory");
2442 assert_eq!(body.len(), 4096);
2443 assert_eq!(
2444 body.as_ptr(),
2445 src_ptr,
2446 "blob payload was copied; `write_blob` should move the `Bytes` handle all the way \
2447 into `SdkBody`"
2448 );
2449 }
2450
2451 #[test]
2452 fn deserialize_response() {
2453 let response = Response::new(
2454 200u16.try_into().unwrap(),
2455 SdkBody::from(r#"{"name":"Bob"}"#),
2456 );
2457 let base_cfg = ConfigBag::base();
2458 let mut deser = make_protocol()
2459 .deserialize_response(&response, &TEST_SCHEMA, &base_cfg)
2460 .unwrap();
2461 assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"name":"Bob"}"#);
2462 }
2463
2464 #[test]
2465 fn update_endpoint() {
2466 let mut request = make_protocol()
2467 .serialize_request(
2468 &EmptyStruct,
2469 &TEST_SCHEMA,
2470 "https://old.example.com",
2471 &ConfigBag::base(),
2472 )
2473 .unwrap();
2474 let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
2475 .url("https://new.example.com")
2476 .build();
2477 make_protocol()
2478 .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
2479 .unwrap();
2480 assert_eq!(request.uri(), "https://new.example.com/");
2481 }
2482
2483 #[test]
2484 fn protocol_id() {
2485 let protocol = HttpBindingProtocol::new(
2486 crate::shape_id!("aws.protocols", "restJson1"),
2487 TestCodec,
2488 "application/json",
2489 );
2490 assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#restJson1");
2491 }
2492
2493 #[test]
2494 fn invalid_uri_returns_error() {
2495 assert!(make_protocol()
2496 .serialize_request(
2497 &EmptyStruct,
2498 &TEST_SCHEMA,
2499 "not a valid uri\n\n",
2500 &ConfigBag::base()
2501 )
2502 .is_err());
2503 }
2504
2505 static HEADER_MEMBER: Schema<'static> = Schema::new_member(
2508 crate::shape_id!("test", "S"),
2509 ShapeType::String,
2510 "xToken",
2511 0,
2512 )
2513 .with_http_header("X-Token");
2514
2515 static HEADER_SCHEMA: Schema<'static> = Schema::new_struct(
2516 crate::shape_id!("test", "S"),
2517 ShapeType::Structure,
2518 &[&HEADER_MEMBER],
2519 );
2520
2521 struct HeaderStruct;
2522 impl SerializableStruct for HeaderStruct {
2523 fn schema(&self) -> &Schema<'_> {
2524 &HEADER_SCHEMA
2525 }
2526
2527 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2528 s.write_string(&HEADER_MEMBER, "my-token-value")
2529 }
2530 }
2531
2532 #[test]
2533 fn http_header_string() {
2534 let request = make_protocol()
2535 .serialize_request(
2536 &HeaderStruct,
2537 &HEADER_SCHEMA,
2538 "https://example.com",
2539 &ConfigBag::base(),
2540 )
2541 .unwrap();
2542 assert_eq!(request.headers().get("X-Token").unwrap(), "my-token-value");
2543 }
2544
2545 #[test]
2548 fn http_header_string_list_quoting_matches_legacy() {
2549 static LIST_MEMBER: Schema<'static> =
2550 Schema::new_member(crate::shape_id!("test", "L"), ShapeType::List, "items", 0)
2551 .with_http_header("X-Items");
2552 static LIST_SCHEMA: Schema<'static> = Schema::new_struct(
2553 crate::shape_id!("test", "L"),
2554 ShapeType::Structure,
2555 &[&LIST_MEMBER],
2556 );
2557 struct ListStruct;
2558 impl SerializableStruct for ListStruct {
2559 fn schema(&self) -> &Schema<'_> {
2560 &LIST_SCHEMA
2561 }
2562 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2563 s.write_list(&LIST_MEMBER, &|e| {
2564 for v in ["plain", " a", "b ", "(x)", "c,d", "q\"q"] {
2565 e.write_string(&crate::prelude::STRING, v)?;
2566 }
2567 Ok(())
2568 })
2569 }
2570 }
2571 let request = make_protocol()
2572 .serialize_request(
2573 &ListStruct,
2574 &LIST_SCHEMA,
2575 "https://example.com",
2576 &ConfigBag::base(),
2577 )
2578 .unwrap();
2579 assert_eq!(
2580 request.headers().get("X-Items").unwrap(),
2581 r#"plain, " a", "b ", "(x)", "c,d", "q\"q""#
2582 );
2583 }
2584
2585 #[test]
2594 fn http_header_on_a_runtime_built_schema() {
2595 let arena: Vec<String> = vec![
2599 String::from("token"),
2600 String::from("runtime-value"),
2601 String::from("X-Interned-Token"),
2602 ];
2603
2604 let member: Schema<'_> = Schema::new_member(
2605 crate::shape_id!("test", "S"),
2606 ShapeType::String,
2607 &arena[0],
2608 0,
2609 )
2610 .with_http_header(crate::intern_header_name(&arena[2]));
2611
2612 assert_eq!(
2614 member.http_header().unwrap().value_static(),
2615 Some("X-Interned-Token")
2616 );
2617
2618 let members = [&member];
2619 let schema = Schema::new_struct(
2620 crate::shape_id!("test", "S"),
2621 ShapeType::Structure,
2622 &members,
2623 );
2624
2625 struct RuntimeStruct<'a> {
2626 schema: &'a Schema<'a>,
2627 member: &'a Schema<'a>,
2628 value: &'a str,
2629 }
2630 impl SerializableStruct for RuntimeStruct<'_> {
2631 fn schema(&self) -> &Schema<'_> {
2632 self.schema
2633 }
2634
2635 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2636 s.write_string(self.member, self.value)
2637 }
2638 }
2639 let input = RuntimeStruct {
2640 schema: &schema,
2641 member: &member,
2642 value: &arena[1],
2643 };
2644 assert!(std::ptr::eq(input.schema(), &schema));
2645
2646 let request = make_protocol()
2647 .serialize_request(&input, &schema, "https://example.com", &ConfigBag::base())
2648 .unwrap();
2649
2650 assert_eq!(
2651 request.headers().get("X-Interned-Token").unwrap(),
2652 "runtime-value"
2653 );
2654 }
2655
2656 static INT_HEADER_MEMBER: Schema<'static> = Schema::new_member(
2657 crate::shape_id!("test", "S"),
2658 ShapeType::Integer,
2659 "retryCount",
2660 0,
2661 )
2662 .with_http_header("X-Retry-Count");
2663
2664 static INT_HEADER_SCHEMA: Schema<'static> = Schema::new_struct(
2665 crate::shape_id!("test", "S"),
2666 ShapeType::Structure,
2667 &[&INT_HEADER_MEMBER],
2668 );
2669
2670 struct IntHeaderStruct;
2671 impl SerializableStruct for IntHeaderStruct {
2672 fn schema(&self) -> &Schema<'_> {
2673 &INT_HEADER_SCHEMA
2674 }
2675
2676 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2677 s.write_integer(&INT_HEADER_MEMBER, 3)
2678 }
2679 }
2680
2681 #[test]
2682 fn http_header_integer() {
2683 let request = make_protocol()
2684 .serialize_request(
2685 &IntHeaderStruct,
2686 &INT_HEADER_SCHEMA,
2687 "https://example.com",
2688 &ConfigBag::base(),
2689 )
2690 .unwrap();
2691 assert_eq!(request.headers().get("X-Retry-Count").unwrap(), "3");
2692 }
2693
2694 static BOOL_HEADER_MEMBER: Schema<'static> = Schema::new_member(
2695 crate::shape_id!("test", "S"),
2696 ShapeType::Boolean,
2697 "verbose",
2698 0,
2699 )
2700 .with_http_header("X-Verbose");
2701
2702 static BOOL_HEADER_SCHEMA: Schema<'static> = Schema::new_struct(
2703 crate::shape_id!("test", "S"),
2704 ShapeType::Structure,
2705 &[&BOOL_HEADER_MEMBER],
2706 );
2707
2708 struct BoolHeaderStruct;
2709 impl SerializableStruct for BoolHeaderStruct {
2710 fn schema(&self) -> &Schema<'_> {
2711 &BOOL_HEADER_SCHEMA
2712 }
2713
2714 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2715 s.write_boolean(&BOOL_HEADER_MEMBER, true)
2716 }
2717 }
2718
2719 #[test]
2720 fn http_header_boolean() {
2721 let request = make_protocol()
2722 .serialize_request(
2723 &BoolHeaderStruct,
2724 &BOOL_HEADER_SCHEMA,
2725 "https://example.com",
2726 &ConfigBag::base(),
2727 )
2728 .unwrap();
2729 assert_eq!(request.headers().get("X-Verbose").unwrap(), "true");
2730 }
2731
2732 static QUERY_MEMBER: Schema<'static> =
2735 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "color", 0)
2736 .with_http_query("color");
2737
2738 static QUERY_SCHEMA: Schema<'static> = Schema::new_struct(
2739 crate::shape_id!("test", "S"),
2740 ShapeType::Structure,
2741 &[&QUERY_MEMBER],
2742 );
2743
2744 struct QueryStruct;
2745 impl SerializableStruct for QueryStruct {
2746 fn schema(&self) -> &Schema<'_> {
2747 &QUERY_SCHEMA
2748 }
2749
2750 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2751 s.write_string(&QUERY_MEMBER, "blue")
2752 }
2753 }
2754
2755 #[test]
2756 fn http_query_string() {
2757 let request = make_protocol()
2758 .serialize_request(
2759 &QueryStruct,
2760 &QUERY_SCHEMA,
2761 "https://example.com/things",
2762 &ConfigBag::base(),
2763 )
2764 .unwrap();
2765 assert_eq!(request.uri(), "https://example.com/things?color=blue");
2766 }
2767
2768 static INT_QUERY_MEMBER: Schema<'static> =
2769 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Integer, "size", 0)
2770 .with_http_query("size");
2771
2772 static INT_QUERY_SCHEMA: Schema<'static> = Schema::new_struct(
2773 crate::shape_id!("test", "S"),
2774 ShapeType::Structure,
2775 &[&INT_QUERY_MEMBER],
2776 );
2777
2778 struct IntQueryStruct;
2779 impl SerializableStruct for IntQueryStruct {
2780 fn schema(&self) -> &Schema<'_> {
2781 &INT_QUERY_SCHEMA
2782 }
2783
2784 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2785 s.write_integer(&INT_QUERY_MEMBER, 42)
2786 }
2787 }
2788
2789 #[test]
2790 fn http_query_integer() {
2791 let request = make_protocol()
2792 .serialize_request(
2793 &IntQueryStruct,
2794 &INT_QUERY_SCHEMA,
2795 "https://example.com/things",
2796 &ConfigBag::base(),
2797 )
2798 .unwrap();
2799 assert_eq!(request.uri(), "https://example.com/things?size=42");
2800 }
2801
2802 static Q1: Schema<'static> =
2805 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "a", 0)
2806 .with_http_query("a");
2807 static Q2: Schema<'static> =
2808 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "b", 1)
2809 .with_http_query("b");
2810 static MULTI_QUERY_SCHEMA: Schema<'static> = Schema::new_struct(
2811 crate::shape_id!("test", "S"),
2812 ShapeType::Structure,
2813 &[&Q1, &Q2],
2814 );
2815
2816 struct MultiQueryStruct;
2817 impl SerializableStruct for MultiQueryStruct {
2818 fn schema(&self) -> &Schema<'_> {
2819 &MULTI_QUERY_SCHEMA
2820 }
2821
2822 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2823 s.write_string(&Q1, "x")?;
2824 s.write_string(&Q2, "y")
2825 }
2826 }
2827
2828 #[test]
2829 fn http_query_multiple_params() {
2830 let request = make_protocol()
2831 .serialize_request(
2832 &MultiQueryStruct,
2833 &MULTI_QUERY_SCHEMA,
2834 "https://example.com",
2835 &ConfigBag::base(),
2836 )
2837 .unwrap();
2838 assert_eq!(request.uri(), "https://example.com?a=x&b=y");
2839 }
2840
2841 #[test]
2844 fn http_query_percent_encodes_values() {
2845 struct SpaceQueryStruct;
2846 impl SerializableStruct for SpaceQueryStruct {
2847 fn schema(&self) -> &Schema<'_> {
2848 &QUERY_SCHEMA
2849 }
2850
2851 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2852 s.write_string(&QUERY_MEMBER, "hello world")
2853 }
2854 }
2855 let request = make_protocol()
2856 .serialize_request(
2857 &SpaceQueryStruct,
2858 &QUERY_SCHEMA,
2859 "https://example.com",
2860 &ConfigBag::base(),
2861 )
2862 .unwrap();
2863 assert_eq!(request.uri(), "https://example.com?color=hello%20world");
2864 }
2865
2866 static LABEL_MEMBER: Schema<'static> = Schema::new_member(
2869 crate::shape_id!("test", "S"),
2870 ShapeType::String,
2871 "bucketName",
2872 0,
2873 )
2874 .with_http_label();
2875
2876 static LABEL_SCHEMA: Schema<'static> = Schema::new_struct(
2877 crate::shape_id!("test", "S"),
2878 ShapeType::Structure,
2879 &[&LABEL_MEMBER],
2880 );
2881
2882 struct LabelStruct;
2883 impl SerializableStruct for LabelStruct {
2884 fn schema(&self) -> &Schema<'_> {
2885 &LABEL_SCHEMA
2886 }
2887
2888 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2889 s.write_string(&LABEL_MEMBER, "my-bucket")
2890 }
2891 }
2892
2893 #[test]
2894 fn http_label_substitution() {
2895 let request = make_protocol()
2896 .serialize_request(
2897 &LabelStruct,
2898 &LABEL_SCHEMA,
2899 "https://example.com/{bucketName}/objects",
2900 &ConfigBag::base(),
2901 )
2902 .unwrap();
2903 assert_eq!(request.uri(), "https://example.com/my-bucket/objects");
2904 }
2905
2906 #[test]
2907 fn http_label_percent_encodes() {
2908 struct SpecialLabelStruct;
2909 impl SerializableStruct for SpecialLabelStruct {
2910 fn schema(&self) -> &Schema<'_> {
2911 &LABEL_SCHEMA
2912 }
2913
2914 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2915 s.write_string(&LABEL_MEMBER, "my bucket/name")
2916 }
2917 }
2918 let request = make_protocol()
2919 .serialize_request(
2920 &SpecialLabelStruct,
2921 &LABEL_SCHEMA,
2922 "https://example.com/{bucketName}",
2923 &ConfigBag::base(),
2924 )
2925 .unwrap();
2926 assert!(request.uri().contains("my%20bucket%2Fname"));
2927 }
2928
2929 static INT_LABEL_MEMBER: Schema<'static> = Schema::new_member(
2930 crate::shape_id!("test", "S"),
2931 ShapeType::Integer,
2932 "itemId",
2933 0,
2934 )
2935 .with_http_label();
2936
2937 static INT_LABEL_SCHEMA: Schema<'static> = Schema::new_struct(
2938 crate::shape_id!("test", "S"),
2939 ShapeType::Structure,
2940 &[&INT_LABEL_MEMBER],
2941 );
2942
2943 struct IntLabelStruct;
2944 impl SerializableStruct for IntLabelStruct {
2945 fn schema(&self) -> &Schema<'_> {
2946 &INT_LABEL_SCHEMA
2947 }
2948
2949 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2950 s.write_integer(&INT_LABEL_MEMBER, 123)
2951 }
2952 }
2953
2954 #[test]
2955 fn http_label_integer() {
2956 let request = make_protocol()
2957 .serialize_request(
2958 &IntLabelStruct,
2959 &INT_LABEL_SCHEMA,
2960 "https://example.com/items/{itemId}",
2961 &ConfigBag::base(),
2962 )
2963 .unwrap();
2964 assert_eq!(request.uri(), "https://example.com/items/123");
2965 }
2966
2967 #[test]
2971 fn missing_or_empty_label_is_an_error() {
2972 fn expand(template: &str, labels: &[(&'static str, &str)]) -> Result<String, String> {
2973 let labels: Vec<(Cow<'_, str>, String)> = labels
2974 .iter()
2975 .map(|(n, v)| (Cow::Borrowed(*n), v.to_string()))
2976 .collect();
2977 let mut out = String::new();
2978 append_uri_with_labels(template, &labels, &mut out)
2979 .map(|()| out)
2980 .map_err(|e| e.to_string())
2981 }
2982 let template = "/{Bucket}/{Key+}";
2983 assert_eq!(
2984 expand(template, &[("Bucket", "b"), ("Key", "a//b")]).unwrap(),
2985 "/b/a//b",
2986 "an empty inner segment of a greedy label is still allowed"
2987 );
2988 for (labels, missing) in [
2989 (&[("Key", "k")][..], "Bucket"),
2990 (&[("Bucket", ""), ("Key", "k")][..], "Bucket"),
2991 (&[("Bucket", "b")][..], "Key"),
2992 (&[("Bucket", "b"), ("Key", "")][..], "Key"),
2993 ] {
2994 let err = expand(template, labels).expect_err(&format!("{labels:?}"));
2995 assert!(
2996 err.contains(&format!("{missing} was missing: cannot be empty or unset")),
2997 "{err}"
2998 );
2999 }
3000 assert_eq!(expand("/a{b", &[]).unwrap(), "/a{b");
3002 }
3003
3004 #[test]
3005 fn empty_label_fails_serialize_request() {
3006 static BUCKET: Schema<'static> = Schema::new_member(
3007 crate::shape_id!("test", "Get"),
3008 ShapeType::String,
3009 "Bucket",
3010 0,
3011 )
3012 .with_http_label();
3013 static KEY: Schema<'static> =
3014 Schema::new_member(crate::shape_id!("test", "Get"), ShapeType::String, "Key", 1)
3015 .with_http_label();
3016 static GET_SCHEMA: Schema<'static> = Schema::new_struct(
3017 crate::shape_id!("test", "Get"),
3018 ShapeType::Structure,
3019 &[&BUCKET, &KEY],
3020 )
3021 .with_http(crate::traits::HttpTrait::new(
3022 "GET",
3023 "/{Bucket}/{Key+}",
3024 None,
3025 ));
3026 struct Get(&'static str);
3027 impl SerializableStruct for Get {
3028 fn schema(&self) -> &Schema<'_> {
3029 &GET_SCHEMA
3030 }
3031 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3032 s.write_string(&BUCKET, self.0)?;
3033 s.write_string(&KEY, "my-key")
3034 }
3035 }
3036 let protocol = make_protocol();
3037 let request = protocol
3038 .serialize_request(&Get("b"), &GET_SCHEMA, "", &ConfigBag::base())
3039 .unwrap();
3040 assert_eq!(request.uri(), "/b/my-key");
3041 let err = protocol
3042 .serialize_request(&Get(""), &GET_SCHEMA, "", &ConfigBag::base())
3043 .expect_err("an empty bucket must not produce `//my-key`");
3044 assert!(
3045 err.to_string()
3046 .contains("Bucket was missing: cannot be empty or unset"),
3047 "{err}"
3048 );
3049 }
3050
3051 static COMBINED_LABEL: Schema<'static> =
3054 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "id", 0)
3055 .with_http_label();
3056 static COMBINED_HEADER: Schema<'static> =
3057 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "token", 1)
3058 .with_http_header("X-Token");
3059 static COMBINED_QUERY: Schema<'static> = Schema::new_member(
3060 crate::shape_id!("test", "S"),
3061 ShapeType::String,
3062 "filter",
3063 2,
3064 )
3065 .with_http_query("filter");
3066 static COMBINED_BODY: Schema<'static> =
3067 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "data", 3);
3068 static COMBINED_SCHEMA: Schema<'static> = Schema::new_struct(
3069 crate::shape_id!("test", "S"),
3070 ShapeType::Structure,
3071 &[
3072 &COMBINED_LABEL,
3073 &COMBINED_HEADER,
3074 &COMBINED_QUERY,
3075 &COMBINED_BODY,
3076 ],
3077 );
3078
3079 struct CombinedStruct;
3080 impl SerializableStruct for CombinedStruct {
3081 fn schema(&self) -> &Schema<'_> {
3082 &COMBINED_SCHEMA
3083 }
3084
3085 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3086 s.write_string(&COMBINED_LABEL, "item-42")?;
3087 s.write_string(&COMBINED_HEADER, "secret")?;
3088 s.write_string(&COMBINED_QUERY, "active")?;
3089 s.write_string(&COMBINED_BODY, "payload-data")
3090 }
3091 }
3092
3093 #[test]
3094 fn combined_bindings() {
3095 let request = make_protocol()
3096 .serialize_request(
3097 &CombinedStruct,
3098 &COMBINED_SCHEMA,
3099 "https://example.com/{id}/details",
3100 &ConfigBag::base(),
3101 )
3102 .unwrap();
3103 assert_eq!(
3104 request.uri(),
3105 "https://example.com/item-42/details?filter=active"
3106 );
3107 assert_eq!(request.headers().get("X-Token").unwrap(), "secret");
3109 let body = request.body().bytes().unwrap();
3111 assert!(body
3112 .windows(b"payload-data".len())
3113 .any(|w| w == b"payload-data"));
3114 }
3115
3116 static PREFIX_MEMBER: Schema<'static> =
3119 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "metadata", 0)
3120 .with_http_prefix_headers("X-Meta-");
3121
3122 static PREFIX_SCHEMA: Schema<'static> = Schema::new_struct(
3123 crate::shape_id!("test", "S"),
3124 ShapeType::Structure,
3125 &[&PREFIX_MEMBER],
3126 );
3127
3128 struct PrefixHeaderStruct;
3129 impl SerializableStruct for PrefixHeaderStruct {
3130 fn schema(&self) -> &Schema<'_> {
3131 &PREFIX_SCHEMA
3132 }
3133
3134 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3135 s.write_map(&PREFIX_MEMBER, &|s| {
3136 s.write_string(&STRING, "Color")?;
3137 s.write_string(&STRING, "red")?;
3138 s.write_string(&STRING, "Size")?;
3139 s.write_string(&STRING, "large")?;
3140 Ok(())
3141 })
3142 }
3143 }
3144
3145 #[test]
3146 fn http_prefix_headers() {
3147 let request = make_protocol()
3148 .serialize_request(
3149 &PrefixHeaderStruct,
3150 &PREFIX_SCHEMA,
3151 "https://example.com",
3152 &ConfigBag::base(),
3153 )
3154 .unwrap();
3155 assert_eq!(request.headers().get("X-Meta-Color").unwrap(), "red");
3156 assert_eq!(request.headers().get("X-Meta-Size").unwrap(), "large");
3157 }
3158
3159 fn serialize_prefix_entry(key: &'static str, value: &'static str) -> Result<(), SerdeError> {
3161 struct Entry(&'static str, &'static str);
3162 impl SerializableStruct for Entry {
3163 fn schema(&self) -> &Schema<'_> {
3164 &PREFIX_SCHEMA
3165 }
3166 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3167 s.write_map(&PREFIX_MEMBER, &|s| {
3168 s.write_string(&STRING, self.0)?;
3169 s.write_string(&STRING, self.1)
3170 })
3171 }
3172 }
3173 make_protocol()
3174 .serialize_request(
3175 &Entry(key, value),
3176 &PREFIX_SCHEMA,
3177 "https://example.com",
3178 &ConfigBag::base(),
3179 )
3180 .map(|_| ())
3181 }
3182
3183 #[test]
3187 fn invalid_prefix_header_name_or_value_is_an_error_not_a_panic() {
3188 for (key, value) in [
3189 ("has space", "secret-1"),
3190 ("colon:", "secret-2"),
3191 ("non-ascii-é", "secret-3"),
3192 ("Ok", "secret\nbreak"),
3193 ] {
3194 let err = serialize_prefix_entry(key, value)
3195 .expect_err(&format!("{key:?}/{value:?} must be rejected"));
3196 let message = err.to_string();
3197 assert!(
3198 message.contains("metadata") && message.contains("prefix `X-Meta-`"),
3199 "{message}"
3200 );
3201 assert!(!message.contains(value), "value leaked: {message}");
3202 }
3203 serialize_prefix_entry("Ok", "fine").expect("a valid entry still serializes");
3204 }
3205
3206 #[test]
3207 fn invalid_header_value_is_an_error_not_a_panic() {
3208 struct Scalar;
3209 impl SerializableStruct for Scalar {
3210 fn schema(&self) -> &Schema<'_> {
3211 &HEADER_SCHEMA
3212 }
3213 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3214 s.write_string(&HEADER_MEMBER, "secret\r\ninjected: 1")
3215 }
3216 }
3217 static LIST_MEMBER: Schema<'static> =
3218 Schema::new_member(crate::shape_id!("test", "L"), ShapeType::List, "items", 0)
3219 .with_http_header("X-Items");
3220 static LIST_SCHEMA: Schema<'static> = Schema::new_struct(
3221 crate::shape_id!("test", "L"),
3222 ShapeType::Structure,
3223 &[&LIST_MEMBER],
3224 );
3225 struct List;
3226 impl SerializableStruct for List {
3227 fn schema(&self) -> &Schema<'_> {
3228 &LIST_SCHEMA
3229 }
3230 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3231 s.write_list(&LIST_MEMBER, &|e| {
3232 e.write_string(&STRING, "ok")?;
3233 e.write_string(&STRING, "secret\nvalue")
3234 })
3235 }
3236 }
3237 let protocol = make_protocol();
3238 let cases: [(&dyn SerializableStruct, &Schema<'static>, &str); 2] = [
3239 (&Scalar, &HEADER_SCHEMA, "xToken to header `X-Token`"),
3240 (&List, &LIST_SCHEMA, "items to header `X-Items`"),
3241 ];
3242 for (input, schema, expected) in cases {
3243 let err = protocol
3244 .serialize_request(input, schema, "https://example.com", &ConfigBag::base())
3245 .expect_err("an invalid header value must be rejected");
3246 let message = err.to_string();
3247 assert!(message.contains(expected), "{message}");
3248 assert!(!message.contains("secret"), "value leaked: {message}");
3249 }
3250 }
3251
3252 #[test]
3256 fn invalid_modeled_header_name_is_an_error_not_a_panic() {
3257 static BAD_MEMBER: Schema<'static> =
3258 Schema::new_member(crate::shape_id!("test", "B"), ShapeType::String, "bad", 0)
3259 .with_http_header("has space");
3260 static BAD_SCHEMA: Schema<'static> = Schema::new_struct(
3261 crate::shape_id!("test", "B"),
3262 ShapeType::Structure,
3263 &[&BAD_MEMBER],
3264 );
3265 struct Bad;
3266 impl SerializableStruct for Bad {
3267 fn schema(&self) -> &Schema<'_> {
3268 &BAD_SCHEMA
3269 }
3270 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3271 s.write_string(&BAD_MEMBER, "value")
3272 }
3273 }
3274 let protocol = make_protocol();
3275 for _ in 0..2 {
3276 let err = protocol
3277 .serialize_request(&Bad, &BAD_SCHEMA, "https://example.com", &ConfigBag::base())
3278 .expect_err("an invalid header name must be rejected");
3279 let message = err.to_string();
3280 assert!(message.contains("bad to header `has space`"), "{message}");
3281 }
3282 }
3283
3284 #[test]
3287 fn cached_header_names_are_reused_by_every_header_write() {
3288 static SCALAR: Schema<'static> = Schema::new_member(
3289 crate::shape_id!("test", "H"),
3290 ShapeType::String,
3291 "scalar",
3292 0,
3293 )
3294 .with_http_header("X-Amz-Scalar");
3295 static LIST: Schema<'static> =
3296 Schema::new_member(crate::shape_id!("test", "H"), ShapeType::List, "list", 1)
3297 .with_http_header("x-amz-list");
3298 static BLOB: Schema<'static> =
3299 Schema::new_member(crate::shape_id!("test", "H"), ShapeType::Blob, "blob", 2)
3300 .with_http_header("Content-MD5");
3301 static SCHEMA: Schema<'static> = Schema::new_struct(
3302 crate::shape_id!("test", "H"),
3303 ShapeType::Structure,
3304 &[&SCALAR, &LIST, &BLOB],
3305 );
3306 struct Headers3;
3307 impl SerializableStruct for Headers3 {
3308 fn schema(&self) -> &Schema<'_> {
3309 &SCHEMA
3310 }
3311 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3312 s.write_string(&SCALAR, "one")?;
3313 s.write_list(&LIST, &|e| {
3314 e.write_string(&STRING, "a")?;
3315 e.write_string(&STRING, "b")
3316 })?;
3317 s.write_blob(&BLOB, aws_smithy_types::Blob::new("md5"))
3318 }
3319 }
3320 let protocol = make_protocol();
3321 for _ in 0..2 {
3322 let request = protocol
3323 .serialize_request(
3324 &Headers3,
3325 &SCHEMA,
3326 "https://example.com",
3327 &ConfigBag::base(),
3328 )
3329 .unwrap();
3330 let mut headers: Vec<_> = request
3331 .headers()
3332 .iter()
3333 .filter(|(name, _)| !name.starts_with("content-type"))
3334 .collect();
3335 headers.sort();
3336 assert_eq!(
3337 headers,
3338 [
3339 ("content-md5", "bWQ1"),
3340 ("x-amz-list", "a, b"),
3341 ("x-amz-scalar", "one"),
3342 ]
3343 );
3344 }
3345 }
3346
3347 static QUERY_PARAMS_MEMBER: Schema<'static> =
3350 Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "params", 0)
3351 .with_http_query_params();
3352
3353 static QUERY_PARAMS_SCHEMA: Schema<'static> = Schema::new_struct(
3354 crate::shape_id!("test", "S"),
3355 ShapeType::Structure,
3356 &[&QUERY_PARAMS_MEMBER],
3357 );
3358
3359 struct QueryParamsStruct;
3360 impl SerializableStruct for QueryParamsStruct {
3361 fn schema(&self) -> &Schema<'_> {
3362 &QUERY_PARAMS_SCHEMA
3363 }
3364
3365 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3366 s.write_map(&QUERY_PARAMS_MEMBER, &|s| {
3367 s.write_string(&STRING, "page")?;
3368 s.write_string(&STRING, "2")?;
3369 s.write_string(&STRING, "limit")?;
3370 s.write_string(&STRING, "50")?;
3371 Ok(())
3372 })
3373 }
3374 }
3375
3376 #[test]
3377 fn http_query_params() {
3378 let request = make_protocol()
3379 .serialize_request(
3380 &QueryParamsStruct,
3381 &QUERY_PARAMS_SCHEMA,
3382 "https://example.com",
3383 &ConfigBag::base(),
3384 )
3385 .unwrap();
3386 assert_eq!(request.uri(), "https://example.com?page=2&limit=50");
3387 }
3388
3389 static TS_HEADER_MEMBER: Schema<'static> = Schema::new_member(
3392 crate::shape_id!("test", "S"),
3393 ShapeType::Timestamp,
3394 "ifModified",
3395 0,
3396 )
3397 .with_http_header("If-Modified-Since");
3398
3399 static TS_HEADER_SCHEMA: Schema<'static> = Schema::new_struct(
3400 crate::shape_id!("test", "S"),
3401 ShapeType::Structure,
3402 &[&TS_HEADER_MEMBER],
3403 );
3404
3405 struct TimestampHeaderStruct;
3406 impl SerializableStruct for TimestampHeaderStruct {
3407 fn schema(&self) -> &Schema<'_> {
3408 &TS_HEADER_SCHEMA
3409 }
3410
3411 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3412 s.write_timestamp(&TS_HEADER_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
3413 }
3414 }
3415
3416 #[test]
3417 fn timestamp_header_uses_http_date() {
3418 let request = make_protocol()
3419 .serialize_request(
3420 &TimestampHeaderStruct,
3421 &TS_HEADER_SCHEMA,
3422 "https://example.com",
3423 &ConfigBag::base(),
3424 )
3425 .unwrap();
3426 let value = request.headers().get("If-Modified-Since").unwrap();
3427 assert!(value.contains("1970"), "expected http-date, got: {value}");
3429 }
3430
3431 static TS_QUERY_MEMBER: Schema<'static> = Schema::new_member(
3434 crate::shape_id!("test", "S"),
3435 ShapeType::Timestamp,
3436 "since",
3437 0,
3438 )
3439 .with_http_query("since");
3440
3441 static TS_QUERY_SCHEMA: Schema<'static> = Schema::new_struct(
3442 crate::shape_id!("test", "S"),
3443 ShapeType::Structure,
3444 &[&TS_QUERY_MEMBER],
3445 );
3446
3447 struct TimestampQueryStruct;
3448 impl SerializableStruct for TimestampQueryStruct {
3449 fn schema(&self) -> &Schema<'_> {
3450 &TS_QUERY_SCHEMA
3451 }
3452
3453 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3454 s.write_timestamp(&TS_QUERY_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
3455 }
3456 }
3457
3458 #[test]
3459 fn timestamp_query_uses_date_time() {
3460 let request = make_protocol()
3461 .serialize_request(
3462 &TimestampQueryStruct,
3463 &TS_QUERY_SCHEMA,
3464 "https://example.com",
3465 &ConfigBag::base(),
3466 )
3467 .unwrap();
3468 assert_eq!(
3469 request.uri(),
3470 "https://example.com?since=1970-01-01T00%3A00%3A00Z"
3471 );
3472 }
3473
3474 static BOUND_MEMBER: Schema<'static> = Schema::new_member(
3477 crate::shape_id!("test", "S"),
3478 ShapeType::String,
3479 "headerVal",
3480 0,
3481 )
3482 .with_http_header("X-Val");
3483 static UNBOUND_MEMBER: Schema<'static> = Schema::new_member(
3484 crate::shape_id!("test", "S"),
3485 ShapeType::String,
3486 "bodyVal",
3487 1,
3488 );
3489 static MIXED_SCHEMA: Schema<'static> = Schema::new_struct(
3490 crate::shape_id!("test", "S"),
3491 ShapeType::Structure,
3492 &[&BOUND_MEMBER, &UNBOUND_MEMBER],
3493 );
3494
3495 struct MixedStruct;
3496 impl SerializableStruct for MixedStruct {
3497 fn schema(&self) -> &Schema<'_> {
3498 &MIXED_SCHEMA
3499 }
3500
3501 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
3502 s.write_string(&BOUND_MEMBER, "in-header")?;
3503 s.write_string(&UNBOUND_MEMBER, "in-body")
3504 }
3505 }
3506
3507 #[test]
3508 fn bound_members_not_in_body() {
3509 let request = make_protocol()
3510 .serialize_request(
3511 &MixedStruct,
3512 &MIXED_SCHEMA,
3513 "https://example.com",
3514 &ConfigBag::base(),
3515 )
3516 .unwrap();
3517 let body = std::str::from_utf8(request.body().bytes().unwrap()).unwrap();
3518 assert!(
3519 body.contains("in-body"),
3520 "body should contain unbound member"
3521 );
3522 assert!(
3523 !body.contains("in-header"),
3524 "body should NOT contain header-bound member"
3525 );
3526 assert_eq!(request.headers().get("X-Val").unwrap(), "in-header");
3527 }
3528
3529 static RESP_NAME: Schema<'static> = Schema::new_member(
3538 crate::shape_id!("test", "Out"),
3539 ShapeType::String,
3540 "name",
3541 0,
3542 )
3543 .with_http_header("x-name");
3544 static RESP_STATUS: Schema<'static> = Schema::new_member(
3545 crate::shape_id!("test", "Out"),
3546 ShapeType::Integer,
3547 "status",
3548 1,
3549 )
3550 .with_http_response_code();
3551 static RESP_BOUND: Schema<'static> = Schema::new_struct(
3552 crate::shape_id!("test", "Out"),
3553 ShapeType::Structure,
3554 &[&RESP_NAME, &RESP_STATUS],
3555 );
3556
3557 static RESP_BODY_FIELD: Schema<'static> = Schema::new_member(
3558 crate::shape_id!("test", "Out"),
3559 ShapeType::String,
3560 "note",
3561 2,
3562 );
3563 static RESP_MIXED: Schema<'static> = Schema::new_struct(
3566 crate::shape_id!("test", "Out"),
3567 ShapeType::Structure,
3568 &[&RESP_NAME, &RESP_STATUS, &RESP_BODY_FIELD],
3569 );
3570
3571 static RESP_STREAM: Schema<'static> = Schema::new_member(
3572 crate::shape_id!("test", "Stream"),
3573 ShapeType::Blob,
3574 "body",
3575 0,
3576 )
3577 .with_http_payload()
3578 .with_streaming();
3579 static RESP_STREAM_HEADER: Schema<'static> = Schema::new_member(
3580 crate::shape_id!("test", "Stream"),
3581 ShapeType::String,
3582 "name",
3583 1,
3584 )
3585 .with_http_header("x-name");
3586 static RESP_STREAMING_OUT: Schema<'static> = Schema::new_struct(
3587 crate::shape_id!("test", "Stream"),
3588 ShapeType::Structure,
3589 &[&RESP_STREAM, &RESP_STREAM_HEADER],
3590 );
3591
3592 fn response_with(
3593 status: u16,
3594 headers: &[(&str, &str)],
3595 body: aws_smithy_types::body::SdkBody,
3596 ) -> Response {
3597 let mut builder = http::Response::builder().status(status);
3598 for (name, value) in headers {
3599 builder = builder.header(*name, *value);
3600 }
3601 Response::try_from(builder.body(body).expect("response")).expect("convertible")
3602 }
3603
3604 fn collect<'c>(
3606 strings: &'c mut Vec<(String, String)>,
3607 integers: &'c mut Vec<(String, i32)>,
3608 ) -> impl FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError> + 'c {
3609 move |member, deser| {
3610 let name = member.member_name().unwrap_or("?").to_string();
3611 match member.shape_type() {
3612 ShapeType::Integer => integers.push((name, deser.read_integer(member)?)),
3613 _ => strings.push((name, deser.read_string(member)?)),
3614 }
3615 Ok(())
3616 }
3617 }
3618
3619 #[test]
3620 fn deserialize_response_reads_modeled_headers_and_status() {
3621 let response = response_with(
3622 201,
3623 &[("x-name", "widget")],
3624 aws_smithy_types::body::SdkBody::from("{}"),
3625 );
3626 let mut strings = Vec::new();
3627 let mut integers = Vec::new();
3628 make_protocol()
3629 .deserialize_response(&response, &RESP_BOUND, &ConfigBag::base())
3630 .unwrap()
3631 .read_struct(&RESP_BOUND, &mut collect(&mut strings, &mut integers))
3632 .unwrap();
3633 assert_eq!(strings, vec![("name".to_string(), "widget".to_string())]);
3634 assert_eq!(integers, vec![("status".to_string(), 201)]);
3635 }
3636
3637 #[test]
3638 fn deserialize_error_response_tolerates_an_empty_body_and_still_reads_bindings() {
3639 let response = response_with(
3646 404,
3647 &[("x-name", "missing")],
3648 aws_smithy_types::body::SdkBody::empty(),
3649 );
3650 let mut strings = Vec::new();
3651 let mut integers = Vec::new();
3652 make_protocol()
3653 .deserialize_error_response(&response, &ConfigBag::base())
3654 .unwrap()
3655 .read_struct(&RESP_MIXED, &mut collect(&mut strings, &mut integers))
3656 .expect("an empty error body must not be handed to the codec");
3657 assert_eq!(strings, vec![("name".to_string(), "missing".to_string())]);
3658 assert_eq!(integers, vec![("status".to_string(), 404)]);
3659 }
3660
3661 #[test]
3662 fn deserialize_response_keeps_the_codecs_empty_body_strictness_for_a_success() {
3663 let response = response_with(
3666 200,
3667 &[("x-name", "here")],
3668 aws_smithy_types::body::SdkBody::empty(),
3669 );
3670 let mut strings = Vec::new();
3671 let mut integers = Vec::new();
3672 let err = make_protocol()
3673 .deserialize_response(&response, &RESP_MIXED, &ConfigBag::base())
3674 .unwrap()
3675 .read_struct(&RESP_MIXED, &mut collect(&mut strings, &mut integers))
3676 .expect_err("a successful response still hands an empty body to the codec");
3677 assert!(
3678 format!("{err}").contains("expected a body"),
3679 "unexpected: {err}"
3680 );
3681 }
3682
3683 #[test]
3684 fn deserialize_response_reads_headers_without_consuming_a_streaming_payload() {
3685 let response = response_with(
3689 200,
3690 &[("x-name", "stream")],
3691 aws_smithy_types::body::SdkBody::taken(),
3692 );
3693 assert!(
3694 response.body().bytes().is_none(),
3695 "precondition: the body is not buffered"
3696 );
3697 let mut strings = Vec::new();
3698 let mut integers = Vec::new();
3699 make_protocol()
3700 .deserialize_response(&response, &RESP_STREAMING_OUT, &ConfigBag::base())
3701 .unwrap()
3702 .read_struct(
3703 &RESP_STREAMING_OUT,
3704 &mut collect(&mut strings, &mut integers),
3705 )
3706 .unwrap();
3707 assert_eq!(
3708 strings,
3709 vec![("name".to_string(), "stream".to_string())],
3710 "the header must be read and the streaming payload left untouched"
3711 );
3712 }
3713
3714 #[test]
3715 fn deserialize_response_still_hands_a_body_only_output_to_the_codec() {
3716 let response = response_with(
3719 200,
3720 &[],
3721 aws_smithy_types::body::SdkBody::from(r#"{"name":"Bob"}"#),
3722 );
3723 let base_cfg = ConfigBag::base();
3724 let mut deser = make_protocol()
3725 .deserialize_response(&response, &STRUCT_WITH_MEMBER, &base_cfg)
3726 .unwrap();
3727 assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"name":"Bob"}"#);
3728 }
3729}