Skip to main content

aws_smithy_schema/schema/http_protocol/
binding.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! HTTP binding protocol for REST-style APIs.
7
8use crate::codec::{Codec, FinishSerializer};
9use crate::protocol::{apply_http_endpoint, ClientProtocolInner};
10use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
11use crate::{Schema, ShapeId};
12use aws_smithy_runtime_api::http::{Headers, Request, Response};
13use aws_smithy_types::body::SdkBody;
14use aws_smithy_types::config_bag::ConfigBag;
15use std::borrow::Cow;
16
17/// An HTTP protocol for REST-style APIs that use HTTP bindings.
18///
19/// This protocol splits input members between HTTP locations (headers, query
20/// strings, URI labels) and the payload based on HTTP binding traits
21/// (`@httpHeader`, `@httpQuery`, `@httpLabel`, `@httpPayload`, etc.).
22/// Non-bound members are serialized into the body using the provided codec.
23///
24/// # Type parameters
25///
26/// * `C` — the payload codec (e.g., `JsonCodec`, `XmlCodec`)
27#[derive(Debug)]
28pub struct HttpBindingProtocol<C> {
29    protocol_id: ShapeId,
30    codec: C,
31    content_type: &'static str,
32}
33
34impl<C: Codec> HttpBindingProtocol<C> {
35    /// Creates a new HTTP binding protocol.
36    pub fn new(protocol_id: ShapeId, codec: C, content_type: &'static str) -> Self {
37        Self {
38            protocol_id,
39            codec,
40            content_type,
41        }
42    }
43
44    /// Returns a reference to the body codec. Used by wrapper protocols
45    /// (e.g. `AwsRestXmlProtocol`) that need to construct and pre-configure
46    /// a body serializer before delegating to
47    /// [`serialize_request_with_body`](Self::serialize_request_with_body).
48    pub fn codec(&self) -> &C {
49        &self.codec
50    }
51
52    /// Body-providable variant of [`serialize_request`](Self::serialize_request).
53    /// The caller supplies an already-constructed body serializer, allowing
54    /// codec-specific pre-configuration (e.g. setting one-shot state on the
55    /// body codec before binding-driven serialization begins). The default
56    /// `serialize_request` implementation calls this with a fresh serializer
57    /// from the codec.
58    ///
59    /// This is the extension point used by `AwsRestXmlProtocol` to inject the
60    /// member-level `@xmlName` for an `@httpPayload` struct member into the
61    /// body codec — a value the codec couldn't otherwise see because codegen
62    /// passes the *target* shape's `SCHEMA` for that member, which carries
63    /// the target's `@xmlName` but not the member's.
64    pub fn serialize_request_with_body(
65        &self,
66        body: <C as Codec>::Serializer,
67        input: &dyn SerializableStruct,
68        input_schema: &Schema,
69        endpoint: &str,
70        cfg: &ConfigBag,
71    ) -> Result<Request, SerdeError> {
72        // Construct the request up front with an empty body. The binder is
73        // given a `&mut Headers` reference into this request and inserts
74        // headers directly as it walks members — avoiding the cost of an
75        // intermediate `Vec<(...)>` plus a late flush loop. The body and URI
76        // are populated after the binder's borrow is released.
77        let mut request = Request::new(SdkBody::empty());
78
79        // Check if there's an @httpPayload member targeting a structure/union.
80        // In that case, the payload member's own write_struct provides the body
81        // framing, so we must not add top-level struct framing.
82        let has_struct_payload = input_schema.members().iter().any(|m| {
83            m.http_payload().is_some()
84                && matches!(
85                    m.shape_type(),
86                    crate::ShapeType::Structure | crate::ShapeType::Union
87                )
88        });
89        // If the schema declares zero body members (every member is HTTP-bound,
90        // and any `@httpPayload` is on a scalar that bypasses the codec),
91        // we can skip body-codec invocation entirely. The wasted work would be:
92        //   - XmlSerializer/JsonSerializer::write_struct opens a wrapper element
93        //   - Proxy::serialize_members re-enters the binder
94        //   - close-element is emitted
95        //   - bytes are collected by `body.finish()` and then discarded
96        //     (since `has_body_members == false` later forces `body = Vec::new()`)
97        // Skipping all of that just calls `serialize_members` directly through
98        // the binder so HTTP-bound members are still routed to headers / query /
99        // labels. `is_top_level` is cleared first so any nested struct that
100        // happens to still pass through (none do in this branch by definition,
101        // but defensive) takes the body-delegation path.
102        //
103        // Codegen sets `with_no_body_members()` on operation input shapes whose
104        // members are all HTTP-bound (e.g., S3 PutObjectInput, CopyObjectInput).
105        // Hand-constructed schemas default to `has_body_members == true` so this
106        // optimization is never silently applied to a schema that actually has
107        // body members.
108        let skip_body_codec = !input_schema.has_body_members() && !has_struct_payload;
109
110        // Run the binder in a scope so its `&mut Headers` borrow on `request`
111        // is released before we mutate the request again (set_uri / body swap
112        // / Content-Type / Content-Length).
113        let (raw_payload, body_bytes, query_params, labels) = {
114            let mut binder =
115                HttpBindingSerializer::new(body, Some(input_schema), request.headers_mut());
116
117            if skip_body_codec || has_struct_payload {
118                // skip_body_codec: input has no body members at all → all members
119                //                  route to HTTP bindings, body bytes are unused.
120                // has_struct_payload: an @httpPayload struct member writes itself
121                //                     to the body without wrapping — call
122                //                     serialize_members directly so framing comes
123                //                     from the payload struct, not from the binder.
124                binder.is_top_level = false;
125                input.serialize_members(&mut binder)?;
126            } else {
127                binder.write_struct(input_schema, input)?;
128            }
129            let raw_payload = binder.raw_payload;
130            let body_bytes = if raw_payload.is_some() || skip_body_codec {
131                // @httpPayload blob/string — don't use the codec output.
132                // skip_body_codec — body codec was never written to.
133                Vec::new()
134            } else {
135                binder.body.finish()
136            };
137            (raw_payload, body_bytes, binder.query_params, binder.labels)
138        };
139
140        // Per the REST-JSON content-type handling spec:
141        // - If @httpPayload targets a blob/string: send raw bytes, no Content-Type when empty
142        // - If body members exist (even if all optional and unset): send `{}` with Content-Type
143        // - If no body members at all (everything is in headers/query/labels): empty body, no Content-Type
144        let has_blob_or_string_payload = raw_payload.is_some();
145        // Mirror the schema's compile-time signal at runtime. When the schema
146        // says no body members AND there's no struct-payload override, this
147        // is straightforwardly false.
148        let has_body_members = has_struct_payload
149            || (input_schema.has_body_members()
150                && input_schema.members().iter().any(|m| {
151                    m.http_header().is_none()
152                        && m.http_query().is_none()
153                        && m.http_label().is_none()
154                        && m.http_prefix_headers().is_none()
155                        && m.http_query_params().is_none()
156                        && m.http_payload().is_none()
157                }));
158
159        let mut body_bytes = body_bytes;
160        let set_content_type = if has_blob_or_string_payload {
161            // Blob/string payload: Content-Type comes from the @httpHeader("Content-Type")
162            // member if present, or defaults to application/octet-stream for blobs.
163            // Don't set the protocol's codec content type (e.g., application/json).
164            false
165        } else if has_body_members {
166            // Operation has body members — body includes framing (e.g., `{}`).
167            // Per the REST-JSON spec, even if all members are optional and unset, send `{}`.
168            true
169        } else {
170            // No body members at all — empty body, no Content-Type.
171            body_bytes = Vec::new();
172            false
173        };
174
175        // Build URI: write directly into a single, capacity-hinted String
176        // instead of repeatedly `format!`-allocating placeholders and
177        // `replace`-allocating new copies of the path. Profiling on PutObject
178        // SER showed `format::format_inner` + `alloc::str::replace` together
179        // were ~25% of bench loop. The new path is one allocation per request
180        // for the URI string itself; percent-encoding writes through
181        // `percent_encode_into` to avoid per-segment String allocs.
182        let template_opt = input_schema.http().map(|h| h.uri());
183        // Capacity heuristic: endpoint + template + slack for label expansion
184        // (greedy labels typically expand by O(1.5x)). Better-than-default
185        // initial capacity avoids the first 1-2 reallocs.
186        let mut uri =
187            String::with_capacity(endpoint.len() + template_opt.map(|t| t.len()).unwrap_or(1) + 64);
188        match template_opt {
189            Some(template) => {
190                if !endpoint.is_empty() {
191                    uri.push_str(endpoint);
192                }
193                append_uri_with_labels(template, &labels, &mut uri);
194            }
195            None => {
196                if endpoint.is_empty() {
197                    uri.push('/');
198                } else {
199                    // Endpoint may contain `{...}` label placeholders to
200                    // substitute (this branch is for shapes without an
201                    // `@http` trait, where the endpoint *is* the template).
202                    append_uri_with_labels(endpoint, &labels, &mut uri);
203                }
204            }
205        }
206        if !query_params.is_empty() {
207            uri.push(if uri.contains('?') { '&' } else { '?' });
208            let mut first = true;
209            for (k, v) in &query_params {
210                if !first {
211                    uri.push('&');
212                }
213                percent_encode_into(k, &mut uri);
214                uri.push('=');
215                percent_encode_into(v, &mut uri);
216                first = false;
217            }
218        }
219
220        // Swap the body in place. Headers were inserted directly during the
221        // binder phase, so no flush loop is needed here.
222        *request.body_mut() = if let Some(payload) = raw_payload {
223            SdkBody::from(payload)
224        } else {
225            SdkBody::from(body_bytes)
226        };
227        // Set HTTP method from @http trait
228        if let Some(http) = input_schema.http() {
229            request
230                .set_method(http.method())
231                .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
232        }
233        request
234            .set_uri(uri.as_str())
235            .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
236        // Customer-supplied @httpHeader("Content-Type") wins over the
237        // protocol default. (Pre-opt2 the late flush loop overwrote our
238        // default after we set it; with direct insertion the customer header
239        // is already present, so we must not clobber it.)
240        //
241        // A presigning interceptor (or any other caller that stored a
242        // `SharedHeaderOmitSettings` in the config bag) can request the
243        // runtime suppress these defaults so they don't end up in the signed-
244        // header set of a presigned URL.
245        let omit = cfg.load::<crate::header_omit_settings::SharedHeaderOmitSettings>();
246        let omit_content_type = omit
247            .map(|s| s.should_omit_default_content_type())
248            .unwrap_or(false);
249        let omit_content_length = omit
250            .map(|s| s.should_omit_default_content_length())
251            .unwrap_or(false);
252        if !omit_content_type && set_content_type && request.headers().get("Content-Type").is_none()
253        {
254            request
255                .headers_mut()
256                .insert("Content-Type", self.content_type);
257        }
258        if !omit_content_length {
259            if let Some(len) = request.body().content_length() {
260                if (len > 0 || set_content_type)
261                    && request.headers().get("Content-Length").is_none()
262                {
263                    request
264                        .headers_mut()
265                        .insert("Content-Length", len.to_string());
266                }
267            }
268        }
269        Ok(request)
270    }
271}
272
273// Note: there is a percent_encoding crate we use some other places for this, but I'm trying to keep
274// the dependencies to a minimum.
275/// Percent-encode a string per RFC 3986 section 2.3 (unreserved characters only).
276pub fn percent_encode(input: &str) -> String {
277    let mut out = String::with_capacity(input.len());
278    percent_encode_into(input, &mut out);
279    out
280}
281
282/// Percent-encode `input` per RFC 3986 section 2.3 (unreserved characters only),
283/// appending the result to `out`. Bulk-copies runs of already-safe bytes via
284/// `push_str` instead of pushing one byte at a time, which is the common case
285/// for URI labels and query values (typical inputs need no escaping).
286pub fn percent_encode_into(input: &str, out: &mut String) {
287    let bytes = input.as_bytes();
288    let mut start = 0usize;
289    for (i, &b) in bytes.iter().enumerate() {
290        let safe = matches!(
291            b,
292            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
293        );
294        if !safe {
295            // Bulk-copy the run of safe bytes ending just before `i`.
296            // SAFETY: `start..i` is a slice of `input`'s UTF-8 bytes, and
297            // every byte in `start..i` was confirmed `safe` (ASCII), so the
298            // slice is valid UTF-8.
299            if start < i {
300                out.push_str(&input[start..i]);
301            }
302            out.push('%');
303            out.push(char::from(HEX[(b >> 4) as usize]));
304            out.push(char::from(HEX[(b & 0x0f) as usize]));
305            start = i + 1;
306        }
307    }
308    if start < bytes.len() {
309        out.push_str(&input[start..]);
310    }
311}
312
313/// Walk a URI template like `/{Bucket}/{Key+}` and append the substituted
314/// path to `out`. `labels` is a small list (typically <4) so a linear
315/// scan per label-site is fine. Greedy labels (`{Name+}`) preserve `/`
316/// separators and percent-encode each segment independently; regular
317/// labels percent-encode the value as a whole.
318///
319/// Replaces an older implementation that did
320/// `path.replace(&format!("{{{name}}}"), ...)` per label — multiple
321/// String allocations per label and quadratic full-string scans. Top
322/// hot path on PutObject SER (~25% of bench loop pre-fix).
323fn append_uri_with_labels(
324    template: &str,
325    labels: &[(Cow<'static, str>, String)],
326    out: &mut String,
327) {
328    let mut rem = template;
329    while let Some(open) = rem.find('{') {
330        out.push_str(&rem[..open]);
331        let after_open = &rem[open + 1..];
332        let close = match after_open.find('}') {
333            Some(c) => c,
334            None => {
335                // Malformed template (unmatched `{`); preserve verbatim.
336                out.push('{');
337                rem = after_open;
338                continue;
339            }
340        };
341        let label = &after_open[..close];
342        let (name, greedy) = match label.strip_suffix('+') {
343            Some(n) => (n, true),
344            None => (label, false),
345        };
346        // Linear lookup — labels.len() is typically <= 4.
347        let value = labels
348            .iter()
349            .find(|(n, _)| n.as_ref() == name)
350            .map(|(_, v)| v.as_str());
351        if let Some(v) = value {
352            if greedy {
353                // Encode each `/`-separated segment independently to preserve `/`.
354                let mut first = true;
355                for seg in v.split('/') {
356                    if !first {
357                        out.push('/');
358                    }
359                    percent_encode_into(seg, out);
360                    first = false;
361                }
362            } else {
363                percent_encode_into(v, out);
364            }
365        }
366        // else: label not provided — leave it as nothing (matches previous
367        // behavior where `replace` would not match because the input never
368        // contained the placeholder).
369        rem = &after_open[close + 1..];
370    }
371    if !rem.is_empty() {
372        out.push_str(rem);
373    }
374}
375
376pub(crate) const HEX: &[u8; 16] = b"0123456789ABCDEF";
377
378/// A ShapeSerializer that intercepts member writes and routes HTTP-bound
379/// members to headers, query params, or URI labels instead of the body.
380///
381/// Members without HTTP binding traits are forwarded to the inner body
382/// serializer unchanged.
383struct HttpBindingSerializer<'a, S> {
384    body: S,
385    /// Headers are inserted directly into the `Request`'s header map as they
386    /// are encountered, avoiding the cost of a `Vec<(...)>` intermediate plus
387    /// a late flush loop. The borrow ends when the binder is dropped at the
388    /// end of `serialize_request_with_body`'s binder-scope.
389    headers: &'a mut Headers,
390    query_params: Vec<(Cow<'static, str>, String)>,
391    labels: Vec<(Cow<'static, str>, String)>,
392    /// When set, member schemas are resolved from this schema by name to find
393    /// HTTP binding traits. This allows the protocol to override bindings
394    /// (e.g., for presigning where body members become query params).
395    input_schema: Option<&'a Schema>,
396    /// True for the top-level input struct in serialize_request.
397    /// Cleared after the first write_struct so nested structs delegate directly.
398    is_top_level: bool,
399    /// Raw payload bytes for `@httpPayload` blob/string members. When a member
400    /// has `@httpPayload` and targets a blob or string, the raw bytes bypass
401    /// the codec serializer entirely and are used as the HTTP body directly.
402    /// Safety: the referenced bytes are borrowed from the input struct passed to
403    /// `serialize_request`, which outlives this serializer.
404    raw_payload: Option<&'a [u8]>,
405    /// Tracks member indices that have already been routed to HTTP bindings
406    /// (`@httpHeader`, `@httpQuery`, `@httpLabel`, `@httpPrefixHeaders`,
407    /// `@httpQueryParams`). Some body codecs (notably `XmlSerializer`) call
408    /// `serialize_members` more than once on a single struct (a two-pass for
409    /// attribute / element ordering). Without this guard each HTTP-bound
410    /// member would be appended to its target collection on every pass,
411    /// duplicating header / query entries and breaking presigning signatures.
412    ///
413    /// Implementation: see [`VisitedMembers`]. Stack-only for shapes with up
414    /// to `VisitedMembers::INLINE_CAPACITY` HTTP-bound members; the JSON
415    /// path (single-pass codec, never observes a duplicate) therefore pays
416    /// no per-request allocation here.
417    visited_bound_members: VisitedMembers,
418}
419
420/// Compact dedup set for member indices seen during HTTP-binding routing.
421///
422/// Replaces `HashSet<usize>` for two reasons:
423/// 1. `HashSet::new()` is zero-alloc, but the first `.insert()` allocates
424///    the bucket array. Single-pass codecs (e.g. `JsonSerializer`) call
425///    `should_route_binding` once per HTTP-bound member but never observe a
426///    duplicate, so that allocation is pure waste on a hot path.
427/// 2. For typical structs (≤ `INLINE_CAPACITY` HTTP-bound members), inline
428///    storage avoids the heap entirely and a linear scan over `u32`s is
429///    cheaper than a hash + bucket lookup.
430///
431/// Spills to a `Vec` for larger structures (rare — S3 `CopyObject`'s
432/// 22-binding worst case still fits within `INLINE_CAPACITY`).
433#[derive(Debug)]
434struct VisitedMembers {
435    inline: [u32; Self::INLINE_CAPACITY],
436    inline_len: u8,
437    overflow: Vec<u32>,
438}
439
440impl VisitedMembers {
441    /// Sized to cover the widest real Smithy operation input shapes we
442    /// know about (S3 `CopyObject`: 22 HTTP-bound members) without
443    /// spilling to the heap. The dedup logic stays correct beyond this
444    /// limit; only the no-allocation property is lost.
445    const INLINE_CAPACITY: usize = 24;
446
447    const fn new() -> Self {
448        Self {
449            inline: [0; Self::INLINE_CAPACITY],
450            inline_len: 0,
451            overflow: Vec::new(),
452        }
453    }
454
455    /// Record `idx` as visited. Returns `true` if newly inserted, `false`
456    /// if already present (matching `HashSet::insert`'s semantics).
457    fn insert(&mut self, idx: usize) -> bool {
458        // Cap the cast at u32::MAX. Smithy member indices in practice are
459        // tiny (the largest model in the AWS catalog has fewer than 1000
460        // members on any single shape), so the truncation is unreachable
461        // except in pathological hand-constructed schemas — in which case
462        // dedup is a no-op for the truncated indices, which is correct
463        // behavior (no duplicate routing) at the cost of a possible spurious
464        // re-route.
465        let idx = idx.min(u32::MAX as usize) as u32;
466        let len = self.inline_len as usize;
467        if self.inline[..len].contains(&idx) {
468            return false;
469        }
470        if !self.overflow.is_empty() && self.overflow.contains(&idx) {
471            return false;
472        }
473        if len < Self::INLINE_CAPACITY {
474            self.inline[len] = idx;
475            self.inline_len += 1;
476        } else {
477            self.overflow.push(idx);
478        }
479        true
480    }
481}
482
483impl<'a, S> HttpBindingSerializer<'a, S> {
484    fn new(body: S, input_schema: Option<&'a Schema>, headers: &'a mut Headers) -> Self {
485        Self {
486            body,
487            headers,
488            query_params: Vec::new(),
489            labels: Vec::new(),
490            input_schema,
491            is_top_level: true,
492            raw_payload: None,
493            visited_bound_members: VisitedMembers::new(),
494        }
495    }
496
497    /// Returns `true` the first time this member's HTTP binding is observed
498    /// on this serializer, marking it visited. Some body codecs (notably
499    /// `XmlSerializer`) invoke `serialize_members` more than once on the same
500    /// struct (a two-pass for attribute / element ordering). Without this
501    /// guard each HTTP-bound member would be appended to its target
502    /// collection on every pass, duplicating header / query / label entries
503    /// and producing wrong-signed presigned URLs.
504    ///
505    /// HTTP-bound members are always struct members and so always have an
506    /// index. The `unwrap_or(true)` fallback for schemas without an index
507    /// keeps the helper conservative — it routes when it can't dedupe.
508    fn should_route_binding(&mut self, schema: &Schema) -> bool {
509        schema
510            .member_index()
511            .map(|idx| self.visited_bound_members.insert(idx))
512            .unwrap_or(true)
513    }
514
515    /// Resolve the effective member schema: if an input_schema override is set,
516    /// look up the member by name there (to get the correct HTTP bindings).
517    /// Otherwise use the schema as-is.
518    fn resolve_member<'s>(&self, schema: &'s Schema) -> &'s Schema
519    where
520        'a: 's,
521    {
522        if let (Some(input_schema), Some(idx)) = (self.input_schema, schema.member_index()) {
523            input_schema.member_schema_by_index(idx).unwrap_or(schema)
524        } else if let (Some(input_schema), Some(name)) = (self.input_schema, schema.member_name()) {
525            // Fallback to name lookup for schemas without a member index
526            input_schema.member_schema(name).unwrap_or(schema)
527        } else {
528            schema
529        }
530    }
531}
532
533impl<'a, S: ShapeSerializer> ShapeSerializer for HttpBindingSerializer<'a, S> {
534    fn write_struct(
535        &mut self,
536        schema: &Schema,
537        value: &dyn SerializableStruct,
538    ) -> Result<(), SerdeError> {
539        if self.is_top_level {
540            // Top-level input struct: route serialize_members through the binder
541            // so HTTP-bound members are intercepted. The body serializer's
542            // write_struct is used for framing (e.g., { } for JSON), with a
543            // proxy whose serialize_members delegates back to the binder.
544            struct Proxy<'a, 'b, S> {
545                binder: &'a mut HttpBindingSerializer<'b, S>,
546                value: &'a dyn SerializableStruct,
547            }
548            impl<S: ShapeSerializer> SerializableStruct for Proxy<'_, '_, S> {
549                fn serialize_members(
550                    &self,
551                    _serializer: &mut dyn ShapeSerializer,
552                ) -> Result<(), SerdeError> {
553                    let binder = self.binder as *const HttpBindingSerializer<'_, S>
554                        as *mut HttpBindingSerializer<'_, S>;
555                    // SAFETY: The body serializer called serialize_members on
556                    // this proxy, passing &mut self (body). The binder wraps
557                    // that same body serializer. We need mutable access to the
558                    // binder to route writes. This is safe because:
559                    // 1. The body serializer's write_struct only calls
560                    //    serialize_members once, synchronously.
561                    // 2. Body member writes from the binder go back to the
562                    //    body serializer, which is in a valid state (between
563                    //    the { and } it emitted).
564                    self.value.serialize_members(unsafe { &mut *binder })
565                }
566            }
567            // Clear is_top_level so nested write_struct calls (from body members)
568            // take the else branch and delegate directly to the body serializer.
569            // input_schema is preserved so resolve_member continues to work.
570            self.is_top_level = false;
571            let proxy = Proxy {
572                binder: self,
573                value,
574            };
575            let binder_ptr = &mut *proxy.binder as *mut HttpBindingSerializer<'_, S>;
576            // SAFETY: `proxy` holds a shared reference to `binder` (via &mut that
577            // we reborrow). We need to call `binder.body.write_struct(schema, &proxy)`
578            // but can't do so through normal references because `proxy` borrows `binder`.
579            // The raw pointer dereference is safe because:
580            // 1. `binder_ptr` points to a valid, live `HttpBindingSerializer` (it was
581            //    just derived from `proxy.binder`).
582            // 2. `body.write_struct` is called synchronously and returns before `proxy`
583            //    is dropped, so the binder is not moved or deallocated.
584            // 3. The only re-entrant access is through `proxy.serialize_members`, which
585            //    uses the same raw-pointer pattern with its own safety justification above.
586            unsafe { (*binder_ptr).body.write_struct(schema, &proxy) }
587        } else {
588            // Nested struct (a body member targeting a structure): delegate
589            // entirely to the body serializer.
590            let schema = self.resolve_member(schema);
591            if schema.http_payload().is_some() {
592                // @httpPayload struct/union: codegen routes these by passing the
593                // target struct's schema directly (not the member schema), so this
594                // path is normally unreachable. Kept as a safety net.
595                self.body.write_struct(schema, value)?;
596                return Ok(());
597            }
598            self.body.write_struct(schema, value)
599        }
600    }
601
602    fn write_list(
603        &mut self,
604        schema: &Schema,
605        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
606    ) -> Result<(), SerdeError> {
607        let schema = self.resolve_member(schema);
608        // @httpHeader on a list: collect elements as comma-separated header value
609        if let Some(header) = schema.http_header() {
610            if !self.should_route_binding(schema) {
611                return Ok(());
612            }
613            let mut collector = ListElementCollector::for_header();
614            write_elements(&mut collector)?;
615            // RFC 7230: string values containing commas or quotes need quoting.
616            // Timestamps are NOT quoted even though http-date contains commas.
617            let header_val = collector
618                .values
619                .iter()
620                .zip(collector.quotable.iter())
621                .map(|(s, &quotable)| {
622                    if quotable && (s.contains(',') || s.contains('"')) {
623                        format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
624                    } else {
625                        s.clone()
626                    }
627                })
628                .collect::<Vec<_>>()
629                .join(", ");
630            self.headers.insert(header.value(), header_val);
631            return Ok(());
632        }
633        // @httpQuery on a list: add each element as a separate query param
634        if let Some(query) = schema.http_query() {
635            if !self.should_route_binding(schema) {
636                return Ok(());
637            }
638            let mut collector = ListElementCollector::for_query();
639            write_elements(&mut collector)?;
640            for val in collector.values {
641                self.query_params.push((Cow::Borrowed(query.value()), val));
642            }
643            return Ok(());
644        }
645        self.body.write_list(schema, write_elements)
646    }
647
648    fn write_map(
649        &mut self,
650        schema: &Schema,
651        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
652    ) -> Result<(), SerdeError> {
653        let schema = self.resolve_member(schema);
654        // @httpPrefixHeaders: serialize map entries as prefixed headers
655        if let Some(prefix) = schema.http_prefix_headers() {
656            if !self.should_route_binding(schema) {
657                return Ok(());
658            }
659            // Collect entries via a temporary serializer
660            let mut collector = MapEntryCollector::new(prefix.value().to_string());
661            write_entries(&mut collector)?;
662            // Names are dynamic (prefix + map key) — owned Strings.
663            for (k, v) in collector.entries {
664                self.headers.insert(k, v);
665            }
666            return Ok(());
667        }
668        // @httpQueryParams: serialize map entries as query params
669        if schema.http_query_params().is_some() {
670            if !self.should_route_binding(schema) {
671                return Ok(());
672            }
673            let mut collector = MapEntryCollector::new(String::new());
674            write_entries(&mut collector)?;
675            // Filter out keys that overlap with explicit @httpQuery params
676            // (query params take precedence over query params map entries)
677            let explicit_query_keys: Vec<&str> = self
678                .input_schema
679                .map(|s| {
680                    s.members()
681                        .iter()
682                        .filter_map(|m| m.http_query().map(|q| q.value()))
683                        .collect()
684                })
685                .unwrap_or_default();
686            for (k, v) in collector.entries {
687                if !explicit_query_keys.contains(&k.as_str()) {
688                    self.query_params.push((Cow::Owned(k), v));
689                }
690            }
691            return Ok(());
692        }
693        self.body.write_map(schema, write_entries)
694    }
695
696    fn write_boolean(&mut self, schema: &Schema, value: bool) -> Result<(), SerdeError> {
697        let schema = self.resolve_member(schema);
698        if let Some(binding) = http_string_binding(schema) {
699            return self.add_binding(binding, schema, &value.to_string());
700        }
701        self.body.write_boolean(schema, value)
702    }
703
704    fn write_byte(&mut self, schema: &Schema, value: i8) -> Result<(), SerdeError> {
705        let schema = self.resolve_member(schema);
706        if let Some(binding) = http_string_binding(schema) {
707            return self.add_binding(binding, schema, &value.to_string());
708        }
709        self.body.write_byte(schema, value)
710    }
711
712    fn write_short(&mut self, schema: &Schema, value: i16) -> Result<(), SerdeError> {
713        let schema = self.resolve_member(schema);
714        if let Some(binding) = http_string_binding(schema) {
715            return self.add_binding(binding, schema, &value.to_string());
716        }
717        self.body.write_short(schema, value)
718    }
719
720    fn write_integer(&mut self, schema: &Schema, value: i32) -> Result<(), SerdeError> {
721        let schema = self.resolve_member(schema);
722        if let Some(binding) = http_string_binding(schema) {
723            return self.add_binding(binding, schema, &value.to_string());
724        }
725        self.body.write_integer(schema, value)
726    }
727
728    fn write_long(&mut self, schema: &Schema, value: i64) -> Result<(), SerdeError> {
729        let schema = self.resolve_member(schema);
730        if let Some(binding) = http_string_binding(schema) {
731            return self.add_binding(binding, schema, &value.to_string());
732        }
733        self.body.write_long(schema, value)
734    }
735
736    fn write_float(&mut self, schema: &Schema, value: f32) -> Result<(), SerdeError> {
737        let schema = self.resolve_member(schema);
738        if let Some(binding) = http_string_binding(schema) {
739            return self.add_binding(binding, schema, &format_float_f32(value));
740        }
741        self.body.write_float(schema, value)
742    }
743
744    fn write_double(&mut self, schema: &Schema, value: f64) -> Result<(), SerdeError> {
745        let schema = self.resolve_member(schema);
746        if let Some(binding) = http_string_binding(schema) {
747            return self.add_binding(binding, schema, &format_float_f64(value));
748        }
749        self.body.write_double(schema, value)
750    }
751
752    fn write_big_integer(
753        &mut self,
754        schema: &Schema,
755        value: &aws_smithy_types::BigInteger,
756    ) -> Result<(), SerdeError> {
757        let schema = self.resolve_member(schema);
758        if let Some(binding) = http_string_binding(schema) {
759            return self.add_binding(binding, schema, value.as_ref());
760        }
761        self.body.write_big_integer(schema, value)
762    }
763
764    fn write_big_decimal(
765        &mut self,
766        schema: &Schema,
767        value: &aws_smithy_types::BigDecimal,
768    ) -> Result<(), SerdeError> {
769        let schema = self.resolve_member(schema);
770        if let Some(binding) = http_string_binding(schema) {
771            return self.add_binding(binding, schema, value.as_ref());
772        }
773        self.body.write_big_decimal(schema, value)
774    }
775
776    fn write_string(&mut self, schema: &Schema, value: &str) -> Result<(), SerdeError> {
777        let schema = self.resolve_member(schema);
778        if let Some(binding) = http_string_binding(schema) {
779            // @mediaType on a header: base64-encode the value
780            if schema.media_type().is_some() {
781                let encoded = aws_smithy_types::base64::encode(value.as_bytes());
782                return self.add_binding(binding, schema, &encoded);
783            }
784            return self.add_binding(binding, schema, value);
785        }
786        if schema.http_payload().is_some() {
787            if !self.should_route_binding(schema) {
788                return Ok(());
789            }
790            // SAFETY: We extend the lifetime of `value.as_bytes()` from its anonymous
791            // lifetime to `'a`. This is sound because:
792            // 1. `value` is borrowed from the input struct passed to `serialize_request`.
793            // 2. `HttpBindingSerializer` is a local variable within `serialize_request`
794            //    and is dropped before `serialize_request` returns.
795            // 3. The input struct (and thus `value`) outlives the serializer.
796            // 4. `raw_payload` is read in `serialize_request` immediately after
797            //    `serialize_members` returns, before the input is dropped.
798            // We use transmute rather than copying to avoid allocating for potentially
799            // multi-GB string payloads.
800            self.raw_payload =
801                Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value.as_bytes()) });
802            return Ok(());
803        }
804        self.body.write_string(schema, value)
805    }
806
807    fn write_blob(&mut self, schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
808        let schema = self.resolve_member(schema);
809        if schema.http_header().is_some() {
810            if !self.should_route_binding(schema) {
811                return Ok(());
812            }
813            let encoded = aws_smithy_types::base64::encode(value);
814            self.headers
815                .insert(schema.http_header().unwrap().value(), encoded);
816            return Ok(());
817        }
818        if schema.http_payload().is_some() {
819            if !self.should_route_binding(schema) {
820                return Ok(());
821            }
822            // SAFETY: We extend the lifetime of `value` (a `&[u8]`) from its
823            // anonymous lifetime to `'a`. This is sound because:
824            // 1. `value` is borrowed from the input struct passed to `serialize_request`.
825            // 2. `HttpBindingSerializer` is a local variable within `serialize_request`
826            //    and is dropped before `serialize_request` returns.
827            // 3. The input struct (and thus `value`) outlives the serializer.
828            // 4. `raw_payload` is read in `serialize_request` immediately after
829            //    `serialize_members` returns, before the input is dropped.
830            // We use transmute rather than copying to avoid allocating for potentially
831            // multi-GB blob payloads.
832            self.raw_payload = Some(unsafe { std::mem::transmute::<&[u8], &'a [u8]>(value) });
833            return Ok(());
834        }
835        self.body.write_blob(schema, value)
836    }
837
838    fn write_timestamp(
839        &mut self,
840        schema: &Schema,
841        value: &aws_smithy_types::DateTime,
842    ) -> Result<(), SerdeError> {
843        let schema = self.resolve_member(schema);
844        if let Some(binding) = http_string_binding(schema) {
845            // Headers default to http-date, query/label default to date-time
846            let format = if let Some(ts_trait) = schema.timestamp_format() {
847                match ts_trait.format() {
848                    crate::traits::TimestampFormat::EpochSeconds => {
849                        aws_smithy_types::date_time::Format::EpochSeconds
850                    }
851                    crate::traits::TimestampFormat::HttpDate => {
852                        aws_smithy_types::date_time::Format::HttpDate
853                    }
854                    crate::traits::TimestampFormat::DateTime => {
855                        aws_smithy_types::date_time::Format::DateTime
856                    }
857                }
858            } else {
859                match binding {
860                    HttpBinding::Header(_) => aws_smithy_types::date_time::Format::HttpDate,
861                    _ => aws_smithy_types::date_time::Format::DateTime,
862                }
863            };
864            let formatted = value
865                .fmt(format)
866                .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?;
867            return self.add_binding(binding, schema, &formatted);
868        }
869        self.body.write_timestamp(schema, value)
870    }
871
872    fn write_document(
873        &mut self,
874        schema: &Schema,
875        value: &aws_smithy_types::Document,
876    ) -> Result<(), SerdeError> {
877        self.body.write_document(schema, value)
878    }
879
880    fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError> {
881        self.body.write_null(schema)
882    }
883}
884
885/// Which HTTP location a member is bound to.
886enum HttpBinding {
887    Header(&'static str),
888    Query(&'static str),
889    Label,
890}
891
892/// Determine the HTTP binding for a member schema, if any.
893fn http_string_binding(schema: &Schema) -> Option<HttpBinding> {
894    if let Some(h) = schema.http_header() {
895        return Some(HttpBinding::Header(h.value()));
896    }
897    if let Some(q) = schema.http_query() {
898        return Some(HttpBinding::Query(q.value()));
899    }
900    if schema.http_label().is_some() {
901        return Some(HttpBinding::Label);
902    }
903    None
904}
905
906impl<'a, S> HttpBindingSerializer<'a, S> {
907    fn add_binding(
908        &mut self,
909        binding: HttpBinding,
910        schema: &Schema,
911        value: &str,
912    ) -> Result<(), SerdeError> {
913        // Dedupe per-member: see `should_route_binding`. Without this, a
914        // multi-pass body codec invokes `serialize_members` more than once
915        // and each pass would append to `headers` / `query_params` / `labels`.
916        if !self.should_route_binding(schema) {
917            return Ok(());
918        }
919        match binding {
920            HttpBinding::Header(name) => {
921                self.headers.insert(name, value.to_string());
922            }
923            HttpBinding::Query(name) => {
924                self.query_params
925                    .push((Cow::Borrowed(name), value.to_string()));
926            }
927            HttpBinding::Label => {
928                let name = schema
929                    .member_name()
930                    .ok_or_else(|| SerdeError::custom("httpLabel on non-member schema"))?;
931                self.labels.push((Cow::Borrowed(name), value.to_string()));
932            }
933        }
934        Ok(())
935    }
936}
937
938/// Whether a `ListElementCollector` is gathering values for a header or query param.
939/// Affects default timestamp format: `http-date` for headers, `date-time` for query.
940#[derive(Copy, Clone)]
941enum HttpListTarget {
942    Header,
943    Query,
944}
945
946/// Collects list element values as strings for @httpHeader and @httpQuery on lists.
947struct ListElementCollector {
948    values: Vec<String>,
949    /// Whether each value should be quoted if it contains commas (strings yes, timestamps no)
950    quotable: Vec<bool>,
951    target: HttpListTarget,
952}
953
954impl ListElementCollector {
955    fn for_header() -> Self {
956        Self::new(HttpListTarget::Header)
957    }
958
959    fn for_query() -> Self {
960        Self::new(HttpListTarget::Query)
961    }
962
963    fn new(target: HttpListTarget) -> Self {
964        Self {
965            values: Vec::new(),
966            quotable: Vec::new(),
967            target,
968        }
969    }
970
971    fn push(&mut self, value: String) {
972        self.quotable.push(true);
973        self.values.push(value);
974    }
975
976    fn push_unquotable(&mut self, value: String) {
977        self.quotable.push(false);
978        self.values.push(value);
979    }
980}
981
982impl ShapeSerializer for ListElementCollector {
983    fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
984        self.push(value.to_string());
985        Ok(())
986    }
987    fn write_boolean(&mut self, _: &Schema, value: bool) -> Result<(), SerdeError> {
988        self.push(value.to_string());
989        Ok(())
990    }
991    fn write_byte(&mut self, _: &Schema, value: i8) -> Result<(), SerdeError> {
992        self.push(value.to_string());
993        Ok(())
994    }
995    fn write_short(&mut self, _: &Schema, value: i16) -> Result<(), SerdeError> {
996        self.push(value.to_string());
997        Ok(())
998    }
999    fn write_integer(&mut self, _: &Schema, value: i32) -> Result<(), SerdeError> {
1000        self.push(value.to_string());
1001        Ok(())
1002    }
1003    fn write_long(&mut self, _: &Schema, value: i64) -> Result<(), SerdeError> {
1004        self.push(value.to_string());
1005        Ok(())
1006    }
1007    fn write_float(&mut self, _: &Schema, value: f32) -> Result<(), SerdeError> {
1008        self.push(format_float_f32(value));
1009        Ok(())
1010    }
1011    fn write_double(&mut self, _: &Schema, value: f64) -> Result<(), SerdeError> {
1012        self.push(format_float_f64(value));
1013        Ok(())
1014    }
1015    fn write_timestamp(
1016        &mut self,
1017        schema: &Schema,
1018        value: &aws_smithy_types::DateTime,
1019    ) -> Result<(), SerdeError> {
1020        let format = match schema.timestamp_format() {
1021            Some(ts) => match ts.format() {
1022                crate::traits::TimestampFormat::EpochSeconds => {
1023                    aws_smithy_types::date_time::Format::EpochSeconds
1024                }
1025                crate::traits::TimestampFormat::HttpDate => {
1026                    aws_smithy_types::date_time::Format::HttpDate
1027                }
1028                crate::traits::TimestampFormat::DateTime => {
1029                    aws_smithy_types::date_time::Format::DateTime
1030                }
1031            },
1032            // Default: headers use http-date, query params use date-time
1033            None => match self.target {
1034                HttpListTarget::Header => aws_smithy_types::date_time::Format::HttpDate,
1035                HttpListTarget::Query => aws_smithy_types::date_time::Format::DateTime,
1036            },
1037        };
1038        self.push_unquotable(
1039            value
1040                .fmt(format)
1041                .map_err(|e| SerdeError::custom(format!("failed to format timestamp: {e}")))?,
1042        );
1043        Ok(())
1044    }
1045    fn write_blob(&mut self, _schema: &Schema, value: &[u8]) -> Result<(), SerdeError> {
1046        self.push(aws_smithy_types::base64::encode(value));
1047        Ok(())
1048    }
1049    // Remaining methods are no-ops for list element collection
1050    fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
1051        Ok(())
1052    }
1053    fn write_list(
1054        &mut self,
1055        _: &Schema,
1056        _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1057    ) -> Result<(), SerdeError> {
1058        Ok(())
1059    }
1060    fn write_map(
1061        &mut self,
1062        _: &Schema,
1063        _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1064    ) -> Result<(), SerdeError> {
1065        Ok(())
1066    }
1067    fn write_big_integer(
1068        &mut self,
1069        _: &Schema,
1070        _: &aws_smithy_types::BigInteger,
1071    ) -> Result<(), SerdeError> {
1072        Ok(())
1073    }
1074    fn write_big_decimal(
1075        &mut self,
1076        _: &Schema,
1077        _: &aws_smithy_types::BigDecimal,
1078    ) -> Result<(), SerdeError> {
1079        Ok(())
1080    }
1081    fn write_document(
1082        &mut self,
1083        _: &Schema,
1084        _: &aws_smithy_types::Document,
1085    ) -> Result<(), SerdeError> {
1086        Ok(())
1087    }
1088    fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1089        Ok(())
1090    }
1091}
1092
1093/// Format a float for HTTP headers/query/labels.
1094/// Rust's Display writes "inf"/"-inf" but HTTP requires "Infinity"/"-Infinity".
1095fn format_float_f32(value: f32) -> String {
1096    if value.is_infinite() {
1097        if value.is_sign_positive() {
1098            "Infinity".to_string()
1099        } else {
1100            "-Infinity".to_string()
1101        }
1102    } else if value.is_nan() {
1103        "NaN".to_string()
1104    } else {
1105        value.to_string()
1106    }
1107}
1108
1109fn format_float_f64(value: f64) -> String {
1110    if value.is_infinite() {
1111        if value.is_sign_positive() {
1112            "Infinity".to_string()
1113        } else {
1114            "-Infinity".to_string()
1115        }
1116    } else if value.is_nan() {
1117        "NaN".to_string()
1118    } else {
1119        value.to_string()
1120    }
1121}
1122
1123/// Collects map key-value pairs written via ShapeSerializer for
1124/// @httpPrefixHeaders and @httpQueryParams.
1125struct MapEntryCollector {
1126    prefix: String,
1127    entries: Vec<(String, String)>,
1128    pending_key: Option<String>,
1129}
1130
1131impl MapEntryCollector {
1132    fn new(prefix: String) -> Self {
1133        Self {
1134            prefix,
1135            entries: Vec::new(),
1136            pending_key: None,
1137        }
1138    }
1139}
1140
1141impl ShapeSerializer for MapEntryCollector {
1142    fn write_string(&mut self, _schema: &Schema, value: &str) -> Result<(), SerdeError> {
1143        if let Some(key) = self.pending_key.take() {
1144            self.entries
1145                .push((format!("{}{}", self.prefix, key), value.to_string()));
1146        } else {
1147            self.pending_key = Some(value.to_string());
1148        }
1149        Ok(())
1150    }
1151
1152    // All other methods are no-ops — maps in HTTP bindings only have string keys/values.
1153    // Exception: write_list handles Map<String, List<String>> for @httpQueryParams.
1154    fn write_struct(&mut self, _: &Schema, _: &dyn SerializableStruct) -> Result<(), SerdeError> {
1155        Ok(())
1156    }
1157    fn write_list(
1158        &mut self,
1159        _: &Schema,
1160        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1161    ) -> Result<(), SerdeError> {
1162        // Map<String, List<String>>: each list element becomes a separate entry
1163        // with the same key (for @httpQueryParams).
1164        if let Some(key) = self.pending_key.take() {
1165            let mut collector = ListElementCollector::for_query(); // query params context
1166            write_elements(&mut collector)?;
1167            for val in collector.values {
1168                self.entries.push((format!("{}{}", self.prefix, key), val));
1169            }
1170        }
1171        Ok(())
1172    }
1173    fn write_map(
1174        &mut self,
1175        _: &Schema,
1176        _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1177    ) -> Result<(), SerdeError> {
1178        Ok(())
1179    }
1180    fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1181        Ok(())
1182    }
1183    fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1184        Ok(())
1185    }
1186    fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1187        Ok(())
1188    }
1189    fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1190        Ok(())
1191    }
1192    fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1193        Ok(())
1194    }
1195    fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1196        Ok(())
1197    }
1198    fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1199        Ok(())
1200    }
1201    fn write_big_integer(
1202        &mut self,
1203        _: &Schema,
1204        _: &aws_smithy_types::BigInteger,
1205    ) -> Result<(), SerdeError> {
1206        Ok(())
1207    }
1208    fn write_big_decimal(
1209        &mut self,
1210        _: &Schema,
1211        _: &aws_smithy_types::BigDecimal,
1212    ) -> Result<(), SerdeError> {
1213        Ok(())
1214    }
1215    fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1216        Ok(())
1217    }
1218    fn write_timestamp(
1219        &mut self,
1220        _: &Schema,
1221        _: &aws_smithy_types::DateTime,
1222    ) -> Result<(), SerdeError> {
1223        Ok(())
1224    }
1225    fn write_document(
1226        &mut self,
1227        _: &Schema,
1228        _: &aws_smithy_types::Document,
1229    ) -> Result<(), SerdeError> {
1230        Ok(())
1231    }
1232    fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1233        Ok(())
1234    }
1235}
1236
1237impl<C> ClientProtocolInner for HttpBindingProtocol<C>
1238where
1239    C: Codec + Send + Sync + std::fmt::Debug + 'static,
1240    for<'a> C::Deserializer<'a>: ShapeDeserializer,
1241{
1242    type Request = Request;
1243    type Response = Response;
1244
1245    fn protocol_id(&self) -> &ShapeId {
1246        &self.protocol_id
1247    }
1248
1249    fn serialize_request(
1250        &self,
1251        input: &dyn SerializableStruct,
1252        input_schema: &Schema,
1253        endpoint: &str,
1254        cfg: &ConfigBag,
1255    ) -> Result<Request, SerdeError> {
1256        let body = self.codec.create_serializer();
1257        self.serialize_request_with_body(body, input, input_schema, endpoint, cfg)
1258    }
1259
1260    fn deserialize_response<'a>(
1261        &self,
1262        response: &'a Response,
1263        _output_schema: &Schema,
1264        _cfg: &ConfigBag,
1265    ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
1266        // For non-streaming responses the orchestrator has already loaded
1267        // the body into an in-memory `Once(...)`, so `bytes()` returns the
1268        // payload to feed into the codec. For streaming responses (whose
1269        // outputs have an `@httpPayload` streaming blob or event-stream
1270        // member) the body is left as a streaming `BoxBody` — possibly
1271        // further wrapped by interceptors such as `ResponseChecksumInterceptor`
1272        // — and `bytes()` returns `None`. The streaming codegen path
1273        // doesn't actually feed the body through this deserializer (it
1274        // passes `&[]` to `deserialize_with_response`), so we hand back an
1275        // empty-input deserializer instead of erroring. Empty input is
1276        // interpreted by the codec as "no body members to read", which
1277        // matches the streaming path's contract.
1278        let body = response.body().bytes().unwrap_or(&[]);
1279        Ok(Box::new(self.codec.create_deserializer(body)))
1280    }
1281
1282    fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
1283        Some(&self.codec)
1284    }
1285
1286    fn update_endpoint(
1287        &self,
1288        request: &mut Request,
1289        endpoint: &aws_smithy_types::endpoint::Endpoint,
1290        cfg: &ConfigBag,
1291    ) -> Result<(), SerdeError> {
1292        apply_http_endpoint(request, endpoint, cfg)
1293    }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298    use super::*;
1299    use crate::serde::SerializableStruct;
1300    use crate::{prelude::*, ShapeType};
1301
1302    #[test]
1303    fn visited_members_inline_dedup() {
1304        let mut v = VisitedMembers::new();
1305        // First insert returns true; identical second insert returns false.
1306        assert!(v.insert(3));
1307        assert!(!v.insert(3));
1308        // Distinct indices return true.
1309        assert!(v.insert(7));
1310        assert!(v.insert(0));
1311        // Re-asserting any of the above returns false.
1312        assert!(!v.insert(3));
1313        assert!(!v.insert(7));
1314        assert!(!v.insert(0));
1315    }
1316
1317    #[test]
1318    fn visited_members_spills_to_overflow() {
1319        // Exceeding INLINE_CAPACITY pushes additional entries into the heap
1320        // overflow vec. Both halves must dedup correctly.
1321        let mut v = VisitedMembers::new();
1322        let n = VisitedMembers::INLINE_CAPACITY;
1323        // Fill inline storage.
1324        for i in 0..n {
1325            assert!(v.insert(i), "fresh inline insert at {i} must return true");
1326        }
1327        // Inserting again into inline range returns false (no allocation).
1328        for i in 0..n {
1329            assert!(
1330                !v.insert(i),
1331                "duplicate inline insert at {i} must return false"
1332            );
1333        }
1334        // Cross the capacity boundary — these go into overflow.
1335        assert!(v.insert(n));
1336        assert!(v.insert(n + 5));
1337        // Duplicates of overflow entries return false.
1338        assert!(!v.insert(n));
1339        assert!(!v.insert(n + 5));
1340        // And duplicates of inline entries still return false even after
1341        // the overflow vec is non-empty.
1342        assert!(!v.insert(0));
1343    }
1344
1345    struct TestSerializer {
1346        output: Vec<u8>,
1347    }
1348
1349    impl FinishSerializer for TestSerializer {
1350        fn finish(self) -> Vec<u8> {
1351            self.output
1352        }
1353    }
1354
1355    impl ShapeSerializer for TestSerializer {
1356        fn write_struct(
1357            &mut self,
1358            _: &Schema,
1359            value: &dyn SerializableStruct,
1360        ) -> Result<(), SerdeError> {
1361            self.output.push(b'{');
1362            value.serialize_members(self)?;
1363            self.output.push(b'}');
1364            Ok(())
1365        }
1366        fn write_list(
1367            &mut self,
1368            _: &Schema,
1369            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1370        ) -> Result<(), SerdeError> {
1371            Ok(())
1372        }
1373        fn write_map(
1374            &mut self,
1375            _: &Schema,
1376            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1377        ) -> Result<(), SerdeError> {
1378            Ok(())
1379        }
1380        fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1381            Ok(())
1382        }
1383        fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1384            Ok(())
1385        }
1386        fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1387            Ok(())
1388        }
1389        fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1390            Ok(())
1391        }
1392        fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1393            Ok(())
1394        }
1395        fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1396            Ok(())
1397        }
1398        fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1399            Ok(())
1400        }
1401        fn write_big_integer(
1402            &mut self,
1403            _: &Schema,
1404            _: &aws_smithy_types::BigInteger,
1405        ) -> Result<(), SerdeError> {
1406            Ok(())
1407        }
1408        fn write_big_decimal(
1409            &mut self,
1410            _: &Schema,
1411            _: &aws_smithy_types::BigDecimal,
1412        ) -> Result<(), SerdeError> {
1413            Ok(())
1414        }
1415        fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
1416            self.output.extend_from_slice(v.as_bytes());
1417            Ok(())
1418        }
1419        fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1420            Ok(())
1421        }
1422        fn write_timestamp(
1423            &mut self,
1424            _: &Schema,
1425            _: &aws_smithy_types::DateTime,
1426        ) -> Result<(), SerdeError> {
1427            Ok(())
1428        }
1429        fn write_document(
1430            &mut self,
1431            _: &Schema,
1432            _: &aws_smithy_types::Document,
1433        ) -> Result<(), SerdeError> {
1434            Ok(())
1435        }
1436        fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1437            Ok(())
1438        }
1439    }
1440
1441    struct TestDeserializer<'a> {
1442        input: &'a [u8],
1443    }
1444
1445    impl ShapeDeserializer for TestDeserializer<'_> {
1446        fn read_struct(
1447            &mut self,
1448            _: &Schema,
1449            _: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1450        ) -> Result<(), SerdeError> {
1451            Ok(())
1452        }
1453        fn read_list(
1454            &mut self,
1455            _: &Schema,
1456            _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1457        ) -> Result<(), SerdeError> {
1458            Ok(())
1459        }
1460        fn read_map(
1461            &mut self,
1462            _: &Schema,
1463            _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
1464        ) -> Result<(), SerdeError> {
1465            Ok(())
1466        }
1467        fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
1468            Ok(false)
1469        }
1470        fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
1471            Ok(0)
1472        }
1473        fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
1474            Ok(0)
1475        }
1476        fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
1477            Ok(0)
1478        }
1479        fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
1480            Ok(0)
1481        }
1482        fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
1483            Ok(0.0)
1484        }
1485        fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
1486            Ok(0.0)
1487        }
1488        fn read_big_integer(
1489            &mut self,
1490            _: &Schema,
1491        ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
1492            use std::str::FromStr;
1493            Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
1494        }
1495        fn read_big_decimal(
1496            &mut self,
1497            _: &Schema,
1498        ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
1499            use std::str::FromStr;
1500            Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
1501        }
1502        fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
1503            Ok(String::from_utf8_lossy(self.input).into_owned())
1504        }
1505        fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
1506            Ok(aws_smithy_types::Blob::new(vec![]))
1507        }
1508        fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
1509            Ok(aws_smithy_types::DateTime::from_secs(0))
1510        }
1511        fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
1512            Ok(aws_smithy_types::Document::Null)
1513        }
1514        fn is_null(&self) -> bool {
1515            false
1516        }
1517        fn container_size(&self) -> Option<usize> {
1518            None
1519        }
1520    }
1521
1522    #[derive(Debug)]
1523    struct TestCodec;
1524
1525    impl Codec for TestCodec {
1526        type Serializer = TestSerializer;
1527        type Deserializer<'a> = TestDeserializer<'a>;
1528        fn create_serializer(&self) -> Self::Serializer {
1529            TestSerializer { output: Vec::new() }
1530        }
1531        fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
1532            TestDeserializer { input }
1533        }
1534    }
1535
1536    static TEST_SCHEMA: Schema =
1537        Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
1538
1539    struct EmptyStruct;
1540    impl SerializableStruct for EmptyStruct {
1541        fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1542            Ok(())
1543        }
1544    }
1545
1546    static NAME_MEMBER: Schema = Schema::new_member(
1547        crate::shape_id!("test", "TestStruct"),
1548        ShapeType::String,
1549        "name",
1550        0,
1551    );
1552    static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
1553    static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
1554        crate::shape_id!("test", "TestStruct"),
1555        ShapeType::Structure,
1556        MEMBERS,
1557    );
1558
1559    struct NameStruct;
1560    impl SerializableStruct for NameStruct {
1561        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1562            s.write_string(&NAME_MEMBER, "Alice")
1563        }
1564    }
1565
1566    fn make_protocol() -> HttpBindingProtocol<TestCodec> {
1567        HttpBindingProtocol::new(
1568            crate::shape_id!("test", "proto"),
1569            TestCodec,
1570            "application/test",
1571        )
1572    }
1573
1574    #[test]
1575    fn serialize_sets_content_type() {
1576        // A struct with body members gets Content-Type
1577        let request = make_protocol()
1578            .serialize_request(
1579                &EmptyStruct,
1580                &STRUCT_WITH_MEMBER,
1581                "https://example.com",
1582                &ConfigBag::base(),
1583            )
1584            .unwrap();
1585        assert_eq!(
1586            request.headers().get("Content-Type").unwrap(),
1587            "application/test"
1588        );
1589    }
1590
1591    #[test]
1592    fn serialize_no_body_members_omits_content_type() {
1593        // A struct with no members gets no Content-Type per REST-JSON spec
1594        let request = make_protocol()
1595            .serialize_request(
1596                &EmptyStruct,
1597                &TEST_SCHEMA,
1598                "https://example.com",
1599                &ConfigBag::base(),
1600            )
1601            .unwrap();
1602        assert!(request.headers().get("Content-Type").is_none());
1603    }
1604
1605    /// The bug fix at the center of PR #4686 review: when a presigning
1606    /// interceptor (or any other caller) stores a `SharedHeaderOmitSettings`
1607    /// in the config bag, the runtime must not insert protocol-default
1608    /// Content-Type or Content-Length headers — even on the standard-body
1609    /// path used by ordinary structure inputs.
1610    #[test]
1611    fn presigning_omit_settings_suppress_default_content_headers() {
1612        use crate::header_omit_settings::{HeaderOmitSettings, SharedHeaderOmitSettings};
1613        use aws_smithy_types::config_bag::Layer;
1614
1615        #[derive(Debug)]
1616        struct OmitBoth;
1617        impl HeaderOmitSettings for OmitBoth {
1618            fn should_omit_default_content_type(&self) -> bool {
1619                true
1620            }
1621            fn should_omit_default_content_length(&self) -> bool {
1622                true
1623            }
1624        }
1625
1626        let mut layer = Layer::new("test_omit");
1627        layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
1628        let cfg = ConfigBag::of_layers(vec![layer]);
1629
1630        let request = make_protocol()
1631            .serialize_request(
1632                &NameStruct,
1633                &STRUCT_WITH_MEMBER,
1634                "https://example.com",
1635                &cfg,
1636            )
1637            .unwrap();
1638        assert!(
1639            request.headers().get("Content-Type").is_none(),
1640            "presigning omit suppresses default Content-Type"
1641        );
1642        assert!(
1643            request.headers().get("Content-Length").is_none(),
1644            "presigning omit suppresses default Content-Length"
1645        );
1646    }
1647
1648    /// Companion to `presigning_omit_settings_suppress_default_content_headers`:
1649    /// when no `SharedHeaderOmitSettings` is in the config bag, the runtime
1650    /// inserts the protocol's default Content-Type plus a body-length-derived
1651    /// Content-Length. `NameStruct` writes a single `"Alice"` member, which
1652    /// the test codec frames as `{Alice}` for a 7-byte body — exercises the
1653    /// `len > 0` branch of the Content-Length insertion logic.
1654    #[test]
1655    fn default_content_headers_inserted_when_omit_settings_absent() {
1656        let request = make_protocol()
1657            .serialize_request(
1658                &NameStruct,
1659                &STRUCT_WITH_MEMBER,
1660                "https://example.com",
1661                &ConfigBag::base(),
1662            )
1663            .unwrap();
1664        assert_eq!(
1665            request
1666                .headers()
1667                .get("Content-Type")
1668                .expect("Content-Type set"),
1669            "application/test"
1670        );
1671        assert_eq!(
1672            request
1673                .headers()
1674                .get("Content-Length")
1675                .expect("Content-Length set"),
1676            "7"
1677        );
1678    }
1679
1680    /// When a schema is annotated `with_no_body_members()`, the body codec
1681    /// must not be invoked (no XML/JSON wrapper element gets opened, no
1682    /// `serialize_members` re-entry through a Proxy). HTTP-bound members
1683    /// still get routed to headers/query/labels via the binder. Verified by
1684    /// substituting a body codec that panics on any write call.
1685    #[test]
1686    fn serialize_skips_body_codec_when_no_body_members() {
1687        use std::sync::atomic::{AtomicUsize, Ordering};
1688
1689        static WRITE_CALLS: AtomicUsize = AtomicUsize::new(0);
1690
1691        struct PanicSerializer;
1692        impl FinishSerializer for PanicSerializer {
1693            fn finish(self) -> Vec<u8> {
1694                panic!("body codec finish() called — short-circuit failed");
1695            }
1696        }
1697        impl ShapeSerializer for PanicSerializer {
1698            fn write_struct(
1699                &mut self,
1700                _: &Schema,
1701                _: &dyn SerializableStruct,
1702            ) -> Result<(), SerdeError> {
1703                WRITE_CALLS.fetch_add(1, Ordering::SeqCst);
1704                panic!("body codec write_struct() called — short-circuit failed");
1705            }
1706            fn write_list(
1707                &mut self,
1708                _: &Schema,
1709                _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1710            ) -> Result<(), SerdeError> {
1711                panic!("body codec write_list() called");
1712            }
1713            fn write_map(
1714                &mut self,
1715                _: &Schema,
1716                _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
1717            ) -> Result<(), SerdeError> {
1718                panic!("body codec write_map() called");
1719            }
1720            fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
1721                panic!("body codec write_boolean() called");
1722            }
1723            fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
1724                panic!("body codec write_byte() called");
1725            }
1726            fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
1727                panic!("body codec write_short() called");
1728            }
1729            fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
1730                panic!("body codec write_integer() called");
1731            }
1732            fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
1733                panic!("body codec write_long() called");
1734            }
1735            fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
1736                panic!("body codec write_float() called");
1737            }
1738            fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
1739                panic!("body codec write_double() called");
1740            }
1741            fn write_big_integer(
1742                &mut self,
1743                _: &Schema,
1744                _: &aws_smithy_types::BigInteger,
1745            ) -> Result<(), SerdeError> {
1746                panic!("body codec write_big_integer() called");
1747            }
1748            fn write_big_decimal(
1749                &mut self,
1750                _: &Schema,
1751                _: &aws_smithy_types::BigDecimal,
1752            ) -> Result<(), SerdeError> {
1753                panic!("body codec write_big_decimal() called");
1754            }
1755            fn write_string(&mut self, _: &Schema, _: &str) -> Result<(), SerdeError> {
1756                panic!("body codec write_string() called");
1757            }
1758            fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
1759                panic!("body codec write_blob() called");
1760            }
1761            fn write_timestamp(
1762                &mut self,
1763                _: &Schema,
1764                _: &aws_smithy_types::DateTime,
1765            ) -> Result<(), SerdeError> {
1766                panic!("body codec write_timestamp() called");
1767            }
1768            fn write_document(
1769                &mut self,
1770                _: &Schema,
1771                _: &aws_smithy_types::Document,
1772            ) -> Result<(), SerdeError> {
1773                panic!("body codec write_document() called");
1774            }
1775            fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
1776                panic!("body codec write_null() called");
1777            }
1778        }
1779
1780        #[derive(Debug)]
1781        struct PanicCodec;
1782        impl Codec for PanicCodec {
1783            type Serializer = PanicSerializer;
1784            type Deserializer<'a> = TestDeserializer<'a>;
1785            fn create_serializer(&self) -> Self::Serializer {
1786                PanicSerializer
1787            }
1788            fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
1789                TestDeserializer { input }
1790            }
1791        }
1792
1793        // Header-only struct: one `@httpHeader` member, marked
1794        // `with_no_body_members()`. The runtime should never touch the body
1795        // codec.
1796        static HEADER_MEMBER: Schema = Schema::new_member(
1797            crate::shape_id!("test", "HeaderOnlyStruct"),
1798            ShapeType::String,
1799            "x_header",
1800            0,
1801        )
1802        .with_http_header("X-Header");
1803        static HEADER_MEMBERS: &[&Schema] = &[&HEADER_MEMBER];
1804        static HEADER_ONLY_SCHEMA: Schema = Schema::new_struct(
1805            crate::shape_id!("test", "HeaderOnlyStruct"),
1806            ShapeType::Structure,
1807            HEADER_MEMBERS,
1808        )
1809        .with_no_body_members();
1810
1811        struct HeaderOnlyStruct;
1812        impl SerializableStruct for HeaderOnlyStruct {
1813            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1814                s.write_string(&HEADER_MEMBER, "hello")
1815            }
1816        }
1817
1818        let protocol = HttpBindingProtocol::new(
1819            crate::shape_id!("test", "testProtocol"),
1820            PanicCodec,
1821            "application/test",
1822        );
1823        let request = protocol
1824            .serialize_request(
1825                &HeaderOnlyStruct,
1826                &HEADER_ONLY_SCHEMA,
1827                "https://example.com",
1828                &ConfigBag::base(),
1829            )
1830            .unwrap();
1831
1832        // No body, no Content-Type, header is set
1833        assert_eq!(request.body().bytes().unwrap_or(&[]), b"");
1834        assert!(request.headers().get("Content-Type").is_none());
1835        assert_eq!(request.headers().get("X-Header").unwrap(), "hello");
1836        // Sanity: the panic-on-write codec was never written to
1837        assert_eq!(WRITE_CALLS.load(Ordering::SeqCst), 0);
1838    }
1839
1840    /// Inverse case: a struct WITHOUT `with_no_body_members()` (default
1841    /// `has_body_members == true`) and an actual body member must still
1842    /// invoke the body codec. Guards against accidentally short-circuiting
1843    /// schemas that were never opted in.
1844    #[test]
1845    fn serialize_invokes_body_codec_when_has_body_members() {
1846        let request = make_protocol()
1847            .serialize_request(
1848                &NameStruct,
1849                &STRUCT_WITH_MEMBER,
1850                "https://example.com",
1851                &ConfigBag::base(),
1852            )
1853            .unwrap();
1854        // TestSerializer writes "{Alice}" — the wrapper braces prove the
1855        // body codec was invoked.
1856        assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
1857        assert_eq!(
1858            request.headers().get("Content-Type").unwrap(),
1859            "application/test"
1860        );
1861    }
1862
1863    #[test]
1864    fn serialize_sets_uri() {
1865        let request = make_protocol()
1866            .serialize_request(
1867                &EmptyStruct,
1868                &TEST_SCHEMA,
1869                "https://example.com/path",
1870                &ConfigBag::base(),
1871            )
1872            .unwrap();
1873        assert_eq!(request.uri(), "https://example.com/path");
1874    }
1875
1876    #[test]
1877    fn serialize_body() {
1878        let request = make_protocol()
1879            .serialize_request(
1880                &NameStruct,
1881                &STRUCT_WITH_MEMBER,
1882                "https://example.com",
1883                &ConfigBag::base(),
1884            )
1885            .unwrap();
1886        assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
1887    }
1888
1889    #[test]
1890    fn deserialize_response() {
1891        let response = Response::new(
1892            200u16.try_into().unwrap(),
1893            SdkBody::from(r#"{"name":"Bob"}"#),
1894        );
1895        let mut deser = make_protocol()
1896            .deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
1897            .unwrap();
1898        assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"name":"Bob"}"#);
1899    }
1900
1901    #[test]
1902    fn update_endpoint() {
1903        let mut request = make_protocol()
1904            .serialize_request(
1905                &EmptyStruct,
1906                &TEST_SCHEMA,
1907                "https://old.example.com",
1908                &ConfigBag::base(),
1909            )
1910            .unwrap();
1911        let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
1912            .url("https://new.example.com")
1913            .build();
1914        make_protocol()
1915            .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
1916            .unwrap();
1917        assert_eq!(request.uri(), "https://new.example.com/");
1918    }
1919
1920    #[test]
1921    fn protocol_id() {
1922        let protocol = HttpBindingProtocol::new(
1923            crate::shape_id!("aws.protocols", "restJson1"),
1924            TestCodec,
1925            "application/json",
1926        );
1927        assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#restJson1");
1928    }
1929
1930    #[test]
1931    fn invalid_uri_returns_error() {
1932        assert!(make_protocol()
1933            .serialize_request(
1934                &EmptyStruct,
1935                &TEST_SCHEMA,
1936                "not a valid uri\n\n",
1937                &ConfigBag::base()
1938            )
1939            .is_err());
1940    }
1941
1942    // -- @httpHeader tests --
1943
1944    static HEADER_MEMBER: Schema = Schema::new_member(
1945        crate::shape_id!("test", "S"),
1946        ShapeType::String,
1947        "xToken",
1948        0,
1949    )
1950    .with_http_header("X-Token");
1951
1952    static HEADER_SCHEMA: Schema = Schema::new_struct(
1953        crate::shape_id!("test", "S"),
1954        ShapeType::Structure,
1955        &[&HEADER_MEMBER],
1956    );
1957
1958    struct HeaderStruct;
1959    impl SerializableStruct for HeaderStruct {
1960        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1961            s.write_string(&HEADER_MEMBER, "my-token-value")
1962        }
1963    }
1964
1965    #[test]
1966    fn http_header_string() {
1967        let request = make_protocol()
1968            .serialize_request(
1969                &HeaderStruct,
1970                &HEADER_SCHEMA,
1971                "https://example.com",
1972                &ConfigBag::base(),
1973            )
1974            .unwrap();
1975        assert_eq!(request.headers().get("X-Token").unwrap(), "my-token-value");
1976    }
1977
1978    static INT_HEADER_MEMBER: Schema = Schema::new_member(
1979        crate::shape_id!("test", "S"),
1980        ShapeType::Integer,
1981        "retryCount",
1982        0,
1983    )
1984    .with_http_header("X-Retry-Count");
1985
1986    static INT_HEADER_SCHEMA: Schema = Schema::new_struct(
1987        crate::shape_id!("test", "S"),
1988        ShapeType::Structure,
1989        &[&INT_HEADER_MEMBER],
1990    );
1991
1992    struct IntHeaderStruct;
1993    impl SerializableStruct for IntHeaderStruct {
1994        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1995            s.write_integer(&INT_HEADER_MEMBER, 3)
1996        }
1997    }
1998
1999    #[test]
2000    fn http_header_integer() {
2001        let request = make_protocol()
2002            .serialize_request(
2003                &IntHeaderStruct,
2004                &INT_HEADER_SCHEMA,
2005                "https://example.com",
2006                &ConfigBag::base(),
2007            )
2008            .unwrap();
2009        assert_eq!(request.headers().get("X-Retry-Count").unwrap(), "3");
2010    }
2011
2012    static BOOL_HEADER_MEMBER: Schema = Schema::new_member(
2013        crate::shape_id!("test", "S"),
2014        ShapeType::Boolean,
2015        "verbose",
2016        0,
2017    )
2018    .with_http_header("X-Verbose");
2019
2020    static BOOL_HEADER_SCHEMA: Schema = Schema::new_struct(
2021        crate::shape_id!("test", "S"),
2022        ShapeType::Structure,
2023        &[&BOOL_HEADER_MEMBER],
2024    );
2025
2026    struct BoolHeaderStruct;
2027    impl SerializableStruct for BoolHeaderStruct {
2028        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2029            s.write_boolean(&BOOL_HEADER_MEMBER, true)
2030        }
2031    }
2032
2033    #[test]
2034    fn http_header_boolean() {
2035        let request = make_protocol()
2036            .serialize_request(
2037                &BoolHeaderStruct,
2038                &BOOL_HEADER_SCHEMA,
2039                "https://example.com",
2040                &ConfigBag::base(),
2041            )
2042            .unwrap();
2043        assert_eq!(request.headers().get("X-Verbose").unwrap(), "true");
2044    }
2045
2046    // -- @httpQuery tests --
2047
2048    static QUERY_MEMBER: Schema =
2049        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "color", 0)
2050            .with_http_query("color");
2051
2052    static QUERY_SCHEMA: Schema = Schema::new_struct(
2053        crate::shape_id!("test", "S"),
2054        ShapeType::Structure,
2055        &[&QUERY_MEMBER],
2056    );
2057
2058    struct QueryStruct;
2059    impl SerializableStruct for QueryStruct {
2060        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2061            s.write_string(&QUERY_MEMBER, "blue")
2062        }
2063    }
2064
2065    #[test]
2066    fn http_query_string() {
2067        let request = make_protocol()
2068            .serialize_request(
2069                &QueryStruct,
2070                &QUERY_SCHEMA,
2071                "https://example.com/things",
2072                &ConfigBag::base(),
2073            )
2074            .unwrap();
2075        assert_eq!(request.uri(), "https://example.com/things?color=blue");
2076    }
2077
2078    static INT_QUERY_MEMBER: Schema =
2079        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Integer, "size", 0)
2080            .with_http_query("size");
2081
2082    static INT_QUERY_SCHEMA: Schema = Schema::new_struct(
2083        crate::shape_id!("test", "S"),
2084        ShapeType::Structure,
2085        &[&INT_QUERY_MEMBER],
2086    );
2087
2088    struct IntQueryStruct;
2089    impl SerializableStruct for IntQueryStruct {
2090        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2091            s.write_integer(&INT_QUERY_MEMBER, 42)
2092        }
2093    }
2094
2095    #[test]
2096    fn http_query_integer() {
2097        let request = make_protocol()
2098            .serialize_request(
2099                &IntQueryStruct,
2100                &INT_QUERY_SCHEMA,
2101                "https://example.com/things",
2102                &ConfigBag::base(),
2103            )
2104            .unwrap();
2105        assert_eq!(request.uri(), "https://example.com/things?size=42");
2106    }
2107
2108    // -- Multiple @httpQuery params --
2109
2110    static Q1: Schema =
2111        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "a", 0)
2112            .with_http_query("a");
2113    static Q2: Schema =
2114        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "b", 1)
2115            .with_http_query("b");
2116    static MULTI_QUERY_SCHEMA: Schema = Schema::new_struct(
2117        crate::shape_id!("test", "S"),
2118        ShapeType::Structure,
2119        &[&Q1, &Q2],
2120    );
2121
2122    struct MultiQueryStruct;
2123    impl SerializableStruct for MultiQueryStruct {
2124        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2125            s.write_string(&Q1, "x")?;
2126            s.write_string(&Q2, "y")
2127        }
2128    }
2129
2130    #[test]
2131    fn http_query_multiple_params() {
2132        let request = make_protocol()
2133            .serialize_request(
2134                &MultiQueryStruct,
2135                &MULTI_QUERY_SCHEMA,
2136                "https://example.com",
2137                &ConfigBag::base(),
2138            )
2139            .unwrap();
2140        assert_eq!(request.uri(), "https://example.com?a=x&b=y");
2141    }
2142
2143    // -- @httpQuery with percent-encoding --
2144
2145    #[test]
2146    fn http_query_percent_encodes_values() {
2147        struct SpaceQueryStruct;
2148        impl SerializableStruct for SpaceQueryStruct {
2149            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2150                s.write_string(&QUERY_MEMBER, "hello world")
2151            }
2152        }
2153        let request = make_protocol()
2154            .serialize_request(
2155                &SpaceQueryStruct,
2156                &QUERY_SCHEMA,
2157                "https://example.com",
2158                &ConfigBag::base(),
2159            )
2160            .unwrap();
2161        assert_eq!(request.uri(), "https://example.com?color=hello%20world");
2162    }
2163
2164    // -- @httpLabel tests --
2165
2166    static LABEL_MEMBER: Schema = Schema::new_member(
2167        crate::shape_id!("test", "S"),
2168        ShapeType::String,
2169        "bucketName",
2170        0,
2171    )
2172    .with_http_label();
2173
2174    static LABEL_SCHEMA: Schema = Schema::new_struct(
2175        crate::shape_id!("test", "S"),
2176        ShapeType::Structure,
2177        &[&LABEL_MEMBER],
2178    );
2179
2180    struct LabelStruct;
2181    impl SerializableStruct for LabelStruct {
2182        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2183            s.write_string(&LABEL_MEMBER, "my-bucket")
2184        }
2185    }
2186
2187    #[test]
2188    fn http_label_substitution() {
2189        let request = make_protocol()
2190            .serialize_request(
2191                &LabelStruct,
2192                &LABEL_SCHEMA,
2193                "https://example.com/{bucketName}/objects",
2194                &ConfigBag::base(),
2195            )
2196            .unwrap();
2197        assert_eq!(request.uri(), "https://example.com/my-bucket/objects");
2198    }
2199
2200    #[test]
2201    fn http_label_percent_encodes() {
2202        struct SpecialLabelStruct;
2203        impl SerializableStruct for SpecialLabelStruct {
2204            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2205                s.write_string(&LABEL_MEMBER, "my bucket/name")
2206            }
2207        }
2208        let request = make_protocol()
2209            .serialize_request(
2210                &SpecialLabelStruct,
2211                &LABEL_SCHEMA,
2212                "https://example.com/{bucketName}",
2213                &ConfigBag::base(),
2214            )
2215            .unwrap();
2216        assert!(request.uri().contains("my%20bucket%2Fname"));
2217    }
2218
2219    static INT_LABEL_MEMBER: Schema = Schema::new_member(
2220        crate::shape_id!("test", "S"),
2221        ShapeType::Integer,
2222        "itemId",
2223        0,
2224    )
2225    .with_http_label();
2226
2227    static INT_LABEL_SCHEMA: Schema = Schema::new_struct(
2228        crate::shape_id!("test", "S"),
2229        ShapeType::Structure,
2230        &[&INT_LABEL_MEMBER],
2231    );
2232
2233    struct IntLabelStruct;
2234    impl SerializableStruct for IntLabelStruct {
2235        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2236            s.write_integer(&INT_LABEL_MEMBER, 123)
2237        }
2238    }
2239
2240    #[test]
2241    fn http_label_integer() {
2242        let request = make_protocol()
2243            .serialize_request(
2244                &IntLabelStruct,
2245                &INT_LABEL_SCHEMA,
2246                "https://example.com/items/{itemId}",
2247                &ConfigBag::base(),
2248            )
2249            .unwrap();
2250        assert_eq!(request.uri(), "https://example.com/items/123");
2251    }
2252
2253    // -- Combined: @httpHeader + @httpQuery + @httpLabel + body --
2254
2255    static COMBINED_LABEL: Schema =
2256        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "id", 0)
2257            .with_http_label();
2258    static COMBINED_HEADER: Schema =
2259        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "token", 1)
2260            .with_http_header("X-Token");
2261    static COMBINED_QUERY: Schema = Schema::new_member(
2262        crate::shape_id!("test", "S"),
2263        ShapeType::String,
2264        "filter",
2265        2,
2266    )
2267    .with_http_query("filter");
2268    static COMBINED_BODY: Schema =
2269        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::String, "data", 3);
2270    static COMBINED_SCHEMA: Schema = Schema::new_struct(
2271        crate::shape_id!("test", "S"),
2272        ShapeType::Structure,
2273        &[
2274            &COMBINED_LABEL,
2275            &COMBINED_HEADER,
2276            &COMBINED_QUERY,
2277            &COMBINED_BODY,
2278        ],
2279    );
2280
2281    struct CombinedStruct;
2282    impl SerializableStruct for CombinedStruct {
2283        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2284            s.write_string(&COMBINED_LABEL, "item-42")?;
2285            s.write_string(&COMBINED_HEADER, "secret")?;
2286            s.write_string(&COMBINED_QUERY, "active")?;
2287            s.write_string(&COMBINED_BODY, "payload-data")
2288        }
2289    }
2290
2291    #[test]
2292    fn combined_bindings() {
2293        let request = make_protocol()
2294            .serialize_request(
2295                &CombinedStruct,
2296                &COMBINED_SCHEMA,
2297                "https://example.com/{id}/details",
2298                &ConfigBag::base(),
2299            )
2300            .unwrap();
2301        assert_eq!(
2302            request.uri(),
2303            "https://example.com/item-42/details?filter=active"
2304        );
2305        // Header
2306        assert_eq!(request.headers().get("X-Token").unwrap(), "secret");
2307        // Body contains only the unbound member
2308        let body = request.body().bytes().unwrap();
2309        assert!(body
2310            .windows(b"payload-data".len())
2311            .any(|w| w == b"payload-data"));
2312    }
2313
2314    // -- @httpPrefixHeaders tests --
2315
2316    static PREFIX_MEMBER: Schema =
2317        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "metadata", 0)
2318            .with_http_prefix_headers("X-Meta-");
2319
2320    static PREFIX_SCHEMA: Schema = Schema::new_struct(
2321        crate::shape_id!("test", "S"),
2322        ShapeType::Structure,
2323        &[&PREFIX_MEMBER],
2324    );
2325
2326    struct PrefixHeaderStruct;
2327    impl SerializableStruct for PrefixHeaderStruct {
2328        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2329            s.write_map(&PREFIX_MEMBER, &|s| {
2330                s.write_string(&STRING, "Color")?;
2331                s.write_string(&STRING, "red")?;
2332                s.write_string(&STRING, "Size")?;
2333                s.write_string(&STRING, "large")?;
2334                Ok(())
2335            })
2336        }
2337    }
2338
2339    #[test]
2340    fn http_prefix_headers() {
2341        let request = make_protocol()
2342            .serialize_request(
2343                &PrefixHeaderStruct,
2344                &PREFIX_SCHEMA,
2345                "https://example.com",
2346                &ConfigBag::base(),
2347            )
2348            .unwrap();
2349        assert_eq!(request.headers().get("X-Meta-Color").unwrap(), "red");
2350        assert_eq!(request.headers().get("X-Meta-Size").unwrap(), "large");
2351    }
2352
2353    // -- @httpQueryParams tests --
2354
2355    static QUERY_PARAMS_MEMBER: Schema =
2356        Schema::new_member(crate::shape_id!("test", "S"), ShapeType::Map, "params", 0)
2357            .with_http_query_params();
2358
2359    static QUERY_PARAMS_SCHEMA: Schema = Schema::new_struct(
2360        crate::shape_id!("test", "S"),
2361        ShapeType::Structure,
2362        &[&QUERY_PARAMS_MEMBER],
2363    );
2364
2365    struct QueryParamsStruct;
2366    impl SerializableStruct for QueryParamsStruct {
2367        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2368            s.write_map(&QUERY_PARAMS_MEMBER, &|s| {
2369                s.write_string(&STRING, "page")?;
2370                s.write_string(&STRING, "2")?;
2371                s.write_string(&STRING, "limit")?;
2372                s.write_string(&STRING, "50")?;
2373                Ok(())
2374            })
2375        }
2376    }
2377
2378    #[test]
2379    fn http_query_params() {
2380        let request = make_protocol()
2381            .serialize_request(
2382                &QueryParamsStruct,
2383                &QUERY_PARAMS_SCHEMA,
2384                "https://example.com",
2385                &ConfigBag::base(),
2386            )
2387            .unwrap();
2388        assert_eq!(request.uri(), "https://example.com?page=2&limit=50");
2389    }
2390
2391    // -- Timestamp in header defaults to http-date --
2392
2393    static TS_HEADER_MEMBER: Schema = Schema::new_member(
2394        crate::shape_id!("test", "S"),
2395        ShapeType::Timestamp,
2396        "ifModified",
2397        0,
2398    )
2399    .with_http_header("If-Modified-Since");
2400
2401    static TS_HEADER_SCHEMA: Schema = Schema::new_struct(
2402        crate::shape_id!("test", "S"),
2403        ShapeType::Structure,
2404        &[&TS_HEADER_MEMBER],
2405    );
2406
2407    struct TimestampHeaderStruct;
2408    impl SerializableStruct for TimestampHeaderStruct {
2409        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2410            s.write_timestamp(&TS_HEADER_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
2411        }
2412    }
2413
2414    #[test]
2415    fn timestamp_header_uses_http_date() {
2416        let request = make_protocol()
2417            .serialize_request(
2418                &TimestampHeaderStruct,
2419                &TS_HEADER_SCHEMA,
2420                "https://example.com",
2421                &ConfigBag::base(),
2422            )
2423            .unwrap();
2424        let value = request.headers().get("If-Modified-Since").unwrap();
2425        // http-date format: "Thu, 01 Jan 1970 00:00:00 GMT"
2426        assert!(value.contains("1970"), "expected http-date, got: {value}");
2427    }
2428
2429    // -- Timestamp in query defaults to date-time --
2430
2431    static TS_QUERY_MEMBER: Schema = Schema::new_member(
2432        crate::shape_id!("test", "S"),
2433        ShapeType::Timestamp,
2434        "since",
2435        0,
2436    )
2437    .with_http_query("since");
2438
2439    static TS_QUERY_SCHEMA: Schema = Schema::new_struct(
2440        crate::shape_id!("test", "S"),
2441        ShapeType::Structure,
2442        &[&TS_QUERY_MEMBER],
2443    );
2444
2445    struct TimestampQueryStruct;
2446    impl SerializableStruct for TimestampQueryStruct {
2447        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2448            s.write_timestamp(&TS_QUERY_MEMBER, &aws_smithy_types::DateTime::from_secs(0))
2449        }
2450    }
2451
2452    #[test]
2453    fn timestamp_query_uses_date_time() {
2454        let request = make_protocol()
2455            .serialize_request(
2456                &TimestampQueryStruct,
2457                &TS_QUERY_SCHEMA,
2458                "https://example.com",
2459                &ConfigBag::base(),
2460            )
2461            .unwrap();
2462        assert_eq!(
2463            request.uri(),
2464            "https://example.com?since=1970-01-01T00%3A00%3A00Z"
2465        );
2466    }
2467
2468    // -- Unbound members go to body, bound members do not --
2469
2470    static BOUND_MEMBER: Schema = Schema::new_member(
2471        crate::shape_id!("test", "S"),
2472        ShapeType::String,
2473        "headerVal",
2474        0,
2475    )
2476    .with_http_header("X-Val");
2477    static UNBOUND_MEMBER: Schema = Schema::new_member(
2478        crate::shape_id!("test", "S"),
2479        ShapeType::String,
2480        "bodyVal",
2481        1,
2482    );
2483    static MIXED_SCHEMA: Schema = Schema::new_struct(
2484        crate::shape_id!("test", "S"),
2485        ShapeType::Structure,
2486        &[&BOUND_MEMBER, &UNBOUND_MEMBER],
2487    );
2488
2489    struct MixedStruct;
2490    impl SerializableStruct for MixedStruct {
2491        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2492            s.write_string(&BOUND_MEMBER, "in-header")?;
2493            s.write_string(&UNBOUND_MEMBER, "in-body")
2494        }
2495    }
2496
2497    #[test]
2498    fn bound_members_not_in_body() {
2499        let request = make_protocol()
2500            .serialize_request(
2501                &MixedStruct,
2502                &MIXED_SCHEMA,
2503                "https://example.com",
2504                &ConfigBag::base(),
2505            )
2506            .unwrap();
2507        let body = std::str::from_utf8(request.body().bytes().unwrap()).unwrap();
2508        assert!(
2509            body.contains("in-body"),
2510            "body should contain unbound member"
2511        );
2512        assert!(
2513            !body.contains("in-header"),
2514            "body should NOT contain header-bound member"
2515        );
2516        assert_eq!(request.headers().get("X-Val").unwrap(), "in-header");
2517    }
2518}