pub trait ClientProtocolInner:
Send
+ Sync
+ Debug {
type Request;
type Response;
// Required methods
fn protocol_id(&self) -> &ShapeId<'static>;
fn serialize_request(
&self,
input: &dyn SerializableStruct,
input_schema: &Schema<'_>,
endpoint: &str,
cfg: &ConfigBag,
) -> Result<Self::Request, SerdeError>;
fn deserialize_response<'a>(
&self,
response: &'a Self::Response,
output_schema: &Schema<'_>,
cfg: &'a ConfigBag,
) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>;
fn update_endpoint(
&self,
request: &mut Self::Request,
endpoint: &Endpoint,
cfg: &ConfigBag,
) -> Result<(), SerdeError>;
// Provided methods
fn parse_error_metadata(
&self,
response: &Self::Response,
cfg: &ConfigBag,
) -> Result<ErrorMetadataBuilder, SerdeError> { ... }
fn deserialize_error_response<'a>(
&self,
response: &'a Self::Response,
cfg: &'a ConfigBag,
) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> { ... }
fn payload_codec(&self) -> Option<&dyn DynCodec> { ... }
fn event_stream_media_type(&self) -> Option<&str> { ... }
fn parse_event_stream_error_metadata(
&self,
payload: &[u8],
) -> Result<ErrorMetadataBuilder, SerdeError> { ... }
}Expand description
Statically-dispatched client protocol trait — the one implementors write.
Request and Response are associated types so a protocol can target any transport
(HTTP, MQTT, Unix-socket, in-memory, …). For the common HTTP case, set both to
aws_smithy_runtime_api::http::Request / Response.
Callers who need to store a protocol behind dyn (e.g., in a ConfigBag for
runtime swapping) should use the object-safe ClientProtocol trait instead.
Every ClientProtocolInner is automatically a
ClientProtocol<Self::Request, Self::Response> via a blanket impl, so implementors
never write ClientProtocol manually.
See apply_http_endpoint for the canonical HTTP implementation of
update_endpoint.
§Lifecycle
Instances are immutable and thread-safe. They are typically created once and shared across all requests for a client.
Required Associated Types§
Required Methods§
Sourcefn protocol_id(&self) -> &ShapeId<'static>
fn protocol_id(&self) -> &ShapeId<'static>
Returns the Smithy shape ID of this protocol.
Sourcefn serialize_request(
&self,
input: &dyn SerializableStruct,
input_schema: &Schema<'_>,
endpoint: &str,
cfg: &ConfigBag,
) -> Result<Self::Request, SerdeError>
fn serialize_request( &self, input: &dyn SerializableStruct, input_schema: &Schema<'_>, endpoint: &str, cfg: &ConfigBag, ) -> Result<Self::Request, SerdeError>
Serializes an operation input into a request message.
§The protocol owns its own wire format
Anything this protocol puts on the wire that the protocol alone determines must be
resolved here, not emitted by codegen. A client generated for one protocol can be pointed at
another with Config::builder().protocol(..), so anything codegen bakes in based on the
generated protocol is wrong after a swap: left behind when this protocol is swapped out, and
missing when it is swapped in. Concretely, an implementor owns:
- Framing headers. rpcv2Cbor sets
smithy-protocolandaccept; awsJson setsX-Amz-Target. Codegen’s schema path deliberately emits none of these. - The request path, per the note on
endpointbelow. - Model facts the protocol needs but a caller cannot know, read from
cfg:ServiceShapeName,ServiceVersion,ServiceXmlNamespace. Generated clients store these regardless of which protocol they were generated for, precisely so a swapped-in protocol can find them. Keep an explicit builder as an override for what the model cannot express – a target prefix that is not the service shape name, for instance.
Headers determined by the service rather than the protocol – x-amzn-query-mode, from
@awsQueryCompatible – are outside this rule and stay in codegen, because their value does
not change when the protocol does.
§endpoint is advisory
endpoint is a request path (or ""), never a host; scheme and authority are merged
later by apply_http_endpoint. It is whatever codegen computed for the protocol the
client was generated for, so a protocol that alone determines its route must ignore it:
- Fixed route – awsJson and awsQuery are specified to
POST /, so they pass/and ignore the argument entirely. Forwarding it would let an rpcv2Cbor-generated client POST to/service/{service}/operation/{operation}. - Route derived from model facts – rpcv2Cbor computes
/service/{service}/operation/{operation}fromcfg, falling back toendpointonly when those facts are absent. - Route from
@httpbindings – REST protocols expand the operation’s@httptemplate from the schema, which is authoritative;endpointis ignored. Generated REST clients pass"", so this costs them nothing, and it stops an RPC route from being prefixed onto the template after a swap.endpointacts as the template only for a schema that carries no@httptrait at all.
The shared HttpRpcProtocol helper
deliberately does not hard-code /; only a concrete protocol knows whether its route is
constant.
Sourcefn deserialize_response<'a>(
&self,
response: &'a Self::Response,
output_schema: &Schema<'_>,
cfg: &'a ConfigBag,
) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>
fn deserialize_response<'a>( &self, response: &'a Self::Response, output_schema: &Schema<'_>, cfg: &'a ConfigBag, ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>
Deserializes a response message, returning a boxed ShapeDeserializer that supplies
every member of output_schema this protocol carries in the response.
The protocol, not the caller, decides where each member comes from. A protocol that uses
HTTP bindings reads @httpHeader, @httpPrefixHeaders, @httpResponseCode and
@httpPayload members from the HTTP message and the rest from the body (see
http_output_deserializer); a body-only
protocol ignores those traits and reads every member from the body. Generated code hands
the result to the output builder’s member consumer and does no transport parsing itself.
A @streaming payload member is never supplied: the caller owns the live body.
cfg shares the returned deserializer’s lifetime so a protocol can defer reading
response-scoped settings until they are needed; the HTTP-binding composite reads
NonUtf8HeaderHandling only after a header fails to parse. An implementation that does
not keep cfg may declare it as a plain &ConfigBag.
Sourcefn update_endpoint(
&self,
request: &mut Self::Request,
endpoint: &Endpoint,
cfg: &ConfigBag,
) -> Result<(), SerdeError>
fn update_endpoint( &self, request: &mut Self::Request, endpoint: &Endpoint, cfg: &ConfigBag, ) -> Result<(), SerdeError>
Updates a previously serialized request with a resolved endpoint.
Required by SEP requirement 7. The orchestrator calls this after endpoint
resolution, which happens after serialize_request.
HTTP protocols should implement this as:
apply_http_endpoint(request, endpoint, cfg)(See apply_http_endpoint.) Non-HTTP protocols implement the transport’s
equivalent.
Provided Methods§
Sourcefn parse_error_metadata(
&self,
response: &Self::Response,
cfg: &ConfigBag,
) -> Result<ErrorMetadataBuilder, SerdeError>
fn parse_error_metadata( &self, response: &Self::Response, cfg: &ConfigBag, ) -> Result<ErrorMetadataBuilder, SerdeError>
Extracts canonical error metadata (code, message, request id) from a response’s wire envelope.
Returns a Builder so callers can attach
per-request fields (e.g., x-amzn-RequestId from an HTTP header) before
finalizing.
Concrete protocols override this to extract their envelope-specific fields:
- awsJson1.0 / awsJson1.1:
__typefrom the body,X-Amzn-Errortypeheader fallback. - restJson1: same as awsJson.
- restXml (wrapped):
<ErrorResponse><Error><Code>etc. - restXml (
@restXml(noErrorWrapping: true)):<Error><Code>etc. - awsQuery / ec2Query:
<ErrorResponse><Error><Code>etc. - rpcv2Cbor:
__typefrom the CBOR map.
The default implementation returns an empty
Builder — sufficient for protocols that
haven’t migrated to schema-driven error dispatch yet, but
callers will see Option::None for code() / message() and treat
the response as an unhandled error.
Sourcefn deserialize_error_response<'a>(
&self,
response: &'a Self::Response,
cfg: &'a ConfigBag,
) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>
fn deserialize_error_response<'a>( &self, response: &'a Self::Response, cfg: &'a ConfigBag, ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError>
Returns a ShapeDeserializer positioned at the body of an error
response — inside the protocol’s error envelope, where applicable.
Generated error dispatch code calls this to obtain a deserializer for the modeled error’s
member consumer, regardless of which protocol is active at runtime. As with
deserialize_response, the protocol decides which members
come from the transport and which from the body.
For body-only protocols (awsJson1.0/1.1, awsQuery, ec2Query, rpcv2Cbor) the default
implementation suffices: the body root is the error body, so it forwards to
deserialize_response against
prelude::DOCUMENT, and HTTP response bindings are correctly
ignored.
Envelope-bearing protocols (restXml wrapped / unwrapped, awsQuery, ec2Query) MUST
override to strip the outer <ErrorResponse> / <Error> wrapper before returning the
deserializer.
§Protocols that use HTTP bindings must override this
A protocol whose errors carry @httpHeader, @httpPrefixHeaders or
@httpResponseCode members MUST override this and build its deserializer with
http_error_deserializer, even when its
body needs no envelope positioning. The default’s two properties are both accidents
rather than contracts: it evaluates the success body-only fast path, and it does so
against a schema that is not the error’s. Neither is safe to depend on, because the
concrete error schema is unknown until the generated error variant calls read_struct.
Every built-in REST protocol therefore overrides this, and
the_error_path_never_takes_the_body_only_fast_path pins the consequence.
Sourcefn payload_codec(&self) -> Option<&dyn DynCodec>
fn payload_codec(&self) -> Option<&dyn DynCodec>
Returns the codec used for payload (de)serialization, if any.
See DynCodec for why the codec is exposed
through the object-safe sibling.
Sourcefn event_stream_media_type(&self) -> Option<&str>
fn event_stream_media_type(&self) -> Option<&str>
The media type used to label a structured event-stream payload, if this
protocol supports event streams — for example application/cbor or
application/json.
Every event-stream frame carries a :content-type header describing its
payload. The payload itself is encoded by whichever protocol is selected at
runtime, so this label has to come from that same protocol; a label baked in
when the client was generated contradicts the bytes as soon as a different
protocol is selected, and a peer that honours the header then decodes with
the wrong codec.
§Why this is on the protocol and not on Codec
The value is protocol-determined, not format-determined, and one codec
serves several protocols that disagree about it. A single JsonCodec backs
restJson1, awsJson1_0 and awsJson1_1, whose request content types are
application/json, application/x-amz-json-1.0 and
application/x-amz-json-1.1 respectively. A codec-level accessor could
therefore only ever return one of those, and would describe event payloads
correctly only because the three JSON protocols happen to agree there.
§What this is not
This is not the request Content-Type. For awsJson the two differ, per
the above. It is also not the content type of a payload whose type is fixed
by the shape rather than the format: an @eventPayload blob is
application/octet-stream and a string is text/plain under every
protocol, so those stay with the code generator and must not be sourced
from here.
Returns None by default, meaning the protocol declares no such media type
— callers must keep a fallback. The default keeps this addition
non-breaking for third-party protocols, which the SEP requires be able to
exist without modifying a code generator.
Sourcefn parse_event_stream_error_metadata(
&self,
payload: &[u8],
) -> Result<ErrorMetadataBuilder, SerdeError>
fn parse_event_stream_error_metadata( &self, payload: &[u8], ) -> Result<ErrorMetadataBuilder, SerdeError>
Extracts canonical error metadata from the payload of an event-stream
exception frame.
This is the event-stream counterpart of
parse_error_metadata, and it exists
separately for a typing reason rather than a behavioral one: that method
takes &Self::Response, an HTTP response, and an event-stream frame is not
one. The parsing itself is identical — both read a protocol-specific error
envelope out of a byte payload — so implementors should delegate to the same
helper they use there.
§Why this must be resolved by the protocol
The frame’s payload is encoded by whichever protocol is selected at runtime
(via payload_codec), so its error envelope must be
parsed by that same protocol. A code generator cannot decide this: it knows
only the protocol the client was generated for, and after a runtime protocol
swap that is the wrong one. Getting it wrong is not silent — the parse fails
and the caller reports an unhandled error — but it costs the error code, and
with it the modeled error variant and any retry classification keyed on that
code.
§Scope
Takes the payload only. An event-stream frame carries no HTTP headers, so
there is nothing to pass for the header-borne discriminators some protocols
also accept (restJson1’s x-amzn-errortype, awsQuery-compatible’s
x-amzn-query-error); implementors should parse as though the header map
were empty. The frame’s own :exception-type header is a separate mechanism
and is handled by generated dispatch code before this is called.
The default returns an empty
Builder, matching
parse_error_metadata: callers see
Option::None for code() / message() and treat the frame as an
unhandled error. The default keeps this addition non-breaking for
third-party protocols, which the SEP requires be able to exist without
modifying a code generator.
Implementors of a protocol without event streams should leave this alone.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".