AWS SDK

AWS SDK

rev. ec7b2441254af868911fccffe8d8dca83aff0045

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/http_protocol/rpc.rs

@@ -0,1 +0,412 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
//! HTTP RPC protocol for body-only APIs.
           7  +
           8  +
use crate::codec::{Codec, FinishSerializer};
           9  +
use crate::protocol::ClientProtocol;
          10  +
use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
          11  +
use crate::{Schema, ShapeId};
          12  +
use aws_smithy_runtime_api::http::{Request, Response};
          13  +
use aws_smithy_types::body::SdkBody;
          14  +
use aws_smithy_types::config_bag::ConfigBag;
          15  +
          16  +
/// An HTTP protocol for RPC-style APIs that put everything in the body.
          17  +
///
          18  +
/// This protocol ignores HTTP binding traits and serializes the entire input
          19  +
/// into the request body using the provided codec. Used by protocols like
          20  +
/// `awsJson1_0`, `awsJson1_1`, and `rpcv2Cbor`.
          21  +
///
          22  +
/// # Type parameters
          23  +
///
          24  +
/// * `C` — the payload codec (ex: `JsonCodec`, `CborCodec`)
          25  +
#[derive(Debug)]
          26  +
pub struct HttpRpcProtocol<C> {
          27  +
    protocol_id: ShapeId,
          28  +
    codec: C,
          29  +
    content_type: &'static str,
          30  +
}
          31  +
          32  +
impl<C: Codec> HttpRpcProtocol<C> {
          33  +
    /// Creates a new HTTP RPC protocol.
          34  +
    pub fn new(protocol_id: ShapeId, codec: C, content_type: &'static str) -> Self {
          35  +
        Self {
          36  +
            protocol_id,
          37  +
            codec,
          38  +
            content_type,
          39  +
        }
          40  +
    }
          41  +
}
          42  +
          43  +
impl<C> ClientProtocol for HttpRpcProtocol<C>
          44  +
where
          45  +
    C: Codec + Send + Sync + std::fmt::Debug + 'static,
          46  +
    for<'a> C::Deserializer<'a>: ShapeDeserializer,
          47  +
{
          48  +
    fn protocol_id(&self) -> &ShapeId {
          49  +
        &self.protocol_id
          50  +
    }
          51  +
          52  +
    fn serialize_request(
          53  +
        &self,
          54  +
        input: &dyn SerializableStruct,
          55  +
        input_schema: &Schema,
          56  +
        endpoint: &str,
          57  +
        _cfg: &ConfigBag,
          58  +
    ) -> Result<Request, SerdeError> {
          59  +
        let mut serializer = self.codec.create_serializer();
          60  +
        serializer.write_struct(input_schema, input)?;
          61  +
        let body = serializer.finish();
          62  +
          63  +
        let mut request = Request::new(SdkBody::from(body));
          64  +
        request
          65  +
            .set_method("POST")
          66  +
            .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
          67  +
        let uri = if endpoint.is_empty() { "/" } else { endpoint };
          68  +
        request
          69  +
            .set_uri(uri)
          70  +
            .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
          71  +
        request
          72  +
            .headers_mut()
          73  +
            .insert("Content-Type", self.content_type);
          74  +
        if let Some(len) = request.body().content_length() {
          75  +
            request
          76  +
                .headers_mut()
          77  +
                .insert("Content-Length", len.to_string());
          78  +
        }
          79  +
        Ok(request)
          80  +
    }
          81  +
          82  +
    fn deserialize_response<'a>(
          83  +
        &self,
          84  +
        response: &'a Response,
          85  +
        _output_schema: &Schema,
          86  +
        _cfg: &ConfigBag,
          87  +
    ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
          88  +
        let body = response
          89  +
            .body()
          90  +
            .bytes()
          91  +
            .ok_or_else(|| SerdeError::custom("response body is not available as bytes"))?;
          92  +
        Ok(Box::new(self.codec.create_deserializer(body)))
          93  +
    }
          94  +
}
          95  +
          96  +
#[cfg(test)]
          97  +
mod tests {
          98  +
    use super::*;
          99  +
    use crate::serde::SerializableStruct;
         100  +
    use crate::{prelude::*, ShapeType};
         101  +
         102  +
    struct TestSerializer {
         103  +
        output: Vec<u8>,
         104  +
    }
         105  +
         106  +
    impl FinishSerializer for TestSerializer {
         107  +
        fn finish(self) -> Vec<u8> {
         108  +
            self.output
         109  +
        }
         110  +
    }
         111  +
         112  +
    impl ShapeSerializer for TestSerializer {
         113  +
        fn write_struct(
         114  +
            &mut self,
         115  +
            _: &Schema,
         116  +
            value: &dyn SerializableStruct,
         117  +
        ) -> Result<(), SerdeError> {
         118  +
            self.output.push(b'{');
         119  +
            value.serialize_members(self)?;
         120  +
            self.output.push(b'}');
         121  +
            Ok(())
         122  +
        }
         123  +
        fn write_list(
         124  +
            &mut self,
         125  +
            _: &Schema,
         126  +
            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
         127  +
        ) -> Result<(), SerdeError> {
         128  +
            Ok(())
         129  +
        }
         130  +
        fn write_map(
         131  +
            &mut self,
         132  +
            _: &Schema,
         133  +
            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
         134  +
        ) -> Result<(), SerdeError> {
         135  +
            Ok(())
         136  +
        }
         137  +
        fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
         138  +
            Ok(())
         139  +
        }
         140  +
        fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
         141  +
            Ok(())
         142  +
        }
         143  +
        fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
         144  +
            Ok(())
         145  +
        }
         146  +
        fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
         147  +
            Ok(())
         148  +
        }
         149  +
        fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
         150  +
            Ok(())
         151  +
        }
         152  +
        fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
         153  +
            Ok(())
         154  +
        }
         155  +
        fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
         156  +
            Ok(())
         157  +
        }
         158  +
        fn write_big_integer(
         159  +
            &mut self,
         160  +
            _: &Schema,
         161  +
            _: &aws_smithy_types::BigInteger,
         162  +
        ) -> Result<(), SerdeError> {
         163  +
            Ok(())
         164  +
        }
         165  +
        fn write_big_decimal(
         166  +
            &mut self,
         167  +
            _: &Schema,
         168  +
            _: &aws_smithy_types::BigDecimal,
         169  +
        ) -> Result<(), SerdeError> {
         170  +
            Ok(())
         171  +
        }
         172  +
        fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
         173  +
            self.output.extend_from_slice(v.as_bytes());
         174  +
            Ok(())
         175  +
        }
         176  +
        fn write_blob(&mut self, _: &Schema, _: &aws_smithy_types::Blob) -> Result<(), SerdeError> {
         177  +
            Ok(())
         178  +
        }
         179  +
        fn write_timestamp(
         180  +
            &mut self,
         181  +
            _: &Schema,
         182  +
            _: &aws_smithy_types::DateTime,
         183  +
        ) -> Result<(), SerdeError> {
         184  +
            Ok(())
         185  +
        }
         186  +
        fn write_document(
         187  +
            &mut self,
         188  +
            _: &Schema,
         189  +
            _: &aws_smithy_types::Document,
         190  +
        ) -> Result<(), SerdeError> {
         191  +
            Ok(())
         192  +
        }
         193  +
        fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
         194  +
            Ok(())
         195  +
        }
         196  +
    }
         197  +
         198  +
    struct TestDeserializer<'a> {
         199  +
        input: &'a [u8],
         200  +
    }
         201  +
         202  +
    impl ShapeDeserializer for TestDeserializer<'_> {
         203  +
        fn read_struct(
         204  +
            &mut self,
         205  +
            _: &Schema,
         206  +
            _: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         207  +
        ) -> Result<(), SerdeError> {
         208  +
            Ok(())
         209  +
        }
         210  +
        fn read_list(
         211  +
            &mut self,
         212  +
            _: &Schema,
         213  +
            _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         214  +
        ) -> Result<(), SerdeError> {
         215  +
            Ok(())
         216  +
        }
         217  +
        fn read_map(
         218  +
            &mut self,
         219  +
            _: &Schema,
         220  +
            _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         221  +
        ) -> Result<(), SerdeError> {
         222  +
            Ok(())
         223  +
        }
         224  +
        fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
         225  +
            Ok(false)
         226  +
        }
         227  +
        fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
         228  +
            Ok(0)
         229  +
        }
         230  +
        fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
         231  +
            Ok(0)
         232  +
        }
         233  +
        fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
         234  +
            Ok(0)
         235  +
        }
         236  +
        fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
         237  +
            Ok(0)
         238  +
        }
         239  +
        fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
         240  +
            Ok(0.0)
         241  +
        }
         242  +
        fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
         243  +
            Ok(0.0)
         244  +
        }
         245  +
        fn read_big_integer(
         246  +
            &mut self,
         247  +
            _: &Schema,
         248  +
        ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
         249  +
            use std::str::FromStr;
         250  +
            Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
         251  +
        }
         252  +
        fn read_big_decimal(
         253  +
            &mut self,
         254  +
            _: &Schema,
         255  +
        ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
         256  +
            use std::str::FromStr;
         257  +
            Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
         258  +
        }
         259  +
        fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
         260  +
            Ok(String::from_utf8_lossy(self.input).into_owned())
         261  +
        }
         262  +
        fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
         263  +
            Ok(aws_smithy_types::Blob::new(vec![]))
         264  +
        }
         265  +
        fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
         266  +
            Ok(aws_smithy_types::DateTime::from_secs(0))
         267  +
        }
         268  +
        fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
         269  +
            Ok(aws_smithy_types::Document::Null)
         270  +
        }
         271  +
        fn is_null(&self) -> bool {
         272  +
            false
         273  +
        }
         274  +
        fn container_size(&self) -> Option<usize> {
         275  +
            None
         276  +
        }
         277  +
    }
         278  +
         279  +
    #[derive(Debug)]
         280  +
    struct TestCodec;
         281  +
         282  +
    impl Codec for TestCodec {
         283  +
        type Serializer = TestSerializer;
         284  +
        type Deserializer<'a> = TestDeserializer<'a>;
         285  +
        fn create_serializer(&self) -> Self::Serializer {
         286  +
            TestSerializer { output: Vec::new() }
         287  +
        }
         288  +
        fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
         289  +
            TestDeserializer { input }
         290  +
        }
         291  +
    }
         292  +
         293  +
    static TEST_SCHEMA: Schema =
         294  +
        Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
         295  +
         296  +
    struct EmptyStruct;
         297  +
    impl SerializableStruct for EmptyStruct {
         298  +
        fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
         299  +
            Ok(())
         300  +
        }
         301  +
    }
         302  +
         303  +
    static NAME_MEMBER: Schema = Schema::new_member(
         304  +
        crate::shape_id!("test", "TestStruct"),
         305  +
        ShapeType::String,
         306  +
        "name",
         307  +
        0,
         308  +
    );
         309  +
    static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
         310  +
    static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
         311  +
        crate::shape_id!("test", "TestStruct"),
         312  +
        ShapeType::Structure,
         313  +
        MEMBERS,
         314  +
    );
         315  +
         316  +
    struct NameStruct;
         317  +
    impl SerializableStruct for NameStruct {
         318  +
        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
         319  +
            s.write_string(&NAME_MEMBER, "Alice")
         320  +
        }
         321  +
    }
         322  +
         323  +
    #[test]
         324  +
    fn serialize_sets_content_type() {
         325  +
        let protocol = HttpRpcProtocol::new(
         326  +
            crate::shape_id!("test", "rpc"),
         327  +
            TestCodec,
         328  +
            "application/x-amz-json-1.0",
         329  +
        );
         330  +
        let request = protocol
         331  +
            .serialize_request(
         332  +
                &EmptyStruct,
         333  +
                &TEST_SCHEMA,
         334  +
                "https://example.com",
         335  +
                &ConfigBag::base(),
         336  +
            )
         337  +
            .unwrap();
         338  +
        assert_eq!(
         339  +
            request.headers().get("Content-Type").unwrap(),
         340  +
            "application/x-amz-json-1.0"
         341  +
        );
         342  +
    }
         343  +
         344  +
    #[test]
         345  +
    fn serialize_body() {
         346  +
        let protocol = HttpRpcProtocol::new(
         347  +
            crate::shape_id!("test", "rpc"),
         348  +
            TestCodec,
         349  +
            "application/x-amz-json-1.0",
         350  +
        );
         351  +
        let request = protocol
         352  +
            .serialize_request(
         353  +
                &NameStruct,
         354  +
                &STRUCT_WITH_MEMBER,
         355  +
                "https://example.com",
         356  +
                &ConfigBag::base(),
         357  +
            )
         358  +
            .unwrap();
         359  +
        assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
         360  +
    }
         361  +
         362  +
    #[test]
         363  +
    fn deserialize_response() {
         364  +
        let protocol = HttpRpcProtocol::new(
         365  +
            crate::shape_id!("test", "rpc"),
         366  +
            TestCodec,
         367  +
            "application/x-amz-json-1.0",
         368  +
        );
         369  +
        let response = Response::new(
         370  +
            200u16.try_into().unwrap(),
         371  +
            SdkBody::from(r#"{"result":42}"#),
         372  +
        );
         373  +
        let mut deser = protocol
         374  +
            .deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
         375  +
            .unwrap();
         376  +
        assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"result":42}"#);
         377  +
    }
         378  +
         379  +
    #[test]
         380  +
    fn update_endpoint() {
         381  +
        let protocol = HttpRpcProtocol::new(
         382  +
            crate::shape_id!("test", "rpc"),
         383  +
            TestCodec,
         384  +
            "application/x-amz-json-1.0",
         385  +
        );
         386  +
        let mut request = protocol
         387  +
            .serialize_request(
         388  +
                &EmptyStruct,
         389  +
                &TEST_SCHEMA,
         390  +
                "https://old.example.com",
         391  +
                &ConfigBag::base(),
         392  +
            )
         393  +
            .unwrap();
         394  +
        let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
         395  +
            .url("https://new.example.com")
         396  +
            .build();
         397  +
        protocol
         398  +
            .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
         399  +
            .unwrap();
         400  +
        assert_eq!(request.uri(), "https://new.example.com/");
         401  +
    }
         402  +
         403  +
    #[test]
         404  +
    fn protocol_id() {
         405  +
        let protocol = HttpRpcProtocol::new(
         406  +
            crate::shape_id!("aws.protocols", "awsJson1_0"),
         407  +
            TestCodec,
         408  +
            "application/x-amz-json-1.0",
         409  +
        );
         410  +
        assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#awsJson1_0");
         411  +
    }
         412  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/protocol.rs

@@ -0,1 +0,240 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
//! Client protocol trait for protocol-agnostic request serialization and response deserialization.
           7  +
//!
           8  +
//! A [`ClientProtocol`] uses a combination of codecs, serializers, and deserializers to
           9  +
//! serialize requests and deserialize responses for a specific Smithy protocol
          10  +
//! (e.g., AWS JSON 1.0, REST JSON, REST XML, RPCv2 CBOR).
          11  +
//!
          12  +
//! # Implementing a custom protocol
          13  +
//!
          14  +
//! Third parties can create custom protocols and use them with any client without
          15  +
//! modifying a code generator.
          16  +
//!
          17  +
//! ```ignore
          18  +
//! use aws_smithy_schema::protocol::ClientProtocol;
          19  +
//! use aws_smithy_schema::{Schema, ShapeId};
          20  +
//! use aws_smithy_schema::serde::SerializableStruct;
          21  +
//!
          22  +
//! #[derive(Debug)]
          23  +
//! struct MyProtocol {
          24  +
//!     codec: MyJsonCodec,
          25  +
//! }
          26  +
//!
          27  +
//! impl ClientProtocol for MyProtocol {
          28  +
//!     fn protocol_id(&self) -> &ShapeId { &MY_PROTOCOL_ID }
          29  +
//!
          30  +
//!     fn serialize_request(
          31  +
//!         &self,
          32  +
//!         input: &dyn SerializableStruct,
          33  +
//!         input_schema: &Schema,
          34  +
//!         endpoint: &str,
          35  +
//!         cfg: &ConfigBag,
          36  +
//!     ) -> Result<aws_smithy_runtime_api::http::Request, SerdeError> {
          37  +
//!         todo!()
          38  +
//!     }
          39  +
//!
          40  +
//!     fn deserialize_response<'a>(
          41  +
//!         &self,
          42  +
//!         response: &'a aws_smithy_runtime_api::http::Response,
          43  +
//!         output_schema: &Schema,
          44  +
//!         cfg: &ConfigBag,
          45  +
//!     ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
          46  +
//!         todo!()
          47  +
//!     }
          48  +
//! }
          49  +
//! ```
          50  +
          51  +
use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer};
          52  +
use crate::{Schema, ShapeId};
          53  +
use aws_smithy_types::config_bag::ConfigBag;
          54  +
          55  +
// Implementation note: We use concrete aws_smithy_runtime_api::http::{Request, Response} types here
          56  +
// rather than associated types. While the SEP allows for transport-agnostic protocols (e.g., MQTT),
          57  +
// the current SDK uses HTTP throughout. If transport abstraction is needed in the future,
          58  +
// Request/Response could become associated types while maintaining object safety of this trait.
          59  +
          60  +
/// An object-safe client protocol for serializing requests and deserializing responses.
          61  +
///
          62  +
/// Each Smithy protocol (e.g., `aws.protocols#restJson1`, `smithy.protocols#rpcv2Cbor`)
          63  +
/// is represented by an implementation of this trait. Protocols combine one or more
          64  +
/// codecs and serializers to produce protocol-specific request messages and parse
          65  +
/// response messages.
          66  +
///
          67  +
/// # Lifecycle
          68  +
///
          69  +
/// `ClientProtocol` instances are immutable and thread-safe. They are typically created
          70  +
/// once and shared across all requests for a client. Serializers and deserializers are
          71  +
/// created per-request internally.
          72  +
pub trait ClientProtocol: Send + Sync + std::fmt::Debug {
          73  +
    /// Returns the Smithy shape ID of this protocol.
          74  +
    ///
          75  +
    /// This enables runtime protocol selection and differentiation. For example,
          76  +
    /// `aws.protocols#restJson1` or `smithy.protocols#rpcv2Cbor`.
          77  +
    fn protocol_id(&self) -> &ShapeId;
          78  +
          79  +
    /// Serializes an operation input into an HTTP request.
          80  +
    ///
          81  +
    /// # Arguments
          82  +
    ///
          83  +
    /// * `input` - The operation input to serialize.
          84  +
    /// * `input_schema` - Schema describing the operation's input shape.
          85  +
    /// * `endpoint` - The target endpoint URI as a string.
          86  +
    /// * `cfg` - The config bag containing request-scoped configuration
          87  +
    ///   (e.g., service name, operation name for RPC protocols).
          88  +
    fn serialize_request(
          89  +
        &self,
          90  +
        input: &dyn SerializableStruct,
          91  +
        input_schema: &Schema,
          92  +
        endpoint: &str,
          93  +
        cfg: &ConfigBag,
          94  +
    ) -> Result<aws_smithy_runtime_api::http::Request, SerdeError>;
          95  +
          96  +
    /// Deserializes an HTTP response, returning a boxed [`ShapeDeserializer`]
          97  +
    /// for the response body.
          98  +
    ///
          99  +
    /// The returned deserializer reads only body members. For outputs with
         100  +
    /// HTTP-bound members (headers, status code), generated code reads those
         101  +
    /// directly from the response before using this deserializer for body members.
         102  +
    ///
         103  +
    /// # Arguments
         104  +
    ///
         105  +
    /// * `response` - The HTTP response to deserialize.
         106  +
    /// * `output_schema` - Schema describing the operation's output shape.
         107  +
    /// * `cfg` - The config bag containing request-scoped configuration.
         108  +
    fn deserialize_response<'a>(
         109  +
        &self,
         110  +
        response: &'a aws_smithy_runtime_api::http::Response,
         111  +
        output_schema: &Schema,
         112  +
        cfg: &ConfigBag,
         113  +
    ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>;
         114  +
         115  +
    /// Returns whether this protocol uses HTTP binding traits to route members
         116  +
    /// to headers, query strings, URI labels, etc.
         117  +
    ///
         118  +
    /// When `true`, generated code may use [`serialize_body`](Self::serialize_body)
         119  +
    /// for the payload and write HTTP-bound members directly onto the request at
         120  +
    /// compile time, avoiding per-member runtime trait checks.
         121  +
    ///
         122  +
    /// When `false` (the default), generated code calls
         123  +
    /// [`serialize_request`](Self::serialize_request) which gives the protocol
         124  +
    /// full control over where every member is placed.
         125  +
    ///
         126  +
    /// This enables correct behavior when a customer swaps protocols at runtime:
         127  +
    /// an RPC protocol that puts everything in the body will return `false`,
         128  +
    /// so generated code won't hardcode header/query writes.
         129  +
    fn supports_http_bindings(&self) -> bool {
         130  +
        false
         131  +
    }
         132  +
         133  +
    /// Serializes only the body members of an operation input into an HTTP request.
         134  +
    /// For REST protocols, `serialize_request` routes each member through
         135  +
    /// `HttpBindingSerializer` which checks HTTP binding traits at runtime to
         136  +
    /// decide whether a member goes to a header, query param, URI label, or body.
         137  +
    /// For operations with many HTTP-bound members, this per-member routing adds
         138  +
    /// measurable overhead.
         139  +
    ///
         140  +
    /// This method bypasses that routing: it serializes only body members using
         141  +
    /// the codec directly, constructs the URI (with `@http` trait pattern), and
         142  +
    /// sets the HTTP method. Generated code then writes HTTP-bound members
         143  +
    /// (headers, query params, labels) directly onto the returned request.
         144  +
    ///
         145  +
    /// The default implementation delegates to `serialize_request`, which is
         146  +
    /// correct but slower for REST protocols with many HTTP bindings.
         147  +
    fn serialize_body(
         148  +
        &self,
         149  +
        input: &dyn SerializableStruct,
         150  +
        input_schema: &Schema,
         151  +
        endpoint: &str,
         152  +
        cfg: &ConfigBag,
         153  +
    ) -> Result<aws_smithy_runtime_api::http::Request, SerdeError> {
         154  +
        self.serialize_request(input, input_schema, endpoint, cfg)
         155  +
    }
         156  +
         157  +
    /// Updates a previously serialized request with a new endpoint.
         158  +
    ///
         159  +
    /// Required by SEP requirement 7: "ClientProtocol MUST be able to update a
         160  +
    /// previously serialized request with a new endpoint." The orchestrator calls
         161  +
    /// this after endpoint resolution, which happens after `serialize_request`.
         162  +
    ///
         163  +
    /// The default implementation applies the endpoint URL (with prefix if present),
         164  +
    /// sets the request URI, and copies any endpoint headers onto the request.
         165  +
    /// This replicates the existing `apply_endpoint` logic from the orchestrator.
         166  +
    /// Custom implementations should rarely need to override this.
         167  +
    fn update_endpoint(
         168  +
        &self,
         169  +
        request: &mut aws_smithy_runtime_api::http::Request,
         170  +
        endpoint: &aws_smithy_types::endpoint::Endpoint,
         171  +
        cfg: &ConfigBag,
         172  +
    ) -> Result<(), SerdeError> {
         173  +
        use std::borrow::Cow;
         174  +
         175  +
        let endpoint_prefix =
         176  +
            cfg.load::<aws_smithy_runtime_api::client::endpoint::EndpointPrefix>();
         177  +
        let endpoint_url = match endpoint_prefix {
         178  +
            None => Cow::Borrowed(endpoint.url()),
         179  +
            Some(prefix) => {
         180  +
                let parsed: http::Uri = endpoint
         181  +
                    .url()
         182  +
                    .parse()
         183  +
                    .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
         184  +
                let scheme = parsed.scheme_str().unwrap_or_default();
         185  +
                let prefix = prefix.as_str();
         186  +
                let authority = parsed.authority().map(|a| a.as_str()).unwrap_or_default();
         187  +
                let path_and_query = parsed
         188  +
                    .path_and_query()
         189  +
                    .map(|pq| pq.as_str())
         190  +
                    .unwrap_or_default();
         191  +
                Cow::Owned(format!("{scheme}://{prefix}{authority}{path_and_query}"))
         192  +
            }
         193  +
        };
         194  +
         195  +
        request.uri_mut().set_endpoint(&endpoint_url).map_err(|e| {
         196  +
            SerdeError::custom(format!("failed to apply endpoint `{endpoint_url}`: {e}"))
         197  +
        })?;
         198  +
         199  +
        for (header_name, header_values) in endpoint.headers() {
         200  +
            request.headers_mut().remove(header_name);
         201  +
            for value in header_values {
         202  +
                request
         203  +
                    .headers_mut()
         204  +
                    .append(header_name.to_owned(), value.to_owned());
         205  +
            }
         206  +
        }
         207  +
         208  +
        Ok(())
         209  +
    }
         210  +
}
         211  +
         212  +
/// A shared, type-erased client protocol stored in a [`ConfigBag`].
         213  +
///
         214  +
/// This wraps an `Arc<dyn ClientProtocol>` so it can be stored
         215  +
/// and retrieved from the config bag for runtime protocol selection.
         216  +
#[derive(Clone, Debug)]
         217  +
pub struct SharedClientProtocol {
         218  +
    inner: std::sync::Arc<dyn ClientProtocol>,
         219  +
}
         220  +
         221  +
impl SharedClientProtocol {
         222  +
    /// Creates a new shared protocol from any `ClientProtocol` implementation.
         223  +
    pub fn new(protocol: impl ClientProtocol + 'static) -> Self {
         224  +
        Self {
         225  +
            inner: std::sync::Arc::new(protocol),
         226  +
        }
         227  +
    }
         228  +
}
         229  +
         230  +
impl std::ops::Deref for SharedClientProtocol {
         231  +
    type Target = dyn ClientProtocol;
         232  +
         233  +
    fn deref(&self) -> &Self::Target {
         234  +
        &*self.inner
         235  +
    }
         236  +
}
         237  +
         238  +
impl aws_smithy_types::config_bag::Storable for SharedClientProtocol {
         239  +
    type Storer = aws_smithy_types::config_bag::StoreReplace<Self>;
         240  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/serde.rs

@@ -144,144 +247,235 @@
  164    164   
        index: usize,
  165    165   
    }
  166    166   
  167    167   
    impl MockDeserializer {
  168    168   
        fn new(values: Vec<String>) -> Self {
  169    169   
            Self { values, index: 0 }
  170    170   
        }
  171    171   
    }
  172    172   
  173    173   
    impl ShapeDeserializer for MockDeserializer {
  174         -
        fn read_struct<T, F>(
         174  +
        fn read_struct(
  175    175   
            &mut self,
  176    176   
            _schema: &Schema,
  177         -
            state: T,
  178         -
            mut consumer: F,
  179         -
        ) -> Result<T, SerdeError>
  180         -
        where
  181         -
            F: FnMut(T, &Schema, &mut Self) -> Result<T, SerdeError>,
  182         -
        {
         177  +
            consumer: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         178  +
        ) -> Result<(), SerdeError> {
  183    179   
            // Simulate reading 2 members
  184         -
            let state = consumer(state, &STRING, self)?;
  185         -
            let state = consumer(state, &INTEGER, self)?;
  186         -
            Ok(state)
         180  +
            consumer(&STRING, self)?;
         181  +
            consumer(&INTEGER, self)?;
         182  +
            Ok(())
  187    183   
        }
  188    184   
  189         -
        fn read_list<T, F>(
         185  +
        fn read_list(
  190    186   
            &mut self,
  191    187   
            _schema: &Schema,
  192         -
            mut state: T,
  193         -
            mut consumer: F,
  194         -
        ) -> Result<T, SerdeError>
  195         -
        where
  196         -
            F: FnMut(T, &mut Self) -> Result<T, SerdeError>,
  197         -
        {
         188  +
            consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         189  +
        ) -> Result<(), SerdeError> {
  198    190   
            // Simulate reading 3 elements
  199    191   
            for _ in 0..3 {
  200         -
                state = consumer(state, self)?;
         192  +
                consumer(self)?;
  201    193   
            }
  202         -
            Ok(state)
         194  +
            Ok(())
  203    195   
        }
  204    196   
  205         -
        fn read_map<T, F>(
         197  +
        fn read_map(
  206    198   
            &mut self,
  207    199   
            _schema: &Schema,
  208         -
            mut state: T,
  209         -
            mut consumer: F,
  210         -
        ) -> Result<T, SerdeError>
  211         -
        where
  212         -
            F: FnMut(T, String, &mut Self) -> Result<T, SerdeError>,
  213         -
        {
         200  +
            consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
         201  +
        ) -> Result<(), SerdeError> {
  214    202   
            // Simulate reading 2 entries
  215         -
            state = consumer(state, "key1".to_string(), self)?;
  216         -
            state = consumer(state, "key2".to_string(), self)?;
  217         -
            Ok(state)
         203  +
            consumer("key1".to_string(), self)?;
         204  +
            consumer("key2".to_string(), self)?;
         205  +
            Ok(())
  218    206   
        }
  219    207   
  220    208   
        fn read_boolean(&mut self, _schema: &Schema) -> Result<bool, SerdeError> {
  221    209   
            Ok(true)
  222    210   
        }
  223    211   
  224    212   
        fn read_byte(&mut self, _schema: &Schema) -> Result<i8, SerdeError> {
  225    213   
            Ok(42)
  226    214   
        }
  227    215   
@@ -330,318 +406,394 @@
  350    338   
        assert_eq!(deser.container_size(), Some(10));
  351    339   
        assert!(!deser.is_null());
  352    340   
    }
  353    341   
  354    342   
    #[test]
  355    343   
    fn test_deserializer_struct() {
  356    344   
        let mut deser = MockDeserializer::new(vec!["value1".to_string(), "value2".to_string()]);
  357    345   
  358    346   
        let mut fields = Vec::new();
  359    347   
        deser
  360         -
            .read_struct(&STRING, &mut fields, |fields, _member, d| {
         348  +
            .read_struct(&STRING, &mut |_member, d| {
  361    349   
                fields.push(d.read_string(&STRING)?);
  362         -
                Ok(fields)
         350  +
                Ok(())
  363    351   
            })
  364    352   
            .unwrap();
  365    353   
  366    354   
        assert_eq!(fields, vec!["value1", "value2"]);
  367    355   
    }
  368    356   
  369    357   
    #[test]
  370    358   
    fn test_deserializer_list() {
  371    359   
        let mut deser =
  372    360   
            MockDeserializer::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
  373    361   
  374    362   
        let mut elements = Vec::new();
  375    363   
        deser
  376         -
            .read_list(&STRING, &mut elements, |elements, d| {
         364  +
            .read_list(&STRING, &mut |d| {
  377    365   
                elements.push(d.read_string(&STRING)?);
  378         -
                Ok(elements)
         366  +
                Ok(())
  379    367   
            })
  380    368   
            .unwrap();
  381    369   
  382    370   
        assert_eq!(elements, vec!["a", "b", "c"]);
  383    371   
    }
  384    372   
  385    373   
    #[test]
  386    374   
    fn test_deserializer_map() {
  387    375   
        let mut deser = MockDeserializer::new(vec!["val1".to_string(), "val2".to_string()]);
  388    376   
  389    377   
        let mut entries = Vec::new();
  390    378   
        deser
  391         -
            .read_map(&STRING, &mut entries, |entries, key, d| {
         379  +
            .read_map(&STRING, &mut |key, d| {
  392    380   
                let value = d.read_string(&STRING)?;
  393    381   
                entries.push((key, value));
  394         -
                Ok(entries)
         382  +
                Ok(())
  395    383   
            })
  396    384   
            .unwrap();
  397    385   
  398    386   
        assert_eq!(
  399    387   
            entries,
  400    388   
            vec![
  401    389   
                ("key1".to_string(), "val1".to_string()),
  402    390   
                ("key2".to_string(), "val2".to_string())
  403    391   
            ]
  404    392   
        );

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/serde/deserializer.rs

@@ -1,1 +168,234 @@
   20     20   
///
   21     21   
/// # Consumer Pattern
   22     22   
///
   23     23   
/// For aggregate types, the deserializer calls a consumer function for each element/member.
   24     24   
/// The consumer receives mutable state and updates it with each deserialized value.
   25     25   
/// This pattern:
   26     26   
/// - Avoids trait object issues with generic methods
   27     27   
/// - Enables zero-cost abstractions (closures can be inlined)
   28     28   
/// - Allows caller to control deserialization order and state management
   29     29   
/// - Matches the SEP's recommendation for compiled typed languages
          30  +
/// - Uses `&mut dyn ShapeDeserializer` so composite deserializers (e.g., HTTP
          31  +
///   binding + body) can transparently delegate without the consumer knowing
          32  +
///   the concrete deserializer type. This enables runtime protocol swapping.
   30     33   
///
   31     34   
/// # Example
   32     35   
///
   33     36   
/// ```ignore
   34     37   
/// // Deserializing a structure
   35     38   
/// let mut builder = MyStructBuilder::default();
   36     39   
/// deserializer.read_struct(
   37     40   
///     &MY_STRUCT_SCHEMA,
   38         -
///     builder,
   39         -
///     |mut builder, member, deser| {
          41  +
///     &mut |member, deser| {
   40     42   
///         match member.member_index() {
   41         -
///             0 => builder.field1 = Some(deser.read_string(member)?),
   42         -
///             1 => builder.field2 = Some(deser.read_i32(member)?),
          43  +
///             Some(0) => builder.field1 = Some(deser.read_string(member)?),
          44  +
///             Some(1) => builder.field2 = Some(deser.read_integer(member)?),
   43     45   
///             _ => {}
   44     46   
///         }
   45         -
///         Ok(builder)
          47  +
///         Ok(())
   46     48   
///     },
   47     49   
/// )?;
   48     50   
/// let my_struct = builder.build();
   49     51   
/// ```
          52  +
/// Maximum pre-allocation size for containers, used to prevent denial-of-service
          53  +
/// from untrusted payloads claiming excessively large sizes.
          54  +
#[allow(dead_code)]
          55  +
pub const MAX_CONTAINER_PREALLOC: usize = 10_000;
          56  +
          57  +
/// Caps a raw container size at [`MAX_CONTAINER_PREALLOC`].
          58  +
///
          59  +
/// Implementations of [`ShapeDeserializer::container_size`] SHOULD use this
          60  +
/// when returning a size derived from untrusted input (e.g., a CBOR length header).
          61  +
#[allow(dead_code)]
          62  +
pub fn capped_container_size(raw: usize) -> usize {
          63  +
    raw.min(MAX_CONTAINER_PREALLOC)
          64  +
}
          65  +
   50     66   
pub trait ShapeDeserializer {
   51     67   
    /// Reads a structure from the deserializer.
   52     68   
    ///
   53         -
    /// The structure deserialization is driven by a consumer callback that is called
   54         -
    /// for each member. The consumer receives the current state, the member schema,
   55         -
    /// and the deserializer, and returns the updated state.
   56         -
    ///
   57         -
    /// # Arguments
   58         -
    ///
   59         -
    /// * `schema` - The schema of the structure being deserialized
   60         -
    /// * `state` - Initial state (typically a builder)
   61         -
    /// * `consumer` - Callback invoked for each member with (state, member_schema, deserializer)
   62         -
    ///
   63         -
    /// # Returns
   64         -
    ///
   65         -
    /// The final state after processing all members
   66         -
    fn read_struct<T, F>(
          69  +
    /// The consumer is called for each member with the member schema and a
          70  +
    /// `&mut dyn ShapeDeserializer` to read the member value. Using `dyn`
          71  +
    /// allows composite deserializers (e.g., HTTP binding + body) to
          72  +
    /// transparently delegate without the consumer knowing the concrete type.
          73  +
    fn read_struct(
   67     74   
        &mut self,
   68     75   
        schema: &Schema,
   69         -
        state: T,
   70         -
        consumer: F,
   71         -
    ) -> Result<T, SerdeError>
   72         -
    where
   73         -
        F: FnMut(T, &Schema, &mut Self) -> Result<T, SerdeError>;
          76  +
        state: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
          77  +
    ) -> Result<(), SerdeError>;
   74     78   
   75     79   
    /// Reads a list from the deserializer.
   76     80   
    ///
   77         -
    /// The list deserialization is driven by a consumer callback that is called
   78         -
    /// for each element. The consumer receives the current state and the deserializer,
   79         -
    /// and returns the updated state.
   80         -
    ///
   81         -
    /// # Arguments
   82         -
    ///
   83         -
    /// * `schema` - The schema of the list being deserialized
   84         -
    /// * `state` - Initial state (typically a Vec or collection)
   85         -
    /// * `consumer` - Callback invoked for each element with (state, deserializer)
   86         -
    ///
   87         -
    /// # Returns
   88         -
    ///
   89         -
    /// The final state after processing all elements
   90         -
    fn read_list<T, F>(&mut self, schema: &Schema, state: T, consumer: F) -> Result<T, SerdeError>
   91         -
    where
   92         -
        F: FnMut(T, &mut Self) -> Result<T, SerdeError>;
          81  +
    /// The consumer is called for each element with a `&mut dyn ShapeDeserializer`.
          82  +
    fn read_list(
          83  +
        &mut self,
          84  +
        schema: &Schema,
          85  +
        state: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
          86  +
    ) -> Result<(), SerdeError>;
   93     87   
   94     88   
    /// Reads a map from the deserializer.
   95     89   
    ///
   96         -
    /// The map deserialization is driven by a consumer callback that is called
   97         -
    /// for each entry. The consumer receives the current state, the key, and the
   98         -
    /// deserializer, and returns the updated state.
   99         -
    ///
  100         -
    /// # Arguments
  101         -
    ///
  102         -
    /// * `schema` - The schema of the map being deserialized
  103         -
    /// * `state` - Initial state (typically a HashMap or collection)
  104         -
    /// * `consumer` - Callback invoked for each entry with (state, key, deserializer)
  105         -
    ///
  106         -
    /// # Returns
  107         -
    ///
  108         -
    /// The final state after processing all entries
  109         -
    fn read_map<T, F>(&mut self, schema: &Schema, state: T, consumer: F) -> Result<T, SerdeError>
  110         -
    where
  111         -
        F: FnMut(T, String, &mut Self) -> Result<T, SerdeError>;
          90  +
    /// The consumer is called for each entry with the key and a `&mut dyn ShapeDeserializer`.
          91  +
    fn read_map(
          92  +
        &mut self,
          93  +
        schema: &Schema,
          94  +
        state: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
          95  +
    ) -> Result<(), SerdeError>;
  112     96   
  113     97   
    /// Reads a boolean value.
  114     98   
    fn read_boolean(&mut self, schema: &Schema) -> Result<bool, SerdeError>;
  115     99   
  116    100   
    /// Reads a byte (i8) value.
  117    101   
    fn read_byte(&mut self, schema: &Schema) -> Result<i8, SerdeError>;
  118    102   
  119    103   
    /// Reads a short (i16) value.
  120    104   
    fn read_short(&mut self, schema: &Schema) -> Result<i16, SerdeError>;
  121    105   
  122    106   
    /// Reads an integer (i32) value.
  123    107   
    fn read_integer(&mut self, schema: &Schema) -> Result<i32, SerdeError>;
  124    108   
  125    109   
    /// Reads a long (i64) value.
  126    110   
    fn read_long(&mut self, schema: &Schema) -> Result<i64, SerdeError>;
  127    111   
  128    112   
    /// Reads a float (f32) value.
  129    113   
    fn read_float(&mut self, schema: &Schema) -> Result<f32, SerdeError>;
  130    114   
  131    115   
    /// Reads a double (f64) value.
  132    116   
    fn read_double(&mut self, schema: &Schema) -> Result<f64, SerdeError>;
  133    117   
  134    118   
    /// Reads a big integer value.
  135    119   
    fn read_big_integer(&mut self, schema: &Schema) -> Result<BigInteger, SerdeError>;
  136    120   
  137    121   
    /// Reads a big decimal value.
  138    122   
    fn read_big_decimal(&mut self, schema: &Schema) -> Result<BigDecimal, SerdeError>;
  139    123   
  140    124   
    /// Reads a string value.
  141    125   
    fn read_string(&mut self, schema: &Schema) -> Result<String, SerdeError>;
  142    126   
  143    127   
    /// Reads a blob (byte array) value.
  144    128   
    fn read_blob(&mut self, schema: &Schema) -> Result<Blob, SerdeError>;
  145    129   
  146    130   
    /// Reads a timestamp value.
  147    131   
    fn read_timestamp(&mut self, schema: &Schema) -> Result<DateTime, SerdeError>;
  148    132   
  149    133   
    /// Reads a document value.
  150    134   
    fn read_document(&mut self, schema: &Schema) -> Result<Document, SerdeError>;
  151    135   
  152    136   
    /// Checks if the current value is null.
  153    137   
    ///
  154    138   
    /// This is used for sparse collections where null values are significant.
  155    139   
    fn is_null(&self) -> bool;
  156    140   
         141  +
    /// Consumes a null value, advancing past it.
         142  +
    ///
         143  +
    /// This should be called after `is_null()` returns true to advance the
         144  +
    /// deserializer past the null token.
         145  +
    fn read_null(&mut self) -> Result<(), SerdeError> {
         146  +
        Ok(())
         147  +
    }
         148  +
  157    149   
    /// Returns the size of the current container if known.
  158    150   
    ///
  159    151   
    /// This is an optimization hint that allows pre-allocating collections
  160    152   
    /// with the correct capacity. Returns `None` if the size is unknown or
  161    153   
    /// not applicable.
  162    154   
    ///
  163         -
    /// Implementations MUST cap the returned value at a reasonable maximum
  164         -
    /// (e.g. 10,000) to prevent denial-of-service from untrusted payloads
  165         -
    /// that claim excessively large container sizes (e.g. a CBOR header
  166         -
    /// declaring billions of elements).
         155  +
    /// Implementations SHOULD cap the returned value at a reasonable maximum
         156  +
    /// (e.g., 10,000) to prevent denial-of-service from untrusted payloads
         157  +
    /// that claim excessively large container sizes (e.g., a CBOR header
         158  +
    /// declaring billions of elements). Use [`capped_container_size`] to apply
         159  +
    /// a standard cap.
  167    160   
    fn container_size(&self) -> Option<usize>;
         161  +
         162  +
    // --- Collection helper methods ---
         163  +
    //
         164  +
    // These methods are not part of the Serialization and Schema Decoupling SEP.
         165  +
    // They exist as a performance optimization for common collection patterns
         166  +
    // (e.g. `List<String>`, `Map<String, String>`) that appear frequently in
         167  +
    // AWS service models. Without these, generated code must emit an inline
         168  +
    // closure calling `read_list`/`read_map` with per-element `read_string`
         169  +
    // calls — roughly 6-8 lines of boilerplate per collection field. These
         170  +
    // helpers replace that with a single method call, reducing generated code
         171  +
    // size (e.g. -43% for DynamoDB deserialize bodies).
         172  +
    //
         173  +
    // Additionally, the default implementations call through
         174  +
    // `&mut dyn ShapeDeserializer`, which introduces vtable dispatch on every
         175  +
    // element. Codec implementations (e.g. `JsonDeserializer`) can override
         176  +
    // these to call their concrete `read_string`/`read_integer`/etc. methods
         177  +
    // directly, eliminating the per-element dynamic dispatch overhead.
         178  +
         179  +
    /// Reads a list of strings.
         180  +
    fn read_string_list(&mut self, schema: &Schema) -> Result<Vec<String>, SerdeError> {
         181  +
        let mut out = Vec::new();
         182  +
        self.read_list(schema, &mut |deser| {
         183  +
            out.push(deser.read_string(schema)?);
         184  +
            Ok(())
         185  +
        })?;
         186  +
        Ok(out)
         187  +
    }
         188  +
         189  +
    /// Reads a list of blobs.
         190  +
    fn read_blob_list(
         191  +
        &mut self,
         192  +
        schema: &Schema,
         193  +
    ) -> Result<Vec<aws_smithy_types::Blob>, SerdeError> {
         194  +
        let mut out = Vec::new();
         195  +
        self.read_list(schema, &mut |deser| {
         196  +
            out.push(deser.read_blob(schema)?);
         197  +
            Ok(())
         198  +
        })?;
         199  +
        Ok(out)
         200  +
    }
         201  +
         202  +
    /// Reads a list of integers.
         203  +
    fn read_integer_list(&mut self, schema: &Schema) -> Result<Vec<i32>, SerdeError> {
         204  +
        let mut out = Vec::new();
         205  +
        self.read_list(schema, &mut |deser| {
         206  +
            out.push(deser.read_integer(schema)?);
         207  +
            Ok(())
         208  +
        })?;
         209  +
        Ok(out)
         210  +
    }
         211  +
         212  +
    /// Reads a list of longs.
         213  +
    fn read_long_list(&mut self, schema: &Schema) -> Result<Vec<i64>, SerdeError> {
         214  +
        let mut out = Vec::new();
         215  +
        self.read_list(schema, &mut |deser| {
         216  +
            out.push(deser.read_long(schema)?);
         217  +
            Ok(())
         218  +
        })?;
         219  +
        Ok(out)
         220  +
    }
         221  +
         222  +
    /// Reads a map with string values.
         223  +
    fn read_string_string_map(
         224  +
        &mut self,
         225  +
        schema: &Schema,
         226  +
    ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
         227  +
        let mut out = std::collections::HashMap::new();
         228  +
        self.read_map(schema, &mut |key, deser| {
         229  +
            out.insert(key, deser.read_string(schema)?);
         230  +
            Ok(())
         231  +
        })?;
         232  +
        Ok(out)
         233  +
    }
  168    234   
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/serde/serializer.rs

@@ -80,80 +138,212 @@
  100    100   
    fn write_blob(&mut self, schema: &Schema, value: &Blob) -> Result<(), SerdeError>;
  101    101   
  102    102   
    /// Writes a timestamp value.
  103    103   
    fn write_timestamp(&mut self, schema: &Schema, value: &DateTime) -> Result<(), SerdeError>;
  104    104   
  105    105   
    /// Writes a document value.
  106    106   
    fn write_document(&mut self, schema: &Schema, value: &Document) -> Result<(), SerdeError>;
  107    107   
  108    108   
    /// Writes a null value (for sparse collections).
  109    109   
    fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError>;
         110  +
         111  +
    // --- Collection helper methods ---
         112  +
    //
         113  +
    // These methods are not part of the Serialization and Schema Decoupling SEP.
         114  +
    // They exist as a performance optimization for common collection patterns
         115  +
    // (e.g. `List<String>`, `Map<String, String>`) that appear frequently in
         116  +
    // AWS service models. Without these, generated code must emit an inline
         117  +
    // closure calling `write_list`/`write_map` with per-element `write_string`
         118  +
    // calls — roughly 6-8 lines of boilerplate per collection field. These
         119  +
    // helpers replace that with a single method call, reducing generated code
         120  +
    // size.
         121  +
    //
         122  +
    // Additionally, the default implementations call through the trait
         123  +
    // interface per element. Codec implementations can override these to write
         124  +
    // elements directly, avoiding per-element dynamic dispatch.
         125  +
         126  +
    /// Writes a list of strings.
         127  +
    fn write_string_list(&mut self, schema: &Schema, values: &[String]) -> Result<(), SerdeError> {
         128  +
        self.write_list(schema, &|ser| {
         129  +
            for item in values {
         130  +
                ser.write_string(&crate::prelude::STRING, item)?;
         131  +
            }
         132  +
            Ok(())
         133  +
        })
         134  +
    }
         135  +
         136  +
    /// Writes a list of blobs.
         137  +
    fn write_blob_list(
         138  +
        &mut self,
         139  +
        schema: &Schema,
         140  +
        values: &[aws_smithy_types::Blob],
         141  +
    ) -> Result<(), SerdeError> {
         142  +
        self.write_list(schema, &|ser| {
         143  +
            for item in values {
         144  +
                ser.write_blob(&crate::prelude::BLOB, item)?;
         145  +
            }
         146  +
            Ok(())
         147  +
        })
         148  +
    }
         149  +
         150  +
    /// Writes a list of integers.
         151  +
    fn write_integer_list(&mut self, schema: &Schema, values: &[i32]) -> Result<(), SerdeError> {
         152  +
        self.write_list(schema, &|ser| {
         153  +
            for item in values {
         154  +
                ser.write_integer(&crate::prelude::INTEGER, *item)?;
         155  +
            }
         156  +
            Ok(())
         157  +
        })
         158  +
    }
         159  +
         160  +
    /// Writes a list of longs.
         161  +
    fn write_long_list(&mut self, schema: &Schema, values: &[i64]) -> Result<(), SerdeError> {
         162  +
        self.write_list(schema, &|ser| {
         163  +
            for item in values {
         164  +
                ser.write_long(&crate::prelude::LONG, *item)?;
         165  +
            }
         166  +
            Ok(())
         167  +
        })
         168  +
    }
         169  +
         170  +
    /// Writes a map with string keys and string values.
         171  +
    fn write_string_string_map(
         172  +
        &mut self,
         173  +
        schema: &Schema,
         174  +
        values: &std::collections::HashMap<String, String>,
         175  +
    ) -> Result<(), SerdeError> {
         176  +
        self.write_map(schema, &|ser| {
         177  +
            for (key, value) in values {
         178  +
                ser.write_string(&crate::prelude::STRING, key)?;
         179  +
                ser.write_string(&crate::prelude::STRING, value)?;
         180  +
            }
         181  +
            Ok(())
         182  +
        })
         183  +
    }
  110    184   
}
  111    185   
  112    186   
/// Trait for structures that can be serialized via a schema.
  113    187   
///
  114    188   
/// Implemented by generated structure types. Because `ShapeSerializer` is object-safe,
  115    189   
/// each struct gets one compiled `serialize_members()` that works with any serializer
  116    190   
/// through dynamic dispatch.
  117    191   
///
  118    192   
/// # Example
  119    193   
///

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-schema/src/schema/traits.rs

@@ -165,165 +224,272 @@
  185    185   
    HttpQueryParamsTrait,
  186    186   
    "smithy.api", "httpQueryParams"
  187    187   
);
  188    188   
  189    189   
annotation_trait!(
  190    190   
    /// The `@httpResponseCode` trait — binds a member to the HTTP status code.
  191    191   
    HttpResponseCodeTrait,
  192    192   
    "smithy.api", "httpResponseCode"
  193    193   
);
  194    194   
         195  +
/// The `@http` trait — defines the HTTP method, URI pattern, and status code for an operation.
         196  +
///
         197  +
/// This is an operation-level trait that is included on the input schema for
         198  +
/// convenience, so that the protocol serializer can construct the correct
         199  +
/// request without needing a separate operation schema.
         200  +
///
         201  +
/// The URI pattern may contain `{label}` placeholders that are substituted
         202  +
/// at serialization time with percent-encoded values from `@httpLabel` members.
         203  +
#[derive(Debug, Clone)]
         204  +
pub struct HttpTrait {
         205  +
    method: &'static str,
         206  +
    uri: &'static str,
         207  +
    code: u16,
         208  +
}
         209  +
         210  +
impl HttpTrait {
         211  +
    /// Creates a new `HttpTrait`. If `code` is `None`, defaults to `200`.
         212  +
    pub const fn new(method: &'static str, uri: &'static str, code: Option<u16>) -> Self {
         213  +
        Self {
         214  +
            method,
         215  +
            uri,
         216  +
            code: match code {
         217  +
                Some(c) => c,
         218  +
                None => 200,
         219  +
            },
         220  +
        }
         221  +
    }
         222  +
         223  +
    /// The HTTP method (e.g., `"GET"`, `"POST"`, `"PUT"`).
         224  +
    pub fn method(&self) -> &str {
         225  +
        self.method
         226  +
    }
         227  +
         228  +
    /// The URI pattern (e.g., `"/resource/{id}"`).
         229  +
    ///
         230  +
    /// May contain `{label}` placeholders that correspond to `@httpLabel` members.
         231  +
    /// The protocol serializer substitutes these with percent-encoded values
         232  +
    /// collected during member serialization.
         233  +
    pub fn uri(&self) -> &str {
         234  +
        self.uri
         235  +
    }
         236  +
         237  +
    /// The HTTP status code for a successful response. Defaults to `200`.
         238  +
    pub fn code(&self) -> u16 {
         239  +
        self.code
         240  +
    }
         241  +
}
         242  +
  195    243   
// --- Streaming traits ---
  196    244   
  197    245   
annotation_trait!(
  198    246   
    /// The `@streaming` trait — marks a blob or union as streaming.
  199    247   
    StreamingTrait,
  200    248   
    "smithy.api", "streaming"
  201    249   
);
  202    250   
  203    251   
annotation_trait!(
  204    252   
    /// The `@eventHeader` trait — binds a member to an event stream header.