Skip to content

Commit 13b10d5

Browse files
authored
Add RFC for improving request ID access in SDK clients (#2083)
1 parent e84ef6c commit 13b10d5

File tree

4 files changed

+251
-4
lines changed

4 files changed

+251
-4
lines changed

design/src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
- [RFC-0029: Finding New Home for Credential Types](./rfcs/rfc0029_new_home_for_cred_types.md)
5555
- [RFC-0030: Serialization And Deserialization](./rfcs/rfc0030_serialization_and_deserialization.md)
5656
- [RFC-0031: Providing Fallback Credentials on Timeout](./rfcs/rfc0031_providing_fallback_credentials_on_timeout.md)
57-
- [RFC-0032: Better Constraint Violations](./rfc0031_better_constraint_violations.md)
57+
- [RFC-0032: Better Constraint Violations](./rfcs/rfc0032_better_constraint_violations.md)
58+
- [RFC-0033: Improving access to request IDs in SDK clients](./rfcs/rfc0033_improve_sdk_request_id_access.md)
5859

5960
- [Contributing](./contributing/overview.md)
6061
- [Writing and debugging a low-level feature that relies on HTTP](./contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md)

design/src/rfcs/overview.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@
4141
- [RFC-0029: Finding New Home for Credential Types](./rfc0029_new_home_for_cred_types.md)
4242
- [RFC-0030: Serialization And Deserialization](./rfc0030_serialization_and_deserialization.md)
4343
- [RFC-0031: Providing Fallback Credentials on Timeout](./rfc0031_providing_fallback_credentials_on_timeout.md)
44-
- [RFC-0032: Better Constraint Violations](./rfc0031_better_constraint_violations.md)
44+
- [RFC-0032: Better Constraint Violations](./rfc0032_better_constraint_violations.md)
45+
- [RFC-0033: Improving access to request IDs in SDK clients](./rfc0033_improve_sdk_request_id_access.md)
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
RFC: Improving access to request IDs in SDK clients
2+
===================================================
3+
4+
> Status: Implemented in [#2129](https://github.com/awslabs/smithy-rs/pull/2129)
5+
>
6+
> Applies to: AWS SDK clients
7+
8+
At time of writing, customers can retrieve a request ID in one of four ways in the Rust SDK:
9+
10+
1. For error cases where the response parsed successfully, the request ID can be retrieved
11+
via accessor method on operation error. This also works for unmodeled errors so long as
12+
the response parsing succeeds.
13+
2. For error cases where a response was received but parsing fails, the response headers
14+
can be retrieved from the raw response on the error, but customers have to manually extract
15+
the request ID from those headers (there's no convenient accessor method).
16+
3. For all error cases where the request ID header was sent in the response, customers can
17+
call `SdkError::into_service_error` to transform the `SdkError` into an operation error,
18+
which has a `request_id` accessor on it.
19+
4. For success cases, the customer can't retrieve the request ID at all if they use the fluent
20+
client. Instead, they must manually make the operation and call the underlying Smithy client
21+
so that they have access to `SdkSuccess`, which provides the raw response where the request ID
22+
can be manually extracted from headers.
23+
24+
Only one of these mechanisms is convenient and ergonomic. The rest need considerable improvements.
25+
Additionally, the request ID should be attached to tracing events where possible so that enabling
26+
debug logging reveals the request IDs without any code changes being necessary.
27+
28+
This RFC proposes changes to make the request ID easier to access.
29+
30+
Terminology
31+
-----------
32+
33+
- **Request ID:** A unique identifier assigned to and associated with a request to AWS that is
34+
sent back in the response headers. This identifier is useful to customers when requesting support.
35+
- **Operation Error:** Operation errors are code generated for each operation in a Smithy model.
36+
They are an enum of every possible modeled error that that operation can respond with, as well
37+
as an `Unhandled` variant for any unmodeled or unrecognized errors.
38+
- **Modeled Errors:** Any error that is represented in a Smithy model with the `@error` trait.
39+
- **Unmodeled Errors:** Errors that a service responds with that do not appear in the Smithy model.
40+
- **SDK Clients:** Clients generated for the AWS SDK, including "adhoc" or "one-off" clients.
41+
- **Smithy Clients:** Any clients not generated for the AWS SDK, excluding "adhoc" or "one-off" clients.
42+
43+
SDK/Smithy Purity
44+
-----------------
45+
46+
Before proposing any changes, the topic of purity needs to be covered. Request IDs are not
47+
currently a Smithy concept. However, at time of writing, the request ID concept is leaked into
48+
the non-SDK rust runtime crates and generated code via the [generic error] struct and the
49+
`request_id` functions on generated operation errors (e.g., [`GetObjectError` example in S3]).
50+
51+
This RFC attempts to remove these leaks from Smithy clients.
52+
53+
Proposed Changes
54+
----------------
55+
56+
First, we'll explore making it easier to retrieve a request ID from errors,
57+
and then look at making it possible to retrieve them from successful responses.
58+
To see the customer experience of these changes, see the **Example Interactions**
59+
section below.
60+
61+
### Make request ID retrieval on errors consistent
62+
63+
One could argue that customers being able to convert a `SdkError` into an operation error
64+
that has a request ID on it is sufficient. However, there's no way to write a function
65+
that takes an error from any operation and logs a request ID, so it's still not ideal.
66+
67+
The `aws-http` crate needs to have a `RequestId` trait on it to facilitate generic
68+
request ID retrieval:
69+
70+
```rust
71+
pub trait RequestId {
72+
/// Returns the request ID if it's available.
73+
fn request_id(&self) -> Option<&str>;
74+
}
75+
```
76+
77+
This trait will be implemented for `SdkError` in `aws-http` where it is declared,
78+
complete with logic to pull the request ID header out of the raw HTTP responses
79+
(it will always return `None` for event stream `Message` responses; an additional
80+
trait may need to be added to `aws-smithy-http` to facilitate access to the headers).
81+
This logic will try different request ID header names in order of probability
82+
since AWS services have a couple of header name variations. `x-amzn-requestid` is
83+
the most common, with `x-amzn-request-id` being the second most common.
84+
85+
`aws-http` will also implement `RequestId` for `aws_smithy_types::error::Error`,
86+
and the `request_id` method will be removed from `aws_smithy_types::error::Error`.
87+
Places that construct `Error` will place the request ID into its `extras` field,
88+
where the `RequestId` trait implementation can retrieve it.
89+
90+
A codegen decorator will be added to `sdk-codegen` to implement `RequestId` for
91+
operation errors, and the existing `request_id` accessors will be removed from
92+
`CombinedErrorGenerator` in `codegen-core`.
93+
94+
With these changes, customers can directly access request IDs from `SdkError` and
95+
operations errors by importing the `RequestId` trait. Additionally, the Smithy/SDK
96+
purity is improved since both places where request IDs are leaked to Smithy clients
97+
will be resolved.
98+
99+
### Implement `RequestId` for outputs
100+
101+
To make it possible to retrieve request IDs when using the fluent client, the new
102+
`RequestId` trait can be implemented for outputs.
103+
104+
Some services (e.g., Transcribe Streaming) model the request ID header in their
105+
outputs, while other services (e.g., Directory Service) model a request ID
106+
field on errors. In some cases, services take `RequestId` as a modeled input
107+
(e.g., IoT Event Data). It follows that it is possible, but unlikely, that
108+
a service could have a field named `RequestId` that is not the same concept
109+
in the future.
110+
111+
Thus, name collisions are going to be a concern for putting a request ID accessor
112+
on output. However, if it is implemented as a trait, then this concern is partially
113+
resolved. In the vast majority of cases, importing `RequestId` will provide the
114+
accessor without any confusion. In cases where it is already modeled and is the
115+
same concept, customers will likely just use it and not even realize they didn't
116+
import the trait. The only concern is future cases where it is modeled as a
117+
separate concept, and as long as customers don't import `RequestId` for something
118+
else in the same file, that confusion can be avoided.
119+
120+
In order to implement `RequestId` for outputs, either the original response needs
121+
to be stored on the output, or the request ID needs to be extracted earlier and
122+
stored on the output. The latter will lead to a small amount of header lookup
123+
code duplication.
124+
125+
In either case, the `StructureGenerator` needs to be customized in `sdk-codegen`
126+
(Appendix B outlines an alternative approach to this and why it was dismissed).
127+
This will be done by adding customization hooks to `StructureGenerator` similar
128+
to the ones for `ServiceConfigGenerator` so that a `sdk-codegen` decorator can
129+
conditionally add fields and functions to any generated structs. A hook will
130+
also be needed to additional trait impl blocks.
131+
132+
Once the hooks are in place, a decorator will be added to store either the original
133+
response or the request ID on outputs, and then the `RequestId` trait will be
134+
implemented for them. The `ParseResponse` trait implementation will be customized
135+
to populate this new field.
136+
137+
Note: To avoid name collisions of the request ID or response on the output struct,
138+
these fields can be prefixed with an underscore. It shouldn't be possible for SDK
139+
fields to code generate with this prefix given the model validation rules in place.
140+
141+
### Implement `RequestId` for `Operation` and `operation::Response`
142+
143+
In the case that a customer wants to ditch the fluent client, it should still
144+
be easy to retrieve a request ID. To do this, `aws-http` will provide `RequestId`
145+
implementations for `Operation` and `operation::Response`. These implementations
146+
will likely make the other `RequestId` implementations easier to implement as well.
147+
148+
### Implement `RequestId` for `Result`
149+
150+
The `Result` returned by the SDK should directly implement `RequestId` when both
151+
its `Ok` and `Err` variants implement `RequestId`. This will make it possible
152+
for a customer to feed the return value from `send()` directly to a request ID logger.
153+
154+
Example Interactions
155+
--------------------
156+
157+
### Generic Handling Case
158+
159+
```rust
160+
// A re-export of the RequestId trait
161+
use aws_sdk_service::primitives::RequestId;
162+
163+
fn my_request_id_logging_fn(request_id: &dyn RequestId) {
164+
println!("request ID: {:?}", request_id.request_id());
165+
}
166+
167+
let result = client.some_operation().send().await?;
168+
my_request_id_logging_fn(&result);
169+
```
170+
171+
### Success Case
172+
173+
```rust
174+
use aws_sdk_service::primitives::RequestId;
175+
176+
let output = client.some_operation().send().await?;
177+
println!("request ID: {:?}", output.request_id());
178+
```
179+
180+
### Error Case with `SdkError`
181+
182+
```rust
183+
use aws_sdk_service::primitives::RequestId;
184+
185+
match client.some_operation().send().await {
186+
Ok(_) => { /* handle OK */ }
187+
Err(err) => {
188+
println!("request ID: {:?}", output.request_id());
189+
}
190+
}
191+
```
192+
193+
### Error Case with operation error
194+
195+
```rust
196+
use aws_sdk_service::primitives::RequestId;
197+
198+
match client.some_operation().send().await {
199+
Ok(_) => { /* handle OK */ }
200+
Err(err) => match err.into_service_err() {
201+
err @ SomeOperationError::SomeError(_) => { println!("request ID: {:?}", err.request_id()); }
202+
_ => { /* don't care */ }
203+
}
204+
}
205+
```
206+
207+
Changes Checklist
208+
-----------------
209+
210+
- [x] Create the `RequestId` trait in `aws-http`
211+
- [x] Implement for errors
212+
- [x] Implement `RequestId` for `SdkError` in `aws-http`
213+
- [x] Remove `request_id` from `aws_smithy_types::error::Error`, and store request IDs in its `extras` instead
214+
- [x] Implement `RequestId` for `aws_smithy_types::error::Error` in `aws-http`
215+
- [x] Remove generation of `request_id` accessors from `CombinedErrorGenerator` in `codegen-core`
216+
- [x] Implement for outputs
217+
- [x] Add customization hooks to `StructureGenerator`
218+
- [x] Add customization hook to `ParseResponse`
219+
- [x] Add customization hook to `HttpBoundProtocolGenerator`
220+
- [x] Customize output structure code gen in `sdk-codegen` to add either a request ID or a response field
221+
- [x] Customize `ParseResponse` in `sdk-codegen` to populate the outputs
222+
- [x] Implement `RequestId` for `Operation` and `operation::Response`
223+
- [x] Implement `RequestId` for `Result<O, E>` where `O` and `E` both implement `RequestId`
224+
- [x] Re-export `RequestId` in generated crates
225+
- [x] Add integration tests for each request ID access point
226+
227+
Appendix A: Alternate solution for access on successful responses
228+
-----------------------------------------------------------------
229+
230+
Alternatively, for successful responses, a second `send` method (that is difficult to name)w
231+
be added to the fluent client that has a return value that includes both the output and
232+
the request ID (or entire response).
233+
234+
This solution was dismissed due to difficulty naming, and the risk of name collision.
235+
236+
Appendix B: Adding `RequestId` as a string to outputs via model transform
237+
-------------------------------------------------------------------------
238+
239+
The request ID could be stored on outputs by doing a model transform in `sdk-codegen` to add a
240+
`RequestId` member field. However, this causes problems when an output already has a `RequestId` field,
241+
and requires the addition of a synthetic trait to skip binding the field in the generated
242+
serializers/deserializers.
243+
244+
[generic error]: https://docs.rs/aws-smithy-types/0.51.0/aws_smithy_types/error/struct.Error.html
245+
[`GetObjectError` example in S3]: https://docs.rs/aws-sdk-s3/0.21.0/aws_sdk_s3/error/struct.GetObjectError.html#method.request_id

tools/ci-build/crate-hasher/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)