use crate::codec::{Codec, FinishSerializer};
use crate::protocol::{apply_http_endpoint, ClientProtocolInner};
use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
use crate::{Schema, ShapeId};
use aws_smithy_runtime_api::http::{Headers, Request, Response};
use aws_smithy_types::body::SdkBody;
use aws_smithy_types::config_bag::ConfigBag;
use std::borrow::Cow;
#[derive(Debug)]
pub struct HttpBindingProtocol<C> {
protocol_id: ShapeId,
codec: C,
content_type: &'static str,
}
impl<C: Codec> HttpBindingProtocol<C> {
pub fn new(protocol_id: ShapeId, codec: C, content_type: &'static str) -> Self {
Self {
protocol_id,
codec,
content_type,
}
}
pub fn codec(&self) -> &C {
&self.codec
}
pub fn serialize_request_with_body(
&self,
body: <C as Codec>::Serializer,
input: &dyn SerializableStruct,
input_schema: &Schema,
endpoint: &str,
cfg: &ConfigBag,
) -> Result<Request, SerdeError> {
let mut request = Request::new(SdkBody::empty());
let has_struct_payload = input_schema.members().iter().any(|m| {
m.http_payload().is_some()
&& matches!(
m.shape_type(),
crate::ShapeType::Structure | crate::ShapeType::Union
)
});
let skip_body_codec = !input_schema.has_body_members() && !has_struct_payload;
let (raw_payload, body_bytes, query_params, labels) = {
let mut binder =
HttpBindingSerializer::new(body, Some(input_schema), request.headers_mut());
if skip_body_codec || has_struct_payload {
binder.is_top_level = false;
input.serialize_members(&mut binder)?;
} else {
binder.write_struct(input_schema, input)?;
}
let raw_payload = binder.raw_payload;
let body_bytes = if raw_payload.is_some() || skip_body_codec {
Vec::new()
} else {
binder.body.finish()
};
(raw_payload, body_bytes, binder.query_params, binder.labels)
};
let has_blob_or_string_payload = raw_payload.is_some();
let has_body_members = has_struct_payload
|| (input_schema.has_body_members()
&& input_schema.members().iter().any(|m| {
m.http_header().is_none()
&& m.http_query().is_none()
&& m.http_label().is_none()
&& m.http_prefix_headers().is_none()
&& m.http_query_params().is_none()
&& m.http_payload().is_none()
}));
let mut body_bytes = body_bytes;
let set_content_type = if has_blob_or_string_payload {
false
} else if has_body_members {
true
} else {
body_bytes = Vec::new();
false
};
let template_opt = input_schema.http().map(|h| h.uri());
let mut uri =
String::with_capacity(endpoint.len() + template_opt.map(|t| t.len()).unwrap_or(1) + 64);
match template_opt {
Some(template) => {
if !endpoint.is_empty() {
uri.push_str(endpoint);
}
append_uri_with_labels(template, &labels, &mut uri);
}
None => {
if endpoint.is_empty() {
uri.push('/');
} else {
append_uri_with_labels(endpoint, &labels, &mut uri);
}
}
}
if !query_params.is_empty() {
uri.push(if uri.contains('?') { '&' } else { '?' });
let mut first = true;
for (k, v) in &query_params {
if !first {
uri.push('&');
}
percent_encode_into(k, &mut uri);
uri.push('=');
percent_encode_into(v, &mut uri);
first = false;
}
}
*request.body_mut() = if let Some(payload) = raw_payload {
SdkBody::from(payload)
} else {
SdkBody::from(body_bytes)
};
if let Some(http) = input_schema.http() {
request
.set_method(http.method())
.map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
}
request
.set_uri(uri.as_str())
.map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
let omit = cfg.load::<crate::header_omit_settings::SharedHeaderOmitSettings>();
let omit_content_type = omit
.map(|s| s.should_omit_default_content_type())
.unwrap_or(false);
let omit_content_length = omit
.map(|s| s.should_omit_default_content_length())
.unwrap_or(false);
if !omit_content_type && set_content_type && request.headers().get("Content-Type").is_none()
{
request
.headers_mut()
.insert("Content-Type", self.content_type);
}
if !omit_content_length {
if let Some(len) = request.body().content_length() {
if (len > 0 || set_content_type)
&& request.headers().get("Content-Length").is_none()
{
request
.headers_mut()
.insert("Content-Length", len.to_string());
}
}
}
Ok(request)
}
}
pub fn percent_encode(input: &str) -> String {
let mut out = String::with_capacity(input.len());
percent_encode_into(input, &mut out);
out
}
pub fn percent_encode_into(input: &str, out: &mut String) {
let bytes = input.as_bytes();
let mut start = 0usize;
for (i, &b) in bytes.iter().enumerate() {
let safe = matches!(
b,
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
);
if !safe {
if start < i {
out.push_str(&input[start..i]);
}
out.push('%');
out.push(char::from(HEX[(b >> 4) as usize]));
out.push(char::from(HEX[(b & 0x0f) as usize]));
start = i + 1;
}
}
if start < bytes.len() {
out.push_str(&input[start..]);
}
}
fn append_uri_with_labels(
template: &str,
labels: &[(Cow<'static, str>, String)],
out: &mut String,
) {
let mut rem = template;
while let Some(open) = rem.find('{') {
out.push_str(&rem[..open]);
let after_open = &rem[open + 1..];
let close = match after_open.find('}') {
Some(c) => c,
None => {
out.push('{');
rem = after_open;
continue;
}
};
let label = &after_open[..close];
let (name, greedy) = match label.strip_suffix('+') {
Some(n) => (n, true),
None => (label, false),
};
let value = labels
.iter()
.find(|(n, _)| n.as_ref() == name)
.map(|(_, v)| v.as_str());
if let Some(v) = value {
if greedy {
let mut first = true;
for seg in v.split('/') {
if !first {
out.push('/');
}
percent_encode_into(seg, out);
first = false;
}
} else {
percent_encode_into(v, out);
}
}
rem = &after_open[close + 1..];
}
if !rem.is_empty() {
out.push_str(rem);
}
}
pub(crate) const HEX: &[u8; 16] = b"0123456789ABCDEF";
struct HttpBindingSerializer<'a, S> {
body: S,
headers: &'a mut Headers,
query_params: Vec<(Cow<'static, str>, String)>,
labels: Vec<(Cow<'static, str>, String)>,
input_schema: Option<&'a Schema>,
is_top_level: bool,
raw_payload: Option<&'a [u8]>,
visited_bound_members: VisitedMembers,
}
#[derive(Debug)]
struct VisitedMembers {
inline: [u32; Self::INLINE_CAPACITY],
inline_len: u8,
overflow: Vec<u32>,
}
impl VisitedMembers {
const INLINE_CAPACITY: usize = 24;
const fn new() -> Self {
Self {
inline: [0; Self::INLINE_CAPACITY],
inline_len: 0,
overflow: Vec::new(),
}
}
fn insert(&mut self, idx: usize) -> bool {
let idx = idx.min(u32::MAX as usize) as u32;
let len = self.inline_len as usize;
if self.inline[..len].contains(&idx) {
return false;
}
if !self.overflow.is_empty() && self.overflow.contains(&idx) {
return false;
}
if len < Self::INLINE_CAPACITY {
self.inline[len] = idx;
self.inline_len += 1;
} else {
self.overflow.push(idx);
}
true
}
}
impl<'a, S> HttpBindingSerializer<'a, S> {
fn new(body: S, input_schema: Option<&'a Schema>, headers: &'a mut Headers) -> Self {
Self {
body,
headers,
query_params: Vec::new(),
labels: Vec::new(),
input_schema,
is_top_level: true,
raw_payload: None,
visited_bound_members: VisitedMembers::new(),
}
}
fn should_route_binding(&mut self, schema: &Schema) -> bool {
schema
.member_index()
.map(|idx| self.visited_bound_members.insert(idx))
.unwrap_or(true)
}
fn resolve_member<'s>(&self, schema: &'s Schema) -> &'s Schema
where
'a: 's,
{
if let (Some(input_schema), Some(idx)) = (self.input_schema, schema.member_index()) {
input_schema.member_schema_by_index(idx).unwrap_or(schema)
} else if let (Some(input_schema), Some(name)) = (self.input_schema, schema.member_name()) {
input_schema.member_schema(name).unwrap_or(schema)
} else {
schema
}
}
}
impl<'a, S: ShapeSerializer> ShapeSerializer for HttpBindingSerializer<'a, S> {
fn write_struct(
&mut self,
schema: &Schema,
value: &dyn SerializableStruct,
) -> Result<(), SerdeError> {
if self.is_top_level {
struct Proxy<'a, 'b, S> {
binder: &'a mut HttpBindingSerializer<'b, S>,
value: &'a dyn SerializableStruct,
}
impl<S: ShapeSerializer> SerializableStruct for Proxy<'_, '_, S> {
fn serialize_members(
&self,
_serializer: &mut dyn ShapeSerializer,
) -> Result<(), SerdeError> {
let binder = self.binder as *const HttpBindingSerializer<'_, S>
as *mut HttpBindingSerializer<'_, S>;
self.value.serialize_members(unsafe { &mut *binder })
}
}
self.is_top_level = false;
let proxy = Proxy {
binder: self,
value,
};
let binder_ptr = &mut *proxy.binder as *mut HttpBindingSerializer<'_, S>;
unsafe { (*binder_ptr).body.write_struct(schema, &proxy) }
} else {
let schema = self.resolve_member(schema);
if schema.http_payload().is_some() {
self.body.write_struct(schema, value)?;
return Ok(());
}
self.body.write_struct(schema, value)
}
}
fn write_list(
&mut self,
schema: &Schema,
write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(header) = schema.http_header() {
if !self.should_route_binding(schema) {
return Ok(());
}
let mut collector = ListElementCollector::for_header();
write_elements(&mut collector)?;
let header_val = collector
.values
.iter()
.zip(collector.quotable.iter())
.map(|(s, "able)| {
if quotable && (s.contains(',') || s.contains('"')) {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
} else {
s.clone()
}
})
.collect::<Vec<_>>()
.join(", ");
self.headers.insert(header.value(), header_val);
return Ok(());
}
if let Some(query) = schema.http_query() {
if !self.should_route_binding(schema) {
return Ok(());
}
let mut collector = ListElementCollector::for_query();
write_elements(&mut collector)?;
for val in collector.values {
self.query_params.push((Cow::Borrowed(query.value()), val));
}
return Ok(());
}
self.body.write_list(schema, write_elements)
}
fn write_map(
&mut self,
schema: &Schema,
write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(prefix) = schema.http_prefix_headers() {
if !self.should_route_binding(schema) {
return Ok(());
}
let mut collector = MapEntryCollector::new(prefix.value().to_string());
write_entries(&mut collector)?;
for (k, v) in collector.entries {
self.headers.insert(k, v);
}
return Ok(());
}
if schema.http_query_params().is_some() {
if !self.should_route_binding(schema) {
return Ok(());
}
let mut collector = MapEntryCollector::new(String::new());
write_entries(&mut collector)?;
let explicit_query_keys: Vec<&str> = self
.input_schema
.map(|s| {
s.members()
.iter()
.filter_map(|m| m.http_query().map(|q| q.value()))
.collect()
})
.unwrap_or_default();
for (k, v) in collector.entries {
if !explicit_query_keys.contains(&k.as_str()) {
self.query_params.push((Cow::Owned(k), v));
}
}
return Ok(());
}
self.body.write_map(schema, write_entries)
}
fn write_boolean(&mut self, schema: &Schema, value: bool) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &value.to_string());
}
self.body.write_boolean(schema, value)
}
fn write_byte(&mut self, schema: &Schema, value: i8) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &value.to_string());
}
self.body.write_byte(schema, value)
}
fn write_short(&mut self, schema: &Schema, value: i16) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &value.to_string());
}
self.body.write_short(schema, value)
}
fn write_integer(&mut self, schema: &Schema, value: i32) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &value.to_string());
}
self.body.write_integer(schema, value)
}
fn write_long(&mut self, schema: &Schema, value: i64) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &value.to_string());
}
self.body.write_long(schema, value)
}
fn write_float(&mut self, schema: &Schema, value: f32) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &format_float_f32(value));
}
self.body.write_float(schema, value)
}
fn write_double(&mut self, schema: &Schema, value: f64) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, &format_float_f64(value));
}
self.body.write_double(schema, value)
}
fn write_big_integer(
&mut self,
schema: &Schema,
value: &aws_smithy_types::BigInteger,
) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, value.as_ref());
}
self.body.write_big_integer(schema, value)
}
fn write_big_decimal(
&mut self,
schema: &Schema,
value: &aws_smithy_types::BigDecimal,
) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
return self.add_binding(binding, schema, value.as_ref());
}
self.body.write_big_decimal(schema, value)
}
fn write_string(&mut self, schema: &Schema, value: &str) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
if schema.media_type().is_some() {
let encoded = aws_smithy_types::base64::encode(value.as_bytes());
return self.add_binding(binding, schema, &encoded);
}
return self.add_binding(binding, schema, value);
}
if schema.http_payload().is_some() {
if !self.should_route_binding(schema) {
return Ok(());
}
self.raw_payload =
Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value.as_bytes()) });
return Ok(());
}
self.body.write_string(schema, value)
}
fn write_blob(&mut self, schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if schema.http_header().is_some() {
if !self.should_route_binding(schema) {
return Ok(());
}
let encoded = aws_smithy_types::base64::encode(value);
self.headers
.insert(schema.http_header().unwrap().value(), encoded);
return Ok(());
}
if schema.http_payload().is_some() {
if !self.should_route_binding(schema) {
return Ok(());
}
self.raw_payload = Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value) });
return Ok(());
}
self.body.write_blob(schema, value)
}
fn write_timestamp(
&mut self,
schema: &Schema,
value: &aws_smithy_types::DateTime,
) -> Result<(), SerdeError> {
let schema = self.resolve_member(schema);
if let Some(binding) = http_string_binding(schema) {
let format = if let Some(ts_trait) = schema.timestamp_format() {
match ts_trait.format() {
crate::traits::TimestampFormat::EpochSeconds => {
aws_smithy_types::date_time::Format::EpochSeconds
}
crate::traits::TimestampFormat::HttpDate => {
aws_smithy_types::date_time::Format::HttpDate
}
crate::traits::TimestampFormat::DateTime => {
aws_smithy_types::date_time::Format::DateTime
}
}
} else {
match binding {
HttpBinding::Header(_) => aws_smithy_types::date_time::Format::HttpDate,
_ => aws_smithy_types::date_time::Format::DateTime,
}
};
let formatted = value
.fmt(format)
.map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?;
return self.add_binding(binding, schema, &formatted);
}
self.body.write_timestamp(schema, value)
}
fn write_document(
&mut self,
schema: &Schema,
value: &aws_smithy_types::Document,
) -> Result<(), SerdeError> {
self.body.write_document(schema, value)
}
fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError> {
self.body.write_null(schema)
}
}
enum HttpBinding {
Header(&'static str),
Query(&'static str),
Label,
}
fn http_string_binding(schema: &Schema) -> Option<HttpBinding> {
if let Some(h) = schema.http_header() {
return Some(HttpBinding::Header(h.value()));
}
if let Some(q) = schema.http_query() {
return Some(HttpBinding::Query(q.value()));
}
if schema.http_label().is_some() {
return Some(HttpBinding::Label);
}
None
}
impl<'a, S> HttpBindingSerializer<'a, S> {
fn add_binding(
&mut self,
binding: HttpBinding,
schema: &Schema,
value: &str,
) -> Result<(), SerdeError> {
if !self.should_route_binding(schema) {
return Ok(());
}
match binding {
HttpBinding::Header(name) => {
self.headers.insert(name, value.to_string());
}
HttpBinding::Query(name) => {
self.query_params
.push((Cow::Borrowed(name), value.to_string()));
}
HttpBinding::Label => {
let name = schema
.member_name()
.ok_or_else(|| SerdeError::custom("httpLabel on non-member schema"))?;
self.labels.push((Cow::Borrowed(name), value.to_string()));
}
}
Ok(())
}
}
#[derive(Copy, Clone)]
enum HttpListTarget {
Header,
Query,
}
struct ListElementCollector {
values: Vec<String>,
quotable: Vec<bool>,
target: HttpListTarget,
}
impl ListElementCollector {
fn for_header() -> Self {
Self::new(HttpListTarget::Header)
}
fn for_query() -> Self {
Self::new(HttpListTarget::Query)
}
fn new(target: HttpListTarget) -> Self {
Self {
values: Vec::new(),
quotable: Vec::new(),
target,
}
}
fn push(&mut self, value: String) {
self.quotable.push(true);
self.values.push(value);
}
fn push_unquotable(&mut self, value: String) {
self.quotable.push(false);
self.values.push(value);
}
}
impl ShapeSerializer for ListElementCollector {
fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_boolean(&mut self, _: &Schema, value: bool) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_byte(&mut self, _: &Schema, value: i8) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_short(&mut self, _: &Schema, value: i16) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_integer(&mut self, _: &Schema, value: i32) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_long(&mut self, _: &Schema, value: i64) -> Result<(), SerdeError> {
self.push(value.to_string());
Ok(())
}
fn write_float(&mut self, _: &Schema, value: f32) -> Result<(), SerdeError> {
self.push(format_float_f32(value));
Ok(())
}
fn write_double(&mut self, _: &Schema, value: f64) -> Result<(), SerdeError> {
self.push(format_float_f64(value));
Ok(())
}
fn write_timestamp(
&mut self,
schema: &Schema,
value: &aws_smithy_types::DateTime,
) -> Result<(), SerdeError> {
let format = match schema.timestamp_format() {
Some(ts) => match ts.format() {
crate::traits::TimestampFormat::EpochSeconds => {
aws_smithy_types::date_time::Format::EpochSeconds
}
crate::traits::TimestampFormat::HttpDate => {
aws_smithy_types::date_time::Format::HttpDate
}
crate::traits::TimestampFormat::DateTime => {
aws_smithy_types::date_time::Format::DateTime
}
},
None => match self.target {
HttpListTarget::Header => aws_smithy_types::date_time::Format::HttpDate,
HttpListTarget::Query => aws_smithy_types::date_time::Format::DateTime,
},
};
self.push_unquotable(
value
.fmt(format)
.map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?,
);
Ok(())
}
fn write_blob(&mut self, _schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
self.push(aws_smithy_types::base64::encode(value));
Ok(())
}
fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
Ok(())
}
fn write_list(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_map(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_integer(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigInteger,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_decimal(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigDecimal,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_document(
&mut self,
_: &Schema,
_: &aws_smithy_types::Document,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
Ok(())
}
}
fn format_float_f32(value: f32) -> String {
if value.is_infinite() {
if value.is_sign_positive() {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else if value.is_nan() {
"NaN".to_string()
} else {
value.to_string()
}
}
fn format_float_f64(value: f64) -> String {
if value.is_infinite() {
if value.is_sign_positive() {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else if value.is_nan() {
"NaN".to_string()
} else {
value.to_string()
}
}
struct MapEntryCollector {
prefix: String,
entries: Vec<(String, String)>,
pending_key: Option<String>,
}
impl MapEntryCollector {
fn new(prefix: String) -> Self {
Self {
prefix,
entries: Vec::new(),
pending_key: None,
}
}
}
impl ShapeSerializer for MapEntryCollector {
fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
if let Some(key) = self.pending_key.take() {
self.entries
.push((format!("{}{}", self.prefix, key), value.to_string()));
} else {
self.pending_key = Some(value.to_string());
}
Ok(())
}
fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
Ok(())
}
fn write_list(
&mut self,
_: &Schema,
write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
if let Some(key) = self.pending_key.take() {
let mut collector = ListElementCollector::for_query(); write_elements(&mut collector)?;
for val in collector.values {
self.entries.push((format!("{}{}", self.prefix, key), val));
}
}
Ok(())
}
fn write_map(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
Ok(())
}
fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
Ok(())
}
fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
Ok(())
}
fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
Ok(())
}
fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
Ok(())
}
fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
Ok(())
}
fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_integer(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigInteger,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_decimal(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigDecimal,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
Ok(())
}
fn write_timestamp(
&mut self,
_: &Schema,
_: &aws_smithy_types::DateTime,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_document(
&mut self,
_: &Schema,
_: &aws_smithy_types::Document,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
Ok(())
}
}
impl<C> ClientProtocolInner for HttpBindingProtocol<C>
where
C: Codec + Send + Sync + std::fmt::Debug + 'static,
for<'a> C::Deserializer<'a>: ShapeDeserializer,
{
type Request = Request;
type Response = Response;
fn protocol_id(&self) -> &ShapeId {
&self.protocol_id
}
fn serialize_request(
&self,
input: &dyn SerializableStruct,
input_schema: &Schema,
endpoint: &str,
cfg: &ConfigBag,
) -> Result<Request, SerdeError> {
let body = self.codec.create_serializer();
self.serialize_request_with_body(body, input, input_schema, endpoint, cfg)
}
fn deserialize_response<'a>(
&self,
response: &'a Response,
_output_schema: &Schema,
_cfg: &ConfigBag,
) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
let body = response.body().bytes().unwrap_or(&[]);
Ok(Box::new(self.codec.create_deserializer(body)))
}
fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
Some(&self.codec)
}
fn update_endpoint(
&self,
request: &mut Request,
endpoint: &aws_smithy_types::endpoint::Endpoint,
cfg: &ConfigBag,
) -> Result<(), SerdeError> {
apply_http_endpoint(request, endpoint, cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serde::SerializableStruct;
use crate::{prelude::*, ShapeType};
#[test]
fn visited_members_inline_dedup() {
let mut v = VisitedMembers::new();
assert!(v.insert(3));
assert!(!v.insert(3));
assert!(v.insert(7));
assert!(v.insert(0));
assert!(!v.insert(3));
assert!(!v.insert(7));
assert!(!v.insert(0));
}
#[test]
fn visited_members_spills_to_overflow() {
let mut v = VisitedMembers::new();
let n = VisitedMembers::INLINE_CAPACITY;
for i in 0..n {
assert!(v.insert(i), "fresh inline insert at {i} must return true");
}
for i in 0..n {
assert!(
!v.insert(i),
"duplicate inline insert at {i} must return false"
);
}
assert!(v.insert(n));
assert!(v.insert(n + 5));
assert!(!v.insert(n));
assert!(!v.insert(n + 5));
assert!(!v.insert(0));
}
struct TestSerializer {
output: Vec<u8>,
}
impl FinishSerializer for TestSerializer {
fn finish(self) -> Vec<u8> {
self.output
}
}
impl ShapeSerializer for TestSerializer {
fn write_struct(
&mut self,
_: &Schema,
value: &dyn SerializableStruct,
) -> Result<(), SerdeError> {
self.output.push(b'{');
value.serialize_members(self)?;
self.output.push(b'}');
Ok(())
}
fn write_list(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_map(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
Ok(())
}
fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
Ok(())
}
fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
Ok(())
}
fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
Ok(())
}
fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
Ok(())
}
fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
Ok(())
}
fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_integer(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigInteger,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_big_decimal(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigDecimal,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
self.output.extend_from_slice(v.as_bytes());
Ok(())
}
fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
Ok(())
}
fn write_timestamp(
&mut self,
_: &Schema,
_: &aws_smithy_types::DateTime,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_document(
&mut self,
_: &Schema,
_: &aws_smithy_types::Document,
) -> Result<(), SerdeError> {
Ok(())
}
fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
Ok(())
}
}
struct TestDeserializer<'a> {
input: &'a [u8],
}
impl ShapeDeserializer for TestDeserializer<'_> {
fn read_struct(
&mut self,
_: &Schema,
_: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn read_list(
&mut self,
_: &Schema,
_: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn read_map(
&mut self,
_: &Schema,
_: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
Ok(())
}
fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
Ok(false)
}
fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
Ok(0)
}
fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
Ok(0)
}
fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
Ok(0)
}
fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
Ok(0)
}
fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
Ok(0.0)
}
fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
Ok(0.0)
}
fn read_big_integer(
&mut self,
_: &Schema,
) -> Result<aws_smithy_types::BigInteger, SerdeError> {
use std::str::FromStr;
Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
}
fn read_big_decimal(
&mut self,
_: &Schema,
) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
use std::str::FromStr;
Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
}
fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
Ok(String::from_utf8_lossy(self.input).into_owned())
}
fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
Ok(aws_smithy_types::Blob::new(vec![]))
}
fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
Ok(aws_smithy_types::DateTime::from_secs(0))
}
fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
Ok(aws_smithy_types::Document::Null)
}
fn is_null(&self) -> bool {
false
}
fn container_size(&self) -> Option<usize> {
None
}
}
#[derive(Debug)]
struct TestCodec;
impl Codec for TestCodec {
type Serializer = TestSerializer;
type Deserializer<'a> = TestDeserializer<'a>;
fn create_serializer(&self) -> Self::Serializer {
TestSerializer { output: Vec::new() }
}
fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
TestDeserializer { input }
}
}
static TEST_SCHEMA: Schema =
Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
struct EmptyStruct;
impl SerializableStruct for EmptyStruct {
fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
Ok(())
}
}
static NAME_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "TestStruct"),
ShapeType::String,
"name",
0,
);
static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
crate::shape_id!("test", "TestStruct"),
ShapeType::Structure,
MEMBERS,
);
struct NameStruct;
impl SerializableStruct for NameStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&NAME_MEMBER, "Alice")
}
}
fn make_protocol() -> HttpBindingProtocol<TestCodec> {
HttpBindingProtocol::new(
crate::shape_id!("test", "proto"),
TestCodec,
"application/test",
)
}
#[test]
fn serialize_sets_content_type() {
let request = make_protocol()
.serialize_request(
&EmptyStruct,
&STRUCT_WITH_MEMBER,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(
request.headers().get("Content-Type").unwrap(),
"application/test"
);
}
#[test]
fn serialize_no_body_members_omits_content_type() {
let request = make_protocol()
.serialize_request(
&EmptyStruct,
&TEST_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert!(request.headers().get("Content-Type").is_none());
}
#[test]
fn presigning_omit_settings_suppress_default_content_headers() {
use crate::header_omit_settings::{HeaderOmitSettings, SharedHeaderOmitSettings};
use aws_smithy_types::config_bag::Layer;
#[derive(Debug)]
struct OmitBoth;
impl HeaderOmitSettings for OmitBoth {
fn should_omit_default_content_type(&self) -> bool {
true
}
fn should_omit_default_content_length(&self) -> bool {
true
}
}
let mut layer = Layer::new("test_omit");
layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
let cfg = ConfigBag::of_layers(vec![layer]);
let request = make_protocol()
.serialize_request(
&NameStruct,
&STRUCT_WITH_MEMBER,
"https://example.com",
&cfg,
)
.unwrap();
assert!(
request.headers().get("Content-Type").is_none(),
"presigning omit suppresses default Content-Type"
);
assert!(
request.headers().get("Content-Length").is_none(),
"presigning omit suppresses default Content-Length"
);
}
#[test]
fn default_content_headers_inserted_when_omit_settings_absent() {
let request = make_protocol()
.serialize_request(
&NameStruct,
&STRUCT_WITH_MEMBER,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(
request
.headers()
.get("Content-Type")
.expect("Content-Type set"),
"application/test"
);
assert_eq!(
request
.headers()
.get("Content-Length")
.expect("Content-Length set"),
"7"
);
}
#[test]
fn serialize_skips_body_codec_when_no_body_members() {
use std::sync::atomic::{AtomicUsize, Ordering};
static WRITE_CALLS: AtomicUsize = AtomicUsize::new(0);
struct PanicSerializer;
impl FinishSerializer for PanicSerializer {
fn finish(self) -> Vec<u8> {
panic!("body codec finish() called — short-circuit failed");
}
}
impl ShapeSerializer for PanicSerializer {
fn write_struct(
&mut self,
_: &Schema,
_: &dyn SerializableStruct,
) -> Result<(), SerdeError> {
WRITE_CALLS.fetch_add(1, Ordering::SeqCst);
panic!("body codec write_struct() called — short-circuit failed");
}
fn write_list(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
panic!("body codec write_list() called");
}
fn write_map(
&mut self,
_: &Schema,
_: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
) -> Result<(), SerdeError> {
panic!("body codec write_map() called");
}
fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
panic!("body codec write_boolean() called");
}
fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
panic!("body codec write_byte() called");
}
fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
panic!("body codec write_short() called");
}
fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
panic!("body codec write_integer() called");
}
fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
panic!("body codec write_long() called");
}
fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
panic!("body codec write_float() called");
}
fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
panic!("body codec write_double() called");
}
fn write_big_integer(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigInteger,
) -> Result<(), SerdeError> {
panic!("body codec write_big_integer() called");
}
fn write_big_decimal(
&mut self,
_: &Schema,
_: &aws_smithy_types::BigDecimal,
) -> Result<(), SerdeError> {
panic!("body codec write_big_decimal() called");
}
fn write_string(&mut self, _: &Schema, _: &str) -> Result<(), SerdeError> {
panic!("body codec write_string() called");
}
fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
panic!("body codec write_blob() called");
}
fn write_timestamp(
&mut self,
_: &Schema,
_: &aws_smithy_types::DateTime,
) -> Result<(), SerdeError> {
panic!("body codec write_timestamp() called");
}
fn write_document(
&mut self,
_: &Schema,
_: &aws_smithy_types::Document,
) -> Result<(), SerdeError> {
panic!("body codec write_document() called");
}
fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
panic!("body codec write_null() called");
}
}
#[derive(Debug)]
struct PanicCodec;
impl Codec for PanicCodec {
type Serializer = PanicSerializer;
type Deserializer<'a> = TestDeserializer<'a>;
fn create_serializer(&self) -> Self::Serializer {
PanicSerializer
}
fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
TestDeserializer { input }
}
}
static HEADER_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "HeaderOnlyStruct"),
ShapeType::String,
"x_header",
0,
)
.with_http_header("X-Header");
static HEADER_MEMBERS: &[&Schema] = &[&HEADER_MEMBER];
static HEADER_ONLY_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "HeaderOnlyStruct"),
ShapeType::Structure,
HEADER_MEMBERS,
)
.with_no_body_members();
struct HeaderOnlyStruct;
impl SerializableStruct for HeaderOnlyStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&HEADER_MEMBER, "hello")
}
}
let protocol = HttpBindingProtocol::new(
crate::shape_id!("test", "testProtocol"),
PanicCodec,
"application/test",
);
let request = protocol
.serialize_request(
&HeaderOnlyStruct,
&HEADER_ONLY_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.body().bytes().unwrap_or(&[]), b"");
assert!(request.headers().get("Content-Type").is_none());
assert_eq!(request.headers().get("X-Header").unwrap(), "hello");
assert_eq!(WRITE_CALLS.load(Ordering::SeqCst), 0);
}
#[test]
fn serialize_invokes_body_codec_when_has_body_members() {
let request = make_protocol()
.serialize_request(
&NameStruct,
&STRUCT_WITH_MEMBER,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
assert_eq!(
request.headers().get("Content-Type").unwrap(),
"application/test"
);
}
#[test]
fn serialize_sets_uri() {
let request = make_protocol()
.serialize_request(
&EmptyStruct,
&TEST_SCHEMA,
"https://example.com/path",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com/path");
}
#[test]
fn serialize_body() {
let request = make_protocol()
.serialize_request(
&NameStruct,
&STRUCT_WITH_MEMBER,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
}
#[test]
fn deserialize_response() {
let response = Response::new(
200u16.try_into().unwrap(),
SdkBody::from(r#"{"name":"Bob"}"#),
);
let mut deser = make_protocol()
.deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
.unwrap();
assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"name":"Bob"}"#);
}
#[test]
fn update_endpoint() {
let mut request = make_protocol()
.serialize_request(
&EmptyStruct,
&TEST_SCHEMA,
"https://old.example.com",
&ConfigBag::base(),
)
.unwrap();
let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
.url("https://new.example.com")
.build();
make_protocol()
.update_endpoint(&mut request, &endpoint, &ConfigBag::base())
.unwrap();
assert_eq!(request.uri(), "https://new.example.com/");
}
#[test]
fn protocol_id() {
let protocol = HttpBindingProtocol::new(
crate::shape_id!("aws.protocols", "restJson1"),
TestCodec,
"application/json",
);
assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#restJson1");
}
#[test]
fn invalid_uri_returns_error() {
assert!(make_protocol()
.serialize_request(
&EmptyStruct,
&TEST_SCHEMA,
"not a valid uri\n\n",
&ConfigBag::base()
)
.is_err());
}
static HEADER_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::String,
"xToken",
0,
)
.with_http_header("X-Token");
static HEADER_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&HEADER_MEMBER],
);
struct HeaderStruct;
impl SerializableStruct for HeaderStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&HEADER_MEMBER, "my-token-value")
}
}
#[test]
fn http_header_string() {
let request = make_protocol()
.serialize_request(
&HeaderStruct,
&HEADER_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.headers().get("X-Token").unwrap(), "my-token-value");
}
static INT_HEADER_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::Integer,
"retryCount",
0,
)
.with_http_header("X-Retry-Count");
static INT_HEADER_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&INT_HEADER_MEMBER],
);
struct IntHeaderStruct;
impl SerializableStruct for IntHeaderStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_integer(&INT_HEADER_MEMBER, 3)
}
}
#[test]
fn http_header_integer() {
let request = make_protocol()
.serialize_request(
&IntHeaderStruct,
&INT_HEADER_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.headers().get("X-Retry-Count").unwrap(), "3");
}
static BOOL_HEADER_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::Boolean,
"verbose",
0,
)
.with_http_header("X-Verbose");
static BOOL_HEADER_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&BOOL_HEADER_MEMBER],
);
struct BoolHeaderStruct;
impl SerializableStruct for BoolHeaderStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_boolean(&BOOL_HEADER_MEMBER, true)
}
}
#[test]
fn http_header_boolean() {
let request = make_protocol()
.serialize_request(
&BoolHeaderStruct,
&BOOL_HEADER_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.headers().get("X-Verbose").unwrap(), "true");
}
static QUERY_MEMBER: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "color", 0)
.with_http_query("color");
static QUERY_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&QUERY_MEMBER],
);
struct QueryStruct;
impl SerializableStruct for QueryStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&QUERY_MEMBER, "blue")
}
}
#[test]
fn http_query_string() {
let request = make_protocol()
.serialize_request(
&QueryStruct,
&QUERY_SCHEMA,
"https://example.com/things",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com/things?color=blue");
}
static INT_QUERY_MEMBER: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Integer, "size", 0)
.with_http_query("size");
static INT_QUERY_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&INT_QUERY_MEMBER],
);
struct IntQueryStruct;
impl SerializableStruct for IntQueryStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_integer(&INT_QUERY_MEMBER, 42)
}
}
#[test]
fn http_query_integer() {
let request = make_protocol()
.serialize_request(
&IntQueryStruct,
&INT_QUERY_SCHEMA,
"https://example.com/things",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com/things?size=42");
}
static Q1: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "a", 0)
.with_http_query("a");
static Q2: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "b", 1)
.with_http_query("b");
static MULTI_QUERY_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&Q1, &Q2],
);
struct MultiQueryStruct;
impl SerializableStruct for MultiQueryStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&Q1, "x")?;
s.write_string(&Q2, "y")
}
}
#[test]
fn http_query_multiple_params() {
let request = make_protocol()
.serialize_request(
&MultiQueryStruct,
&MULTI_QUERY_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com?a=x&b=y");
}
#[test]
fn http_query_percent_encodes_values() {
struct SpaceQueryStruct;
impl SerializableStruct for SpaceQueryStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&QUERY_MEMBER, "hello world")
}
}
let request = make_protocol()
.serialize_request(
&SpaceQueryStruct,
&QUERY_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com?color=hello%20world");
}
static LABEL_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::String,
"bucketName",
0,
)
.with_http_label();
static LABEL_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&LABEL_MEMBER],
);
struct LabelStruct;
impl SerializableStruct for LabelStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&LABEL_MEMBER, "my-bucket")
}
}
#[test]
fn http_label_substitution() {
let request = make_protocol()
.serialize_request(
&LabelStruct,
&LABEL_SCHEMA,
"https://example.com/{bucketName}/objects",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com/my-bucket/objects");
}
#[test]
fn http_label_percent_encodes() {
struct SpecialLabelStruct;
impl SerializableStruct for SpecialLabelStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&LABEL_MEMBER, "my bucket/name")
}
}
let request = make_protocol()
.serialize_request(
&SpecialLabelStruct,
&LABEL_SCHEMA,
"https://example.com/{bucketName}",
&ConfigBag::base(),
)
.unwrap();
assert!(request.uri().contains("my%20bucket%2Fname"));
}
static INT_LABEL_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::Integer,
"itemId",
0,
)
.with_http_label();
static INT_LABEL_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&INT_LABEL_MEMBER],
);
struct IntLabelStruct;
impl SerializableStruct for IntLabelStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_integer(&INT_LABEL_MEMBER, 123)
}
}
#[test]
fn http_label_integer() {
let request = make_protocol()
.serialize_request(
&IntLabelStruct,
&INT_LABEL_SCHEMA,
"https://example.com/items/{itemId}",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com/items/123");
}
static COMBINED_LABEL: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "id", 0)
.with_http_label();
static COMBINED_HEADER: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "token", 1)
.with_http_header("X-Token");
static COMBINED_QUERY: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::String,
"filter",
2,
)
.with_http_query("filter");
static COMBINED_BODY: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "data", 3);
static COMBINED_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[
&COMBINED_LABEL,
&COMBINED_HEADER,
&COMBINED_QUERY,
&COMBINED_BODY,
],
);
struct CombinedStruct;
impl SerializableStruct for CombinedStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&COMBINED_LABEL, "item-42")?;
s.write_string(&COMBINED_HEADER, "secret")?;
s.write_string(&COMBINED_QUERY, "active")?;
s.write_string(&COMBINED_BODY, "payload-data")
}
}
#[test]
fn combined_bindings() {
let request = make_protocol()
.serialize_request(
&CombinedStruct,
&COMBINED_SCHEMA,
"https://example.com/{id}/details",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(
request.uri(),
"https://example.com/item-42/details?filter=active"
);
assert_eq!(request.headers().get("X-Token").unwrap(), "secret");
let body = request.body().bytes().unwrap();
assert!(body
.windows(b"payload-data".len())
.any(|w| w == b"payload-data"));
}
static PREFIX_MEMBER: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "metadata", 0)
.with_http_prefix_headers("X-Meta-");
static PREFIX_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&PREFIX_MEMBER],
);
struct PrefixHeaderStruct;
impl SerializableStruct for PrefixHeaderStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_map(&PREFIX_MEMBER, &|s| {
s.write_string(&STRING, "Color")?;
s.write_string(&STRING, "red")?;
s.write_string(&STRING, "Size")?;
s.write_string(&STRING, "large")?;
Ok(())
})
}
}
#[test]
fn http_prefix_headers() {
let request = make_protocol()
.serialize_request(
&PrefixHeaderStruct,
&PREFIX_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.headers().get("X-Meta-Color").unwrap(), "red");
assert_eq!(request.headers().get("X-Meta-Size").unwrap(), "large");
}
static QUERY_PARAMS_MEMBER: Schema =
Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "params", 0)
.with_http_query_params();
static QUERY_PARAMS_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&QUERY_PARAMS_MEMBER],
);
struct QueryParamsStruct;
impl SerializableStruct for QueryParamsStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_map(&QUERY_PARAMS_MEMBER, &|s| {
s.write_string(&STRING, "page")?;
s.write_string(&STRING, "2")?;
s.write_string(&STRING, "limit")?;
s.write_string(&STRING, "50")?;
Ok(())
})
}
}
#[test]
fn http_query_params() {
let request = make_protocol()
.serialize_request(
&QueryParamsStruct,
&QUERY_PARAMS_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(request.uri(), "https://example.com?page=2&limit=50");
}
static TS_HEADER_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::Timestamp,
"ifModified",
0,
)
.with_http_header("If-Modified-Since");
static TS_HEADER_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&TS_HEADER_MEMBER],
);
struct TimestampHeaderStruct;
impl SerializableStruct for TimestampHeaderStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_timestamp(&TS_HEADER_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
}
}
#[test]
fn timestamp_header_uses_http_date() {
let request = make_protocol()
.serialize_request(
&TimestampHeaderStruct,
&TS_HEADER_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
let value = request.headers().get("If-Modified-Since").unwrap();
assert!(value.contains("1970"), "expected http-date, got: {value}");
}
static TS_QUERY_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::Timestamp,
"since",
0,
)
.with_http_query("since");
static TS_QUERY_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&TS_QUERY_MEMBER],
);
struct TimestampQueryStruct;
impl SerializableStruct for TimestampQueryStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_timestamp(&TS_QUERY_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
}
}
#[test]
fn timestamp_query_uses_date_time() {
let request = make_protocol()
.serialize_request(
&TimestampQueryStruct,
&TS_QUERY_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
assert_eq!(
request.uri(),
"https://example.com?since=1970-01-01T00%3A00%3A00Z"
);
}
static BOUND_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::String,
"headerVal",
0,
)
.with_http_header("X-Val");
static UNBOUND_MEMBER: Schema = Schema::new_member(
crate::shape_id!("test", "S"),
ShapeType::String,
"bodyVal",
1,
);
static MIXED_SCHEMA: Schema = Schema::new_struct(
crate::shape_id!("test", "S"),
ShapeType::Structure,
&[&BOUND_MEMBER, &UNBOUND_MEMBER],
);
struct MixedStruct;
impl SerializableStruct for MixedStruct {
fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
s.write_string(&BOUND_MEMBER, "in-header")?;
s.write_string(&UNBOUND_MEMBER, "in-body")
}
}
#[test]
fn bound_members_not_in_body() {
let request = make_protocol()
.serialize_request(
&MixedStruct,
&MIXED_SCHEMA,
"https://example.com",
&ConfigBag::base(),
)
.unwrap();
let body = std::str::from_utf8(request.body().bytes().unwrap()).unwrap();
assert!(
body.contains("in-body"),
"body should contain unbound member"
);
assert!(
!body.contains("in-header"),
"body should NOT contain header-bound member"
);
assert_eq!(request.headers().get("X-Val").unwrap(), "in-header");
}
}