9. Behavior traits#
9.1. Idempotency#
Operations marked with the readonly trait or idempotent trait
are considered idempotent as defined in RFC 9110#section-9.2.2. Operations
that contain a top-level input member marked with the idempotencyToken trait
that are provided a token for the member are also considered idempotent. All
other operations SHOULD be considered unsafe to retry unless the response to
the operation is an error marked with the retryable trait or another
kind of protocol-specific hint indicates the operation is safe to retry
(for example, a Retry-After
HTTP header, a 503 HTTP response, or a
429 HTTP response).
9.1.1. idempotencyToken
trait#
- Summary
- Defines the input member of an operation that is used by the server to identify and discard replayed requests.
- Trait selector
structure > :test(member > string)
Any structure member that targets a string
- Value type
- Annotation trait
Only a single member of the input of an operation can be targeted by the
idempotencyToken
trait; only top-level structure members of the input of an
operation are considered.
A unique identifier (typically a UUID) SHOULD be used by the client when providing the value for the request token member. When the request token is present, the service MUST ensure that the request is not replayed within a service-defined period of time. This allows the client to safely retry operation invocations, including operations that are not read-only, that fail due to networking issues or internal server errors. The service uses the provided request token to identify and discard duplicate requests.
Client implementations MAY automatically provide a value for a request token member if and only if the member is not explicitly provided.
operation AllocateWidget {
input: AllocateWidgetInput
}
@input
structure AllocateWidgetInput {
@idempotencyToken
clientToken: String
}
9.1.2. idempotent
trait#
- Summary
- Indicates that the intended effect on the server of multiple identical requests with an operation is the same as the effect for a single such request.
- Trait selector
operation
- Value type
- Annotation trait
- Conflicts with
- readonly trait
@idempotent
operation DeleteSomething {
input: DeleteSomethingInput
output: DeleteSomethingOutput
}
Note
All operations that are marked as readonly trait are inherently idempotent.
9.1.3. readonly
trait#
- Summary
- Indicates that an operation is effectively read-only.
- Trait selector
operation
- Value type
- Annotation trait
- Conflicts with
- idempotent trait
@readonly
operation GetSomething {
input: GetSomethingInput
output: GetSomethingOutput
}
9.1.4. retryable
trait#
- Summary
- Indicates that an error MAY be retried by the client.
- Trait selector
structure[trait|error]
A structure shape with the error trait
- Value type
structure
The retryable trait is a structure that contains the following members:
Property | Type | Description |
---|---|---|
throttling | boolean |
Indicates that the error is a retryable throttling error. |
@error("server")
@retryable
@httpError(503)
structure ServiceUnavailableError {}
@error("client")
@retryable(throttling: true)
@httpError(429)
structure ThrottlingError {}
9.2. Pagination#
Pagination is the process of dividing large result sets into discrete pages. Smithy provides a built-in pagination mechanism that utilizes cursor based pagination.
9.2.1. paginated
trait#
- Summary
- The
paginated
trait indicates that an operation intentionally limits the number of results returned in a single response and that multiple invocations might be necessary to retrieve all results. - Trait selector
:is(operation, service)
An operation or service
- Value type
structure
The paginated
trait is a structure that contains the following members:
Property | Type | Description |
---|---|---|
inputToken | string |
The name of the operation input member that contains a continuation
token. When this value is provided as input, the service returns
results from where the previous response left off. This input member
MUST NOT be marked as When contained within a service, a paginated operation MUST either
configure |
outputToken | string |
The path to the operation output member that contains an optional
continuation token. When this value is present and not empty in
operation output, it indicates that there are more results to retrieve.
To get the next page of results, the client passes the received output
continuation token to the input continuation token of the next request.
This output member MUST NOT be marked as When contained within a service, a paginated operation MUST either
configure |
items | string |
The path to an output member of the operation that contains the data that is being paginated across many responses. The named output member, if specified, MUST target a list or map. |
pageSize | string |
The name of an operation input member that limits the maximum number of results to include in the operation output. This input member SHOULD NOT be required and SHOULD target an integer shape. It can, but SHOULD NOT target a byte, short, or long shape. Warning Do not attempt to fill response pages to meet the value provided
for the |
The following example defines a paginated operation that sets each value explicitly on the operation.
$version: "2"
namespace smithy.example
@readonly
@paginated(inputToken: "nextToken", outputToken: "nextToken",
pageSize: "maxResults", items: "foos")
operation GetFoos {
input: GetFoosInput
output: GetFoosOutput
}
@input
structure GetFoosInput {
maxResults: Integer
nextToken: String
}
@output
structure GetFoosOutput {
nextToken: String
@required
foos: StringList
}
list StringList {
member: String
}
Attaching the paginated
trait to a service provides default pagination
configuration settings to all paginated
operations bound within the closure
of the service. Pagination settings configured on an operation override any
inherited service setting.
The following example defines a paginated operation that inherits some settings from a service.
$version: "2"
namespace smithy.example
@paginated(inputToken: "nextToken", outputToken: "nextToken",
pageSize: "maxResults")
service Example {
version: "2019-06-27"
operations: [GetFoos]
}
@readonly @paginated(items: "foos")
operation GetFoos {
input: GetFoosInput
output: GetFoosOutput
}
The values for outputToken
and items
are paths. Paths are a series of
identifiers separated by dots (.
) where each identifier represents a
member name in a structure. The first member name MUST correspond to a member
of the output structure and each subsequent member name MUST correspond to a
member in the previously referenced structure. Paths MUST adhere to the
following ABNF.
path =Identifier
*("."Identifier
)
The following example defines a paginated operation which uses a result wrapper where the output token and items are referenced by paths.
$version: "2"
namespace smithy.example
@readonly
@paginated(inputToken: "nextToken", outputToken: "result.nextToken",
pageSize: "maxResults", items: "result.foos")
operation GetFoos {
input: GetFoosInput,
output: GetFoosOutput
}
@input
structure GetFoosInput {
maxResults: Integer,
nextToken: String
}
@output
structure GetFoosOutput {
@required
result: ResultWrapper
}
structure ResultWrapper {
nextToken: String,
@required
foos: StringList,
}
list StringList {
member: String
}
9.2.2. Pagination Behavior#
- If an operation returns a naturally size-limited subset of data (e.g., a top-ten list of users sorted by rank), then the operation SHOULD NOT be paginated.
- Only one list or map per operation can be paginated.
- Paginated responses SHOULD NOT return the same item of a paginated result set more than once.
- Services SHOULD NOT return items in a paginated result set that have been deleted during the pagination process, but before reaching the relevant page.
- Services MAY include newly created items in a paginated result set on a not yet seen page. If pagination is ordered and newly created items are returned, then newly created items MUST appear in order on the appropriate page.
9.2.3. Client behavior#
Smithy clients SHOULD provide abstractions that can be used to automatically iterate over paginated responses. The following steps describe the process a client MUST follow when iterating over paginated API calls:
- Send the initial request to a paginated operation. This request MAY include input parameters that are used to influence the starting point at which pagination occurs.
- If the received response does not contain a continuation token in the
referenced
outputToken
member (either the member is not set or is set to an empty value), then there are no more results to retrieve and the process is complete. - If there is a continuation token in the referenced
outputToken
member of the response, then the client sends a subsequent request using the same input parameters as the original call, but including the last received continuation token. Clients are free to change the designatedpageSize
input parameter at this step as needed. - If a client receives an identical continuation token from a service in back to back calls, then the client MAY choose to stop sending requests. This scenario implies a "tail" style API operation where clients are running in an infinite loop to send requests to a service in order to retrieve results as they are available.
- Return to step 2.
9.2.4. Continuation tokens#
The paginated
trait indicates that an operation utilizes cursor-based
pagination. When a paginated operation truncates its output, it MUST return a
continuation token in the operation output that can be used to get the next
page of results. This token can then be provided along with the original input
to request additional results from the operation.
Continuation tokens SHOULD be opaque.
Plain text continuation tokens inappropriately expose implementation details to the client, resulting in consumers building systems that manually construct continuation tokens. Making backwards compatible changes to a plain text continuation token format is extremely hard to manage.
Continuation tokens SHOULD be versioned.
The parameters and context needed to paginate an API call can evolve over time. To future-proof these APIs, services SHOULD include some kind of version identifier in their continuation tokens. Once the version identifier of a token is recognized, a service will then know the appropriate operation for decoding and returning the next response for a paginated request.
Continuation tokens SHOULD expire after a period of time.
Continuation tokens SHOULD expire after a short period of time (e.g., 24 hours is a reasonable default for many services). This allows services to quickly phase out deprecated continuation token formats, and helps to set the expectation that continuation tokens are ephemeral and MUST NOT be used after extended periods of time. Services MUST reject a request with a client error when a client uses an expired continuation token.
Continuation tokens MUST be bound to a fixed set of filtering parameters.
Services MUST reject a request that changes filtering input parameters while paging through responses. Services MUST require clients to send the same filtering request parameters used in the initial pagination request to all subsequent pagination requests.
Filtering parameters are defined as parameters that remove certain elements from appearing in the result set of a paginated API call. Filtering parameters do not influence the presentation of results (e.g., the designated
pageSize
input parameter partitions a result set into smaller subsets but does not change the sum of the parts). Services MUST allow clients to change presentation based parameters while paginating through a result set.Continuation tokens MUST NOT influence authorization.
A service MUST NOT evaluate authorization differently depending on the presence, absence, or contents of a continuation token.
9.2.5. Backward compatibility#
Many tools use the paginated
trait to expose additional functionality to
things like generated code. To support these use cases, the following changes
to the paginated
trait are considered backward incompatible:
- Removing the
paginated
trait. - Adding, removing, or changing the
inputToken
,outputToken
, oritems
members. - Removing or changing the
pageSize
member.
The following changes are considered backward compatible:
- Adding the
paginated
trait to an existing operation. - Adding the
pageSize
member to an existingpaginated
trait.
9.3. Compression#
Compression is the process of encoding, restructuring or otherwise modifying data in order to reduce its size and bandwidth capacity, with content encodings referenced in RFC 9110.
Smithy supports operations compressing requests from clients to services through the requestCompression trait.
9.3.1. requestCompression
trait#
- Summary
- Indicates that an operation supports compressing requests from clients to services.
- Trait selector
operation
- Value type
structure
- Validation
- Operation input members must not have both the streaming trait and
requiresLength trait applied.
This avoids the client reading and compressing the entire stream for the
length of the compressed data to set the
Content-Length
header. encodings
must not be empty, and all members must be valid, case-insensitive, supported compression algorithm values.
- Operation input members must not have both the streaming trait and
requiresLength trait applied.
This avoids the client reading and compressing the entire stream for the
length of the compressed data to set the
The requestCompression
trait is a structure that contains the following
members:
Property | Type | Description |
---|---|---|
encodings | [string] |
Defines the priority-ordered list of compression algorithms supported by the service operation. Supported compression algorithms are: "gzip". |
The following example defines an operation that supports gzip
compression
for requests from clients to services with both a streaming and non-streaming
member.
$version: "2"
namespace smithy.example
@requestCompression(
encodings: ["gzip"]
)
operation GetFoos {
input := {
streamingMember: StreamingFoo
member: String
}
}
@streaming
blob StreamingFoo
9.3.2. Request compression behavior#
Operations with the requestCompression
trait applied direct clients to
compress requests sent to services and set the corresponding encoding in the
Content-Encoding
header.
Operations with the requestCompression
trait applied have request
compression enabled by default, and clients SHOULD attempt to compress a request
when a compression algorithm is supported (see DISABLE_REQUEST_COMPRESSION
in Client Implementation to disable request compression).
Since compression is NOT required, services supporting operations with the
requestCompression
trait applied MUST be able to receive non-compressed
requests, for cases such as clients not supporting certain compression
algorithms or older versions of clients that have out-of-date supported
compression algorithms.
The client MUST only compress the request using the first supported algorithm in
encodings
and stop considering algorithms that appear later in the list.
If a client does compress the request, they MUST also set the
Content-Encoding
header per RFC 9110#section-8.4:
If one or more encodings have been applied to a representation, the sender that applied the encodings MUST generate a Content-Encoding header field that lists the content codings in the order in which they were applied.
If a request has a structure member bound to the Content-Encoding
header,
the trait MAY still be applied. If the request is compressed, then the
compression encoding MUST be appended to the Content-Encoding
header after
any user-provided encoding(s).
As an example, using the PutWithContentEncoding
operation below with the
customEncoding
member set to brotli
, the header would be
"Content-Encoding": "brotli, gzip"
.
$version: "2"
namespace smithy.example
@requestCompression(
encodings: ["gzip"]
)
operation PutWithContentEncoding {
input: PutWithContentEncodingInput
}
@input
structure PutWithContentEncodingInput {
@httpHeader("Content-Encoding")
customEncoding: String // brotli
@httpPayload
data: String
}
Clients MUST encode the request content prior to request signing, otherwise the request signature will mismatch.
Because small payloads may become longer when compressed, operations SHOULD only
be compressed when the payload size is greater than or equal to the value of the
minimum compression threshold (see REQUEST_MIN_COMPRESSION_SIZE_BYTES
in
Client Implementation).
If the request contains a member with the streaming trait applied without the requiresLength trait applied, the request MUST be compressed if enabled regardless of the minimum compression threshold.
Operations with input members with both streaming trait and
requiresLength trait are not allowed to use request compression. This
avoids the client reading and compressing the entire stream for the length of
the compressed data to set the Content-Length
header.
9.3.3. Client Implementation#
Warning
The client configuration settings defined in this specification are still evolving and subject to change.
Smithy clients MUST expose a setting DISABLE_REQUEST_COMPRESSION
to disable
request compression. The value MUST be a boolean value which defaults to
false
. In order of precedence from highest to lowest, the setting shall be
exposed:
- Per-request configuration (SHOULD, if request-level configuration is supported by the client)
- Service client configuration -
DisableRequestCompression
(MUST, with idiomatic casing/spacing for that client programming language)
Smithy clients MUST expose a setting REQUEST_MIN_COMPRESSION_SIZE_BYTES
to
specify the minimum size in bytes that a request body should be to trigger
compression. The value MUST be a non-negative integer in the range 0
and
10485760
inclusive, and defaults to 10240
bytes. If the value is outside
of the range, the client MUST throw an error. In order of precedence from
highest to lowest, the setting shall be exposed:
- Per-request configuration (SHOULD, if request-level configuration is supported by that client)
- Service client configuration -
RequestMinCompressionSizeBytes
(MUST, with idiomatic casing/spacing for that client programming language)
If a client's language or standard library does not support a compression algorithm, they are not required to implement that algorithm. If none of the compression algorithms are supported by the client, then the request does not need to be compressed.
If the client's language or standard library exposes a "level" or "quality" setting to control the tradeoff between speed and the compressed size, clients MUST set this setting to an idiomatic default (either the default value for their language, or else a "balanced" mode).
9.3.4. Backward compatibility#
Once a compression algorithm is added to encodings
, the algorithm SHOULD NOT
be removed. If an algorithm is removed, the service MUST continue to support the
removed algorithm on the server-side because older clients or non-official
clients may continue sending compressed requests.