Skip to content

Schema based protocols#4585

Merged
landonxjames merged 92 commits intofeature/schemafrom
landonxjames/schema-protocol
Apr 21, 2026
Merged

Schema based protocols#4585
landonxjames merged 92 commits intofeature/schemafrom
landonxjames/schema-protocol

Conversation

@landonxjames
Copy link
Copy Markdown
Contributor

@landonxjames landonxjames commented Mar 31, 2026

Schema-Based Protocol Serialization and Deserialization

This PR implements the runtime protocol layer from the
Serialization and Schema Decoupling SEP,
building on the schema and codec foundations already merged to feature/schema.
It enables protocol-agnostic request serialization and response deserialization
driven by runtime Schema objects and a ClientProtocol trait, replacing the
per-shape/per-protocol code generation with a single generic path for
restJson1, awsJson1_0, and awsJson1_1 protocols.

What's implemented

Runtime crates (Rust)

  • ClientProtocol trait (aws-smithy-schema/src/schema/protocol.rs) — the
    top-level object-safe trait for serializing requests and deserializing
    responses. Wraps as SharedClientProtocol (via Arc<dyn ClientProtocol>) for
    storage in ConfigBag.
  • HttpBindingProtocol<C>
    (aws-smithy-schema/src/schema/http_protocol/binding.rs) — REST-style
    protocol implementation that routes members to HTTP headers, query params, URI
    labels, or body based on HTTP binding traits (@httpHeader, @httpQuery,
    @httpLabel, @httpPayload, @httpPrefixHeaders, @httpQueryParams).
    Includes HttpBindingSerializer that intercepts ShapeSerializer calls and
    an HttpBindingDeserializer for responses.
  • HttpRpcProtocol<C> (aws-smithy-schema/src/schema/http_protocol/rpc.rs) —
    RPC-style protocol that serializes everything to the body, ignoring HTTP
    bindings.
  • AwsRestJsonProtocol (aws-smithy-json/src/protocol/aws_rest_json_1.rs) —
    thin wrapper constructing HttpBindingProtocol<JsonCodec> with
    use_json_name: true and epoch-seconds default timestamps.
  • AwsJsonRpcProtocol (aws-smithy-json/src/protocol/aws_json_rpc.rs) —
    unified type for both 1.0 and 1.1, wrapping HttpRpcProtocol<JsonCodec> with
    use_json_name: false. Sets X-Amz-Target header from Metadata in
    ConfigBag.
  • FinishSerializer trait — separated from ShapeSerializer to preserve object
    safety, since finish(self) -> Vec<u8> consumes self.
  • ShapeDeserializer made object-safe — read_struct/read_list/read_map
    changed from generic F: FnMut(T, ...) to &mut dyn FnMut(...). This enables
    composite deserializers (HTTP binding + body) to transparently delegate
    without the consumer knowing the concrete type, which is essential for runtime
    protocol swapping.
  • Collection helper methods on ShapeSerializer/ShapeDeserializer
    write_string_list, read_string_list, read_blob_list,
    read_integer_list, read_long_list, read_string_string_map, etc. These
    reduce generated code size by ~43% for collection-heavy models (DynamoDB:
    19,235 → 10,953 deserialize body lines).
  • deserialize_with_response — generated method on output types that reads
    HTTP-bound members (headers, status code, prefix headers) directly from the
    response, then delegates body members to the ShapeDeserializer. This avoids
    the overhead of checking HTTP binding traits at runtime for every body member.
  • serialize_body and supports_http_bindings on ClientProtocol — for REST
    protocols that return supports_http_bindings() == true, serialize_body()
    serializes only body members via the codec, returning a request that generated
    code then populates with HTTP-bound members directly. Protocols that return
    false (the default) cause generated code to fall back to
    serialize_request(), giving the protocol full control. This enables correct
    runtime protocol swapping.
  • Protocol selection in SdkConfig and per-service config — customers can
    override the default protocol at runtime via
    client.config().protocol(MyCustomProtocol). The service runtime plugin only
    stores the default protocol if the customer hasn't set one, ensuring the
    override takes effect.

Code generation (Kotlin)

  • SchemaDecorator expanded — stores SharedClientProtocol in both the service
    config layer and each operation's runtime plugin config layer. Adds
    protocol() getter/setter to service config builders.
  • SchemaSerdeAllowlist — controls which protocols use the schema-based path
    exclusively (currently restJson1, awsJson1_0, awsJson1_1). Services on
    the allowlist generate no legacy protocol_serde code.
  • RequestSerializerGenerator — for allowlisted protocols, generates a
    SerializeRequest impl that loads SharedClientProtocol from ConfigBag and
    calls protocol.serialize_request() or protocol.serialize_body(). For REST
    protocols with HTTP bindings, generates direct header/query/label writes at
    compile time instead of routing through HttpBindingSerializer at runtime.
  • ResponseDeserializerGenerator — generates schema-based deserialization that
    calls protocol.deserialize_response() then
    Output::deserialize_with_response(). Error deserialization uses the protocol
    to deserialize error bodies, with error code dispatch matching the legacy
    path.
  • SchemaGenerator — extended with union serialization/deserialization,
    synthetic member support (e.g., _request_id from x-amzn-requestid header),
    streaming member handling, @httpPayload blob/string support, and
    deserialize_with_response generation.
  • OperationGenerator — adds INPUT_SCHEMA and OUTPUT_SCHEMA constants to
    each operation type.
  • DeserializeResponse trait — deserialize_nonstreaming now takes
    &ConfigBag so the deserializer can load SharedClientProtocol.

JSON deserializer optimizations

  • Removed container_size pre-scan — was scanning the entire JSON token stream
    to count elements before deserializing, effectively parsing data twice.
  • Direct byte parsing — read_string, read_boolean, read_integer,
    read_float, read_struct key parsing, and read_map key parsing all
    replaced json_token_iter() iterator construction with direct byte-level
    scanning.
  • parse_key returns Cow<'a, str> — avoids heap allocation for JSON keys
    without escape sequences (the common case).
  • Null skipping in read_structnull values are skipped before calling the
    consumer, eliminating the need for per-field is_null() checks in generated
    code.
  • JsonFieldMapper made non-locking — removed LazyLock overhead from the
    @jsonName reverse mapping.

How this differs from the SEP

The SEP is written with Java idioms in mind. Key Rust-specific adaptations:

  1. Object-safe ClientProtocol — the SEP uses associated types for
    Request/Response. We hardcode
    aws_smithy_runtime_api::http::{Request, Response} because HTTP assumptions
    are deeply baked into the SDK. The trait is dyn-compatible, stored as a
    SharedClientProtocol containing an Arc<dyn ClientProtocol> in
    ConfigBag.

  2. Object-safe ShapeDeserializer — the SEP's consumer pattern uses
    generics (<T, F: FnMut(T, ...)>). We use &mut dyn FnMut(...) with
    external state capture instead, trading some monomorphization for the ability
    to compose deserializers at runtime (HTTP binding wrapper → body
    deserializer).

  3. FinishSerializer separated from ShapeSerializerfinish(self)
    consumes the serializer, which is incompatible with object safety. The
    Codec trait requires Serializer: ShapeSerializer + FinishSerializer, but
    protocol code calls finish() on the concrete type after using
    &mut dyn ShapeSerializer for member writes.

  4. Hybrid codegen for REST HTTP bindings — the SEP envisions the protocol
    handling all HTTP binding routing at runtime. For performance, when the
    protocol reports supports_http_bindings() == true, generated code uses
    serialize_body() for the payload and writes HTTP-bound members (headers,
    query params, URI labels) directly at compile time, avoiding per-member
    runtime trait checks. When a customer swaps in a protocol that returns
    false (e.g., an RPC protocol), generated code falls back to
    serialize_request(), giving the protocol full control over member
    placement. This preserves the performance optimization for the default
    protocol while maintaining correct runtime protocol swapping.

  5. deserialize_with_response instead of HttpBindingDeserializer — rather
    than wrapping the body deserializer in an HTTP binding deserializer that
    checks traits on every member, we generate a method that reads HTTP-bound
    members directly from the response, then delegates body-only deserialization
    to the codec. This eliminates the per-member overhead for responses.

  6. No ClientTransport abstraction — the SEP defines a ClientTransport
    interface. We skip this since HTTP is deeply embedded in the existing
    orchestrator.

  7. update_endpoint takes &Endpoint — the SEP takes a URI string. We take
    the full Endpoint object (which includes headers) and handle
    EndpointPrefix from the ConfigBag, matching the existing SRA endpoint
    resolution flow.

Benchmarks

Benchmarks were run on macOS arm64 with rustc 1.91.1. Two benchmark suites were
used:

DynamoDB-specific benchmarks (Criterion, aws-sdk-dynamodb crate):

Metric main schema-protocol Delta
Serialization (PutItem) 3.214 µs 3.057 µs -5%
Deserialization (Query) 9.378 µs 8.019 µs -14%

Standardized cross-service benchmarks (custom harness,
SerdeBenchmarkTestGenerator):

Generated from Smithy protocol test models for awsJson1_0 and restJson1,
covering S/M/L payloads with strings, binary, nested maps, shallow maps, and
mixed types. Latest results:

Category Delta vs main
Serialization total -19.8%
Deserialization total -20.9%

Per-benchmark highlights:

Benchmark main schema Delta
awsJson1_0 GetItemOutput_L (deser) 59,774 ns 42,530 ns -28.8%
awsJson1_0 GetItemOutput_M (deser) 8,270 ns 6,004 ns -27.4%
awsJson1_0 GetItemOutputBinary_L (deser) 59,842 ns 54,400 ns -9.1%
restJson1 GetObject_L (deser) 18,776 ns 12,325 ns -34.4%
restJson1 GetObject_M (deser) 3,685 ns 2,557 ns -30.6%
restJson1 CopyObjectOutput_M (deser) 1,519 ns 2,898 ns +90.8%
restJson1 CopyObjectRequest_M (ser) 15,640 ns 7,408 ns -52.6%
awsJson1_0 PutItemRequest_Nested_L (ser) 3,642 ns 3,591 ns -1.4%

Key observations:

  • awsJson1_0 deserialization is 27-29% faster for non-binary payloads due to
    direct byte parsing optimizations in the JSON deserializer.
  • Binary data deserialization is 9% faster — base64 decode overhead through
    the trait interface is offset by the JSON parsing improvements.
  • restJson1 deserialization is 31-44% faster for most operations because the
    schema path avoids the legacy per-shape deserialization functions.
  • restJson1 CopyObjectOutput_M is the outlier at +91% — this operation has
    many HTTP-bound response members, and the deserialize_with_response path has
    overhead from header parsing that the legacy codegen avoided by inlining.
  • Serialization is 20% faster overall — the previous per-operation protocol
    construction overhead has been eliminated by creating the protocol once at
    service config time.

Binary size (aws-sdk-dynamodb):

Metric main schema Delta
Release rlib 27M 30M +3M (+11%)
Debug rlib 109M 113M +4M (+4%)

The size increase comes from schema constants (274 statics), serialize_members
methods (263), and deserialize methods (320) that now live on the types
themselves. The legacy protocol_serde module is eliminated (reduced to 8
lines).

Build time (aws-sdk-dynamodb, clean builds):

Metric main schema Delta
Debug 1m3.1s 0m55.4s -12%
Release 1m18.0s 1m11.6s -8%

Runtime protocol swapping integration tests
(aws/sdk/integration-tests/dynamodb/tests/protocol-swap.rs):

Three tests verify that protocols can be swapped at runtime via the
.protocol() config setter on a DynamoDB client:

  • default_protocol_serializes_correctly — baseline: the default awsJson1_0
    protocol produces the correct Content-Type, X-Amz-Target, and JSON body.
  • swapped_protocol_changes_content_type — swapping to awsJson1_1 changes the
    Content-Type to application/x-amz-json-1.1 while preserving the same
    request body.
  • swap_to_rest_json_protocol — swapping to restJson1 (a fundamentally
    different protocol class) produces Content-Type: application/json, no
    X-Amz-Target header, and a correctly serialized JSON body. This exercises
    the supports_http_bindings() fallback path.

Breaking changes

  • DeserializeResponse::deserialize_nonstreaming now takes &ConfigBag as a
    second parameter.
  • SharedClientProtocol must be present in the ConfigBag for schema-based
    protocols to function. This is automatically set by generated service runtime
    plugins.

What's not yet implemented

  • XML codec and AwsRestXmlProtocol
  • CBOR codec and RpcV2CborProtocol
  • AWS Query / EC2 Query protocols
  • Response deserialization for streaming operations via the schema path
    (currently falls back to legacy error parsing)
  • Full protocol compliance test validation against the schema path

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Also implemented container_size to pre-alloc containers
Previously they were holding a Vec<u8> which required an extra alloc
Also fix bug with JSON deserialization where blobs weren't base64 decoded
Discovered quite a few bugs when applying to real SDK models. Will fix separately
@github-actions

This comment was marked as resolved.

@landonxjames landonxjames marked this pull request as ready for review April 3, 2026 16:40
@landonxjames landonxjames requested review from a team as code owners April 3, 2026 16:40
Comment thread rust-runtime/aws-smithy-runtime-api/src/client/ser_de.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs Outdated
Comment thread rust-runtime/aws-smithy-json/src/codec/deserializer.rs
Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 6, 2026

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link
Copy Markdown
Contributor

@ysaito1001 ysaito1001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Leaving intermediate comments. Circling back for another round of review.

Comment on lines +73 to +75
headers: Vec<(String, String)>,
query_params: Vec<(String, String)>,
labels: Vec<(String, String)>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before release, do we need to revisit use of Strings here as opposed Cow or &'a str?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave this a shot today. The ShapeSerializer trait methods take schema: &Schema with an anonymous lifetime that can't be tied to 'a, making constructing Cow<'a, str> impractical without significant trait changes. The allocation cost for these short strings is negligible is fairly small, so I don't know that it is worth it. I'll sit on it over the weekend and try to come up with a better way to do it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, no worries. If profiling later shows String needs to be revisited, we could try a short string optimization (like this one), assuming these strings are relatively short, e.g. us-east-1, 1234567890, and application/json.

Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/Cargo.toml Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/serde/serializer.rs
Comment thread aws/rust-runtime/aws-config/src/login.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/protocol.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/protocol.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/protocol.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/protocol.rs
/// Writes a null value (for sparse collections).
fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError>;

// --- Collection helper methods ---
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there not a better way to do this?

This is an open-ended pattern. Today it's string_list, blob_list, integer_list, long_list, string_string_map, before long it's timestamp_list, blob_blob_map, string_integer_map, etc. The trait surface grows with each common collection pattern. Wonder if this could be split into extension trait methods at least to keep it off core serializer/deserializer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was kind of a side quest. It wasn't necessary, I had these all implemented in terms of the simpler operations. But these types kept popping up and having these helper methods cut down on code size substantially (20-30% if I remember correctly?). I'll try moving them into extension traits to keep it cleaner.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Played around a bit with move these to an extension trait and it doesn't really work out.

The primary issue I hit is that these methods are called inside serialize_members(&self, serializer: &mut dyn ShapeSerializer), so every generated struct's serialization goes through a trait object. Extension trait methods aren't available on dyn Trait, and there is no support for dyn TraitA + TraitB for non-auto traits.

The implementations need to be overridable on the core trait. The serializer helpers use the default impls today (JsonSerializer doesn't override them), but the corresponding deserializer helpers are overridden by JsonDeserializer to avoid per-element vtable dispatch — calling self.read_string() directly instead of going through &mut dyn ShapeDeserializer. This is measurable, the deserialization helpers are called in tight loops over every element in a list or map. Keeping them on the core trait preserves the ability for codec implementations to specialize.

Although technically open-ended since these collections aren't specified in the SEP we can treat this as a closed set. The 5 helpers (string_list, blob_list, integer_list, long_list, string_string_map) cover the collection patterns that appear most frequently in AWS models. They were chosen by analyzing which patterns generate the most boilerplate (ex: DynamoDB saw a 43% reduction in generated deserialize body lines 19,235 → 10,953). New collection patterns (ex: timestamp_list, string_integer_map) would use the generic write_list/read_list with closures. I'll add a doc comment making this explicit.

Alternatives I looked at:

  • Extension trait with blanket impl: can't call through &mut dyn ShapeSerializer
  • Supertrait (trait ShapeSerializer: CollectionHelpers): still requires every implementor to
    provide the methods, no different from having them on the core trait
  • Remove helpers entirely: +43% generated code size for DDB and likely similar for other collection-heavy models, and loses the deserializer override performance optimization

Comment thread rust-runtime/aws-smithy-schema/src/schema/serde/deserializer.rs Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 9, 2026

A new generated diff is ready to view.

A new doc preview is ready to view.

@github-actions
Copy link
Copy Markdown

A new generated diff is ready to view.

A new doc preview is ready to view.

@ysaito1001
Copy link
Copy Markdown
Contributor

ysaito1001 commented Apr 16, 2026

For personal code review aid to build mental model (take it with grain of salt)

Smithy Model into Static Schemas
  Smithy Model (.smithy file)                    Kotlin Codegen                         Generated Rust                                                                                              
  ═══════════════════════════                    ══════════════                         ══════════════                                                                                              
                                                                                                                                                                                                    
  structure PutItemInput {                                                                                                                                                                          
      @required                                                                                                                                                                                     
      TableName: TableName                                                                                                                                                                          
      Item: PutItemInputAttributeMap                                                                                                                                                                
      ...                                                                                                                                                                                           
  }                                                                                                                                                                                                 
          │                                                                                                                                                                                         
          ▼                                                                                                                                                                                         
  ┌─────────────────────┐                                                                                                                                                                           
  │ Smithy Model (Java) │  model.expectShape("PutItemInput")                                                                                                                                        
  │   StructureShape    │  → members: [TableName, Item, ...]                                                                                                                                        
  │     .members()      │  → each MemberShape has:                                                                                                                                                  
  │     .traits()       │     .memberName ("TableName")                                                                                                                                             
  └─────────┬───────────┘     .target (StringShape)                                                                                                                                                 
            │                 .traits ([@required, @jsonName, ...])                                                                                                                                 
            │                                                                                                                                                                                       
            ▼                                                                                                                                                                                       
  ┌─────────────────────────────────────────────────────┐                                                                                                                                           
  │ StructureGenerator.render()                         │                                                                                                                                           
  │   → writes struct PutItemInput { ... }              │                                                                                                                                           
  │   → calls writeCustomizations(AdditionalTraitImpls) │                                                                                                                                           
  └─────────┬───────────────────────────────────────────┘                                                                                                                                           
            │                                                                                                                                                                                       
            ▼                                                                                                                                                                                       
  ┌─────────────────────────────────────────────────────┐                                                                                                                                           
  │ SchemaStructureCustomization.section()              │                                                                                                                                           
  │   (registered by SchemaDecorator for every struct)  │                                                                                                                                           
  │   → creates SchemaGenerator(codegenContext,          │                                                                                                                                          
  │       writer, shape=PutItemInput)                   │                                                                                                                                           
  │   → calls .render()                                 │                                                                                                                                           
  └─────────┬───────────────────────────────────────────┘                                                                                                                                           
            │                                                                                                                                                                                       
            ▼                                                                                                                                                                                       
  ┌─────────────────────────────────────────────────────┐  ┌──────────────────────────────────────┐                                                                                                 
  │ SchemaGenerator.render()                            │  │ Output: _put_item_input.rs           │                                                                                                 
  │                                                     │  │                                      │                                                                                                 
  │ 1. renderMemberSchemas():                           │  │ static PUTITEMINPUT_SCHEMA_ID =      │                                                                                                 
  │    for each member in shape.members():              │  │   ShapeId::from_static("...#Put..");  │                                                                                                
  │      read member.memberName     → "TableName"       │  │                                      │                                                                                                 
  │      read member.target         → StringShape       │──►│ static PUTITEMINPUT_MEMBER_TABLE_NAME│                                                                                                
  │      read member.traits         → [@required, ...]  │  │   = Schema::new_member(              │                                                                                                 
  │      call traitSetterChain():                       │  │       ShapeId::from_static("..."),    │                                                                                                
  │        for each serde-relevant trait:               │  │       ShapeType::String,              │                                                                                                
  │          knownTraitSetter(trait) → ".with_*()"      │  │       "TableName",                    │                                                                                                
  │                                                     │  │       0,                              │                                                                                                
  │ 2. renderSchemaStatic():                            │  │   );                                  │                                                                                                
  │    collect all member refs into array               │  │                                      │                                                                                                 
  │    emit Schema::new_struct(id, type, &[members])    │──►│ static PUTITEMINPUT_SCHEMA =         │                                                                                                
  │    append traitSetterChain for shape-level traits   │  │   Schema::new_struct(                 │                                                                                                
  │                                                     │  │     PUTITEMINPUT_SCHEMA_ID,           │                                                                                                
  │ 3. emit impl PutItemInput { SCHEMA = &..._SCHEMA } │──►│     ShapeType::Structure,             │                                                                                                
  │                                                     │  │     &[&..._TABLE_NAME, &..._ITEM],   │                                                                                                 
  │ 4. renderSerializableStruct():                      │  │   );                                  │                                                                                                
  │    for each member:                                 │  │                                      │                                                                                                 
  │      match target shape type → write_string/map/etc │──►│ impl SerializableStruct for PutItem..│                                                                                                
  │                                                     │  │   fn serialize_members(&self, ser) {  │                                                                                                
  │ 5. renderDeserializeMethod():                       │  │     ser.write_string(&..TABLE_NAME,v)│                                                                                                 
  │    for each member:                                 │  │     ser.write_map(&..ITEM, ...)      │                                                                                                 
  │      match target shape type → read_string/map/etc  │──►│   }                                  │                                                                                                
  │                                                     │  │ }                                    │                                                                                                 
  │ 6. renderDeserializeHttpHeaders():                  │  │                                      │                                                                                                 
  │    for members with @httpHeader/@httpResponseCode    │──►│ impl PutItemInput {                  │                                                                                               
  │                                                     │  │   fn deserialize_with_response(...)  │                                                                                                 
  └─────────────────────────────────────────────────────┘  │ }                                    │                                                                                                 
                                                           └──────────────────────────────────────┘                                                                                                 
                                                                                                                                                                                                    
  The trait filtering is the key part. knownTraitSetter maps Smithy traits to Rust builder calls:                                                                                                   
                                                                                                                                                                                                    
  ┌─────────────────────────────────────┬─────────────────────────────────────────────────────────┐                                                                                                 
  │ Smithy Trait                        │ Rust Schema Builder Call                                │                                                                                                 
  ├─────────────────────────────────────┼─────────────────────────────────────────────────────────┤                                                                                                 
  │ `@jsonName("foo")`                  │ `.with_json_name("foo")`                                │                                                                                                 
  │ `@httpHeader("X-Foo")`              │ `.with_http_header("X-Foo")`                            │                                                                                                 
  │ `@httpQuery("bar")`                 │ `.with_http_query("bar")`                               │                                                                                                 
  │ `@httpLabel`                        │ `.with_http_label()`                                    │                                                                                                 
  │ `@httpPayload`                      │ `.with_http_payload()`                                  │                                                                                                 
  │ `@timestampFormat("epoch-seconds")` │ `.with_timestamp_format(TimestampFormat::EpochSeconds)` │                                                                                                 
  │ `@sensitive`                        │ `.with_sensitive()`                                     │                                                                                                 
  │ `@streaming`                        │ `.with_streaming()`                                     │                                                                                                 
  │ `@xmlFlattened`                     │ `.with_xml_flattened()`                                 │                                                                                                 
  │ anything else                       │ stored in a `LazyLock<TraitMap>` as generic key-value   │                                                                                                 
  └─────────────────────────────────────┴─────────────────────────────────────────────────────────┘                                                                                                 
Codec Selection Configuration

There are three levels where the protocol (and thus codec) gets selected:

Level 1 — Service default (generated code, config.rs)

// Generated in sdk/dynamodb/src/config.rs by SchemaProtocolCustomization
let mut cfg = Layer::new("DynamoDB_20120810");

// Only set the default if the customer hasn't already set one
if _service_config.protocol().is_none() {
    cfg.store_put(SharedClientProtocol::new(
        AwsJsonRpcProtocol::aws_json_1_0("DynamoDB_20120810"),
    ));
}

The codec is baked inside the protocol. AwsJsonRpcProtocol::aws_json_1_0() internally creates:

// rust-runtime/aws-smithy-json/src/protocol/aws_json_rpc.rs
let codec = JsonCodec::new(
    JsonCodecSettings::builder()
        .use_json_name(false)                    // awsJson ignores @jsonName
        .default_timestamp_format(EpochSeconds)  // awsJson default
        .build(),
);
HttpRpcProtocol::new(protocol_id, codec, "application/x-amz-json-1.0")

Level 2 — Customer override via service config builder

let conf = aws_sdk_dynamodb::config::Builder::new()
    .protocol(AwsJsonRpcProtocol::aws_json_1_1("DynamoDB_20120810"))
    .build();

Because this stores it in the service config, the Level 1 check protocol().is_none() sees it and skips the default.

Level 3 — Customer override via SdkConfig

let sdk_config = aws_config::defaults(BehaviorVersion::latest())
    .protocol(AwsRestJsonProtocol::new())
    .load()
    .await;
let client = aws_sdk_dynamodb::Client::new(&sdk_config);

Selection order:

SdkConfig.protocol()          →  set on service config builder
  ↓ (if set)
ServiceConfig.protocol()       →  stored in ConfigBag
  ↓ (if set, Level 1 skips default)
ServiceRuntimePlugin            →  only sets default if protocol().is_none()
  ↓
ConfigBag contains SharedClientProtocol
  ↓
Generated serializer loads it:  cfg.load::<SharedClientProtocol>()
  ↓
Calls protocol.serialize_request()
  ↓
Protocol uses its internal codec (JsonCodec, future XmlCodec, CborCodec, etc.)

There's no separate "codec selection" — the codec is an implementation detail of the protocol. You pick a protocol, and the protocol knows which codec to use and how to configure it.

Who decides to use HttpRpcProtocol? Can it be swapped?

The AwsJsonRpcProtocol author — it's a hardcoded composition, not a runtime choice:

pub struct AwsJsonRpcProtocol {
    inner: HttpRpcProtocol<JsonCodec>,   // ← hardcoded at the type level
    target_prefix: String,
}

HttpRpcProtocol and HttpBindingProtocol are reusable building blocks parameterized by codec type C. They're concrete generic structs, not traits. A protocol author composes them with a specific codec.

The architecture:

                        ClientProtocol (trait)
                              │
              ┌───────────────┼───────────────┐
              │               │               │
     HttpRpcProtocol<C>  HttpBindingProtocol<C>  (your own impl)
     "everything in body"  "route members by       "do whatever you want"
                            HTTP binding traits"
              │               │
    ┌─────────┼────┐    ┌─────┼──────┐
    │              │    │            │
 AwsJsonRpc    (future  AwsRestJson  (future
 Protocol      RpcV2    Protocol     AwsRestXml
               Cbor)                 Protocol)

AwsRestJsonProtocol already uses a different base:

pub struct AwsRestJsonProtocol {
    inner: HttpBindingProtocol<JsonCodec>,  // ← different base: handles @httpHeader, @httpQuery, etc.
}

A customer can't swap the inner base at runtime, but they can swap the entire protocol by implementing ClientProtocol from scratch:

#[derive(Debug)]
struct MyCustomProtocol;

impl ClientProtocol for MyCustomProtocol {
    fn serialize_request(&self, input, schema, endpoint, cfg) -> Result<Request, SerdeError> {
        // Use MessagePack, Protobuf, carrier pigeon — whatever you want
    }
    fn deserialize_response(&self, response, schema, cfg) -> Result<Box<dyn ShapeDeserializer>, SerdeError> {
        // ...
    }
    // ...
}

let client = aws_sdk_dynamodb::config::Builder::new()
    .protocol(MyCustomProtocol)
    .build();
Runtime Serialization Sequence

Concrete scenario: client.put_item().table_name("T").item("pk", S("v")).send() on DynamoDB (awsJson1_0).

┌──────────┐  ┌─────────────┐  ┌──────────────────────┐  ┌──────────────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│  Client   │  │ Orchestrator│  │ PutItemRequestSer    │  │ AwsJsonRpcProtocol   │  │HttpRpcProtocol│  │  JsonCodec   │  │JsonSerializer│
│ (fluent)  │  │             │  │ (generated)          │  │ (aws-smithy-json)    │  │(aws-smithy-  │  │              │  │              │
│           │  │             │  │                      │  │                      │  │  schema)     │  │              │  │              │
└─────┬─────┘  └──────┬──────┘  └──────────┬───────────┘  └──────────┬───────────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
      │               │                    │                         │                     │                │                │
      │ .send()       │                    │                         │                     │                │                │
      ├──────────────►│                    │                         │                     │                │                │
      │               │                    │                         │                     │                │                │
      │               │ ① Load SharedRequestSerializer from ConfigBag                     │                │                │
      │               │ ② Load SharedClientProtocol from ConfigBag                        │                │                │
      │               │    (set by ServiceRuntimePlugin if customer didn't override)       │                │                │
      │               │                    │                         │                     │                │                │
      │               │ serialize_input(   │                         │                     │                │                │
      │               │   Input::erase(    │                         │                     │                │                │
      │               │     PutItemInput), │                         │                     │                │                │
      │               │   &mut cfg)        │                         │                     │                │                │
      │               ├───────────────────►│                         │                     │                │                │
      │               │                    │                         │                     │                │                │
      │               │                    │ ③ Downcast Input to PutItemInput              │                │                │
      │               │                    │ ④ Load SharedClientProtocol from cfg          │                │                │
      │               │                    │                         │                     │                │                │
      │               │                    │ protocol.serialize_request(                   │                │                │
      │               │                    │   &input,               │                     │                │                │
      │               │                    │   PutItem::INPUT_SCHEMA,│                     │                │                │
      │               │                    │   "", &cfg)             │                     │                │                │
      │               │                    ├────────────────────────►│                     │                │                │
      │               │                    │                         │                     │                │                │
      │               │                    │                         │ ⑤ Delegate to inner │                │                │
      │               │                    │                         │   HttpRpcProtocol    │                │                │
      │               │                    │                         │ .serialize_request() │                │                │
      │               │                    │                         ├────────────────────►│                │                │
      │               │                    │                         │                     │                │                │
      │               │                    │                         │                     │ ⑥ codec        │                │
      │               │                    │                         │                     │ .create_       │                │
      │               │                    │                         │                     │  serializer()  │                │
      │               │                    │                         │                     ├───────────────►│                │
      │               │                    │                         │                     │                │ return         │
      │               │                    │                         │                     │◄───────────────┤ JsonSerializer │
      │               │                    │                         │                     │                │                │
      │               │                    │                         │                     │ ⑦ serializer.write_struct(      │
      │               │                    │                         │                     │    INPUT_SCHEMA, &input)        │
      │               │                    │                         │                     ├───────────────────────────────►│
      │               │                    │                         │                     │                │               │

Inside JsonSerializer::write_struct (step ⑦) — the core serialization loop:

┌──────────────┐                    ┌──────────────────────────────────────────────────┐
│JsonSerializer│                    │ PutItemInput (impl SerializableStruct)           │
│              │                    │   (generated by SchemaGenerator)                 │
└──────┬───────┘                    └────────────────────┬─────────────────────────────┘
       │                                                 │
       │ ⑧ output.push('{')                             │
       │                                                 │
       │ input.serialize_members(self)                   │
       ├────────────────────────────────────────────────►│
       │                                                 │
       │    ┌────────────────────────────────────────────┤
       │    │ Generated code (actual):                   │
       │    │                                            │
       │    │ fn serialize_members(&self, ser) {         │
       │    │   // member 0: table_name (String)         │
       │    │   if let Some(val) = &self.table_name {    │
       │◄───┤     ser.write_string(                      │
       │    │       &PUTITEMINPUT_MEMBER_TABLE_NAME,     │  ← Schema has member name "TableName"
       │    │       val                                  │
       │    │     );                                     │
       │    │   }                                        │
       │    │                                            │
       │    │   // member 1: item (Map<String, AttrVal>) │
       │    │   if let Some(val) = &self.item {          │
       │◄───┤     ser.write_map(                         │
       │    │       &PUTITEMINPUT_MEMBER_ITEM,           │  ← Schema has member name "Item"
       │    │       &|ser| {                             │
       │    │         for (key, value) in val {          │
       │    │           ser.write_string(STRING, key);   │
       │    │           ser.write_struct(                │  ← AttributeValue is a union
       │    │             AttributeValue::SCHEMA, value  │    but uses SerializableStruct
       │    │           );                               │
       │    │         }                                  │
       │    │       }                                    │
       │    │     );                                     │
       │    │   }                                        │
       │    │   // ... remaining members ...             │
       │    │ }                                          │
       │    └────────────────────────────────────────────┘
       │
       │ ⑨ output.push('}')
       │

Inside JsonSerializer::write_string — how a single member becomes JSON:

  write_string(&PUTITEMINPUT_MEMBER_TABLE_NAME, "T")
       │
       ├─► prefix(schema):
       │     │
       │     ├─► needs_comma? → emit ","
       │     │
       │     ├─► field_name(schema):
       │     │     → settings.member_to_field(schema)
       │     │     → JsonFieldMapper::UseMemberName  (awsJson1_0: use_json_name=false)
       │     │     → schema.member_name() → "TableName"
       │     │     emit → "TableName":
       │     │
       │     └─► needs_comma = true
       │
       └─► emit → "T"    (with JSON escaping + quotes)

  Result so far: {"TableName":"T"

Back up the stack — after serialize_members returns:

┌──────────────┐  ┌──────────────────────┐  ┌──────────────────────┐
│HttpRpcProtocol│  │ AwsJsonRpcProtocol   │  │ PutItemRequestSer   │
└──────┬───────┘  └──────────┬───────────┘  └──────────┬───────────┘
       │                     │                         │
       │ ⑩ serializer.finish()                        │
       │    → Vec<u8>: {"TableName":"T","Item":{"pk":{"S":"v"}}}
       │                     │                         │
       │ ⑪ Build HTTP Request:                        │
       │    method = POST                              │
       │    uri = "/" (converted from empty string "")  │
       │    body = SdkBody::from(json_bytes)           │
       │    headers:                                    │
       │      Content-Type: application/x-amz-json-1.0 │
       │      Content-Length: 47                        │
       │                     │                         │
       │ return Request ─────►                         │
       │                     │                         │
       │                     │ ⑫ Add X-Amz-Target header:
       │                     │    cfg.load::<Metadata>()
       │                     │    → format!("{}.{}", "DynamoDB_20120810", "PutItem")
       │                     │    → "DynamoDB_20120810.PutItem"
       │                     │                         │
       │                     │ return Request ──────────►
       │                     │                         │
       │                     │                         │ return Ok(request)
       │                     │                         │    to Orchestrator
Runtime Deserialization Sequence

Concrete scenario: DynamoDB PutItem response with awsJson1_0.

HTTP Response (wire):                                                                                                                                                                           
  ┌──────────────────────────────────────────────────────┐                                                                                                                                        
  │ HTTP/1.1 200 OK                                      │                                                                                                                                        
  │ Content-Type: application/x-amz-json-1.0             │                                                                                                                                        
  │ x-amzn-requestid: ABC-123-DEF                        │                                                                                                                                        
  │                                                      │                                                                                                                                        
  │ {"Attributes":{"pk":{"S":"v"}},"ConsumedCapacity":…} │                                                                                                                                        
  └──────────────────────────────────────────────────────┘                                                                                                                                        
                                                                                                                                                                                                  
  ┌─────────────┐  ┌──────────────────────────┐  ┌──────────────────────┐  ┌──────────────┐  ┌──────────────┐                                                                                     
  │ Orchestrator│  │ PutItemResponseDeser     │  │ AwsJsonRpcProtocol   │  │HttpRpcProtocol│  │  JsonCodec   │                                                                                    
  │             │  │ (generated, put_item.rs) │  │ (aws_json_rpc.rs)    │  │ (rpc.rs)      │  │ (codec.rs)   │                                                                                    
  └──────┬──────┘  └────────────┬─────────────┘  └──────────┬───────────┘  └──────┬───────┘  └──────┬───────┘                                                                                     
         │                      │                           │                     │                │                                                                                              
         │ ① read_body(response) — loads body bytes into memory                   │                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │ ② cfg.load::<SharedResponseDeserializer>()       │                     │                │                                                                                              
         │   deserialize_nonstreaming_with_config(           │                     │                │                                                                                             
         │     &response, &cfg)  │                           │                     │                │                                                                                             
         ├─────────────────────►│                           │                     │                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │ ③ Check: success (200)?   │                     │                │                                                                                              
         │                      │   YES → take success path │                     │                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │ ④ cfg.load::<SharedClientProtocol>()            │                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │ protocol.deserialize_response(                  │                │                                                                                              
         │                      │   &response,              │                     │                │                                                                                              
         │                      │   PutItem::OUTPUT_SCHEMA, │                     │                │                                                                                              
         │                      │   &cfg)                   │                     │                │                                                                                              
         │                      ├──────────────────────────►│                     │                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │                           │ ⑤ delegate to inner │                │                                                                                              
         │                      │                           ├────────────────────►│                │                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │                           │                     │ ⑥ body = response                                                                                             
         │                      │                           │                     │   .body().bytes()                                                                                             
         │                      │                           │                     │                │                                                                                              
         │                      │                           │                     │ ⑦ codec.create_                                                                                               
         │                      │                           │                     │   deserializer(body)                                                                                          
         │                      │                           │                     ├───────────────►│                                                                                              
         │                      │                           │                     │                │                                                                                              
         │                      │                           │                     │◄───────────────┤                                                                                              
         │                      │                           │                     │  JsonDeserializer                                                                                             
         │                      │                           │                     │  (has body bytes                                                                                              
         │                      │                           │                     │   + codec settings)                                                                                           
         │                      │                           │                     │                │                                                                                              
         │                      │◄──────────────────────────┤◄────────────────────┤                │                                                                                              
         │                      │ Box<dyn ShapeDeserializer> (= JsonDeserializer)                  │                                                                                              
         │                      │                           │                     │                │                                                                                              
                                                                                                                                                                                                  
  Now the generated code takes over — `deserialize_with_response`:                                                                                                                                
                                                                                                                                                                                                  
  ┌──────────────────────────┐                  ┌────────────────────────┐                                                                                                                        
  │ PutItemResponseDeser     │                  │ PutItemOutput          │                                                                                                                        
  │ (generated, put_item.rs) │                  │ (generated,            │                                                                                                                        
  │                          │                  │  _put_item_output.rs)  │                                                                                                                        
  └────────────┬─────────────┘                  └───────────┬────────────┘                                                                                                                        
               │                                            │                                                                                                                                     
               │ ⑧ PutItemOutput::deserialize_with_response(│                                                                                                                                     
               │     &mut *deser,                           │                                                                                                                                     
               │     response.headers(),                    │                                                                                                                                     
               │     response.status(),                     │                                                                                                                                     
               │     body)                                  │                                                                                                                                     
               ├───────────────────────────────────────────►│                                                                                                                                     
               │                                            │                                                                                                                                     
               │    ┌───────────────────────────────────────┤                                                                                                                                     
               │    │                                       │                                                                                                                                     
               │    │ ⑨ Read HTTP-bound members FIRST       │                                                                                                                                     
               │    │   (before touching the deserializer): │                                                                                                                                     
               │    │                                       │                                                                                                                                     
               │    │   if let Some(val) = headers          │                                                                                                                                     
               │    │       .get("x-amzn-requestid") {      │                                                                                                                                     
               │    │     builder._request_id = Some(val);  │  ← synthetic member                                                                                                                 
               │    │   }                                   │                                                                                                                                     
               │    │                                       │                                                                                                                                     
               │    └───────────────────────────────────────┤                                                                                                                                     
               │                                            │                                                                                                                                     
                                                                                                                                                                                                  
  Then `deserialize_with_response` calls into the `JsonDeserializer` for body members:                                                                                                            
                                                                                                                                                                                                  
  ┌────────────────────────┐                    ┌──────────────────┐                                                                                                                              
  │ PutItemOutput          │                    │ JsonDeserializer  │                                                                                                                             
  │ deserialize_with_      │                    │ (body bytes +     │                                                                                                                             
  │ response()             │                    │  codec settings)  │                                                                                                                             
  └───────────┬────────────┘                    └────────┬─────────┘                                                                                                                              
              │                                          │                                                                                                                                        
              │ ⑩ deser.read_struct(                     │                                                                                                                                        
              │     &PUTITEMOUTPUT_SCHEMA,                │                                                                                                                                       
              │     &mut |member, deser| { ... })        │                                                                                                                                        
              ├─────────────────────────────────────────►│                                                                                                                                        
              │                                          │                                                                                                                                        
              │         ┌────────────────────────────────┤                                                                                                                                        
              │         │ JsonDeserializer::read_struct:  │                                                                                                                                       
              │         │                                │                                                                                                                                        
              │         │ ⑪ skip_whitespace, expect '{'  │                                                                                                                                        
              │         │                                │                                                                                                                                        
              │         │ loop:                           │                                                                                                                                       
              │         │   parse_key() → "Attributes"   │                                                                                                                                        
              │         │                                │                                                                                                                                        
              │         │   ⑫ resolve_member(             │                                                                                                                                       
              │         │       PUTITEMOUTPUT_SCHEMA,     │                                                                                                                                       
              │         │       "Attributes")             │                                                                                                                                       
              │         │     → field_to_member()         │                                                                                                                                       
              │         │     → JsonFieldMapper::         │                                                                                                                                       
              │         │       UseMemberName             │                                                                                                                                       
              │         │     → schema.member_schema      │                                                                                                                                       
              │         │       ("Attributes")            │                                                                                                                                       
              │         │     → MEMBER_ATTRIBUTES (idx=0) │                                                                                                                                       
              │         │                                │                                                                                                                                        
              │         │   ⑬ consumer(member, deser):   │                                                                                                                                        
              │    ┌────┤     calls back into generated   │                                                                                                                                       
              │    │    │     code with member + deser    │                                                                                                                                       
              │    │    └────────────────────────────────┘                                                                                                                                        
              │    │                                                                                                                                                                              
              │    │  Generated consumer (match on index):                                                                                                                                        
              │    │                                                                                                                                                                              
              │    │  match member.member_index() {                                                                                                                                               
              │    │    Some(0) => {  // "Attributes"                                                                                                                                             
              │    │      let mut map = HashMap::new();                                                                                                                                           
              │◄───┤      deser.read_map(member, |key, deser| {                                                                                                                                   
              │    │        // key = "pk" (parsed from JSON)                                                                                                                                      
              │    │        map.insert(key,                                                                                                                                                       
              │    │          AttributeValue::deserialize(deser)?                                                                                                                                 
              │    │          // ↑ recursively deserializes union                                                                                                                                 
              │    │        );                                                                                                                                                                    
              │    │      });                                                                                                                                                                     
              │    │      builder.attributes = Some(map);                                                                                                                                         
              │    │    }                                                                                                                                                                         
              │    │    Some(1) => {  // "ConsumedCapacity"                                                                                                                                       
              │    │      builder.consumed_capacity =                                                                                                                                             
              │    │        Some(ConsumedCapacity::deserialize(deser)?);                                                                                                                          
              │    │    }                                                                                                                                                                         
              │    │    Some(3) => {  // "_request_id" (body)                                                                                                                                     
              │    │      builder._request_id =                                                                                                                                                   
              │    │        Some(deser.read_string(member)?);                                                                                                                                     
              │    │    }                                                                                                                                                                         
              │    │    _ => {}  // unknown fields skipped                                                                                                                                        
              │    │  }                                                                                                                                                                           
              │    │                                                                                                                                                                              
              │    └──────────────────────────────────────┘                                                                                                                                       
              │                                                                                                                                                                                   
              │ ⑭ JsonDeserializer: next key... → '}'                                                                                                                                             
              │    → loop ends, return Ok(())                                                                                                                                                     
              │                                                                                                                                                                                   
              │ ⑮ builder.build() → PutItemOutput                                                                                                                                                 
              │                                                                                                                                                                                   
              │◄─────────────── Ok(PutItemOutput)                                                                                                                                                 
              │                                                                                                                                                                                   
                                                                                                                                                                                                  
  Back to the orchestrator:                                                                                                                                                                       
                                                                                                                                                                                                  
  ┌─────────────┐  ┌──────────────────────────┐                                                                                                                                                   
  │ Orchestrator│  │ PutItemResponseDeser     │                                                                                                                                                   
  └──────┬──────┘  └────────────┬─────────────┘                                                                                                                                                   
         │                      │                                                                                                                                                                 
         │◄─────────────────────┤ ⑯ Ok(Output::erase(output))                                                                                                                                     
         │                      │                                                                                                                                                                 
         │ ⑰ ctx.set_output_or_error(output)                                                                                                                                                      
         │                                                                                                                                                                                        
         │ ⑱ Run after_deserialization interceptors                                                                                                                                               
         │                                                                                                                                                                                        
         │ ⑲ Return Ok(PutItemOutput) to caller                                                                                                                                                   
                                                                                                                                                                                                  
  Error path (if status != 200) differs at step ③:                                                                                                                                                
                                                                                                                                                                                                  
  ③ Check: success? NO (e.g., status=400)                                                                                                                                                         
    │                                                                                                                                                                                             
    ├─ ④e parse_http_error_metadata(status, headers, body)                                                                                                                                        
    │     → extracts error code from JSON "__type" or "code" field                                                                                                                                
    │     → e.g., "ConditionalCheckFailedException"                                                                                                                                               
    │                                                                                                                                                                                             
    ├─ ⑤e match error_code {                                                                                                                                                                      
    │     "ConditionalCheckFailedException" => {                                                                                                                                                  
    │       protocol.deserialize_response(                                                                                                                                                        
    │         response,                                                                                                                                                                           
    │         ConditionalCheckFailedException::SCHEMA,  ← error type's schema                                                                                                                     
    │         cfg                                                                                                                                                                                 
    │       ) → JsonDeserializer for error body                                                                                                                                                   
    │                                                                                                                                                                                             
    │       ConditionalCheckFailedException::deserialize_with_response(                                                                                                                           
    │         &mut *deser, headers, status, body                                                                                                                                                  
    │       ) → error struct                                                                                                                                                                      
    │                                                                                                                                                                                             
    │       tmp.meta = generic;          ← attach error metadata                                                                                                                                  
    │       tmp.message = _error_message; ← fallback message                                                                                                                                      
    │     }                                                                                                                                                                                       
    │     _ => PutItemError::generic(generic)                                                                                                                                                     
    │   }                                                                                                                                                                                         
    │                                                                                                                                                                                             
    └─ ⑥e Err(OrchestratorError::operation(Error::erase(err)))                                                                                                                                    

Key difference from serialization: deserialization is a two-phase process. deserialize_with_response reads HTTP-bound members (headers, status code) directly from the response first, then delegates body-only parsing to the JsonDeserializer. This avoids runtime trait checks on every member — the generated code knows at compile time which members are HTTP-bound.

Component Responsibility
Layer           │ Component                  │ File                                                                              │ Responsibility
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Generated code  │ PutItemRequestSerializer   │ codegen-client/.../protocol/RequestSerializerGenerator.kt                        │ Downcast input, load protocol from
                │                            │ → generates: src/operation/put_item.rs                                            │ ConfigBag, call protocol.serialize_request()
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Protocol        │ AwsJsonRpcProtocol         │ rust-runtime/aws-smithy-json/src/protocol/aws_json_rpc.rs                        │ Delegates to HttpRpcProtocol, then
 (aws-smithy-   │                            │                                                                                   │ adds X-Amz-Target from Metadata
json)           │                            │                                                                                   │
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Protocol base   │ HttpRpcProtocol<JsonCodec> │ rust-runtime/aws-smithy-schema/src/schema/http_protocol/rpc.rs                   │ Creates serializer from codec,
 (aws-smithy-   │                            │                                                                                   │ calls write_struct(schema, input),
schema)         │                            │                                                                                   │ builds HTTP request (POST /, headers)
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Codec           │ JsonCodec                  │ rust-runtime/aws-smithy-json/src/codec.rs                                        │ Factory: creates JsonSerializer with
 (aws-smithy-   │                            │                                                                                   │ settings (use_json_name=false,
json)           │                            │                                                                                   │ timestamp=epoch-seconds)
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Serializer      │ JsonSerializer             │ rust-runtime/aws-smithy-json/src/codec/serializer.rs                             │ Writes JSON bytes. Calls
 (aws-smithy-   │ (impl ShapeSerializer)     │                                                                                   │ input.serialize_members(self) to
json)           │                            │                                                                                   │ push the visitor pattern
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Generated code  │ PutItemInput               │ codegen-core/.../generators/SchemaGenerator.kt                                   │ impl SerializableStruct: iterates
                │ (impl SerializableStruct)  │ → generates: src/operation/put_item/_put_item_input.rs                           │ own fields, calls ser.write_string(),
                │                            │                                                                                   │ ser.write_map(), etc. with Schema refs
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Schema statics  │ PUTITEMINPUT_SCHEMA,       │ codegen-core/.../generators/SchemaGenerator.kt                                   │ Carry member names, shape types,
(generated)     │ PUTITEMINPUT_MEMBER_*      │ → generates: src/operation/put_item/_put_item_input.rs                           │ traits — the serializer reads these
                │                            │                                                                                   │ to decide field names & formats
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Protocol trait  │ ClientProtocol             │ rust-runtime/aws-smithy-schema/src/schema/protocol.rs                            │ Object-safe trait defining
                │ SharedClientProtocol       │                                                                                   │ serialize_request / deserialize_response
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Serde traits    │ ShapeSerializer            │ rust-runtime/aws-smithy-schema/src/schema/serde/serializer.rs                    │ Object-safe trait: write_string,
                │ SerializableStruct         │                                                                                   │ write_struct, write_map, etc.
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Codec trait     │ Codec, FinishSerializer    │ rust-runtime/aws-smithy-schema/src/schema/codec.rs                               │ Factory trait: create_serializer /
                │                            │                                                                                   │ create_deserializer
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Orchestrator    │ try_attempt / try_op       │ rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs                       │ Calls serialize_input(), then
                │ apply_endpoint             │ rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs              │ protocol.update_endpoint(), then
                │                            │                                                                                   │ auth, then sends
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Deser API       │ DeserializeResponse        │ rust-runtime/aws-smithy-runtime-api/src/client/ser_de.rs                         │ deserialize_nonstreaming_with_config
                │ SerializeRequest           │                                                                                   │ / serialize_input trait boundary
────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────
Config wiring   │ SchemaDecorator            │ codegen-client/.../customizations/SchemaDecorator.kt                             │ Stores SharedClientProtocol in
                │ SchemaSerdeAllowlist       │                                                                                   │ ConfigBag, adds protocol() to config

Copy link
Copy Markdown
Contributor

@ysaito1001 ysaito1001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this.

Comment thread rust-runtime/aws-smithy-json/Cargo.toml Outdated
Comment thread rust-runtime/aws-smithy-runtime-api/src/client/ser_de.rs
Comment thread rust-runtime/aws-smithy-runtime-api/src/client/ser_de.rs
Comment on lines +73 to +75
headers: Vec<(String, String)>,
query_params: Vec<(String, String)>,
labels: Vec<(String, String)>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, no worries. If profiling later shows String needs to be revisited, we could try a short string optimization (like this one), assuming these strings are relatively short, e.g. us-east-1, 1234567890, and application/json.

Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/binding.rs Outdated
Comment thread rust-runtime/aws-smithy-schema/src/schema/http_protocol/rpc.rs
Comment thread rust-runtime/aws-smithy-schema/src/schema/protocol.rs
@github-actions
Copy link
Copy Markdown

A new generated diff is ready to view.

A new doc preview is ready to view.

@github-actions
Copy link
Copy Markdown

A new generated diff is ready to view.

A new doc preview is ready to view.

@landonxjames
Copy link
Copy Markdown
Contributor Author

Failing CI tasks are all related to:

Error: Crate `aws-smithy-runtime-api-macros` was included in the previous release's `versions.toml`, but is not included in the upcoming release. If this is expected, update the publisher tool to expect and allow it.

Need to merge main to get the changes to fix that. Going to merge this back to the feature branch first so I don't have to pull the changes forward through a bunch of different branches

@landonxjames landonxjames merged commit c35bc88 into feature/schema Apr 21, 2026
37 of 41 checks passed
@landonxjames landonxjames deleted the landonxjames/schema-protocol branch April 21, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants