diff --git a/.changes/next-release/documentation-cb5e01785b74682fd9c538213573557a68d56c31.json b/.changes/next-release/documentation-cb5e01785b74682fd9c538213573557a68d56c31.json new file mode 100644 index 00000000000..e95c5b5be4b --- /dev/null +++ b/.changes/next-release/documentation-cb5e01785b74682fd9c538213573557a68d56c31.json @@ -0,0 +1,7 @@ +{ + "type": "documentation", + "description": "Add client guidance for context types.", + "pull_requests": [ + "[#2924](https://github.com/smithy-lang/smithy/pull/2924)" + ] +} diff --git a/docs/source-2.0/guides/client-guidance/application-protocols/http.md b/docs/source-2.0/guides/client-guidance/application-protocols/http.md index ceac08f4a0e..fcb515a392b 100644 --- a/docs/source-2.0/guides/client-guidance/application-protocols/http.md +++ b/docs/source-2.0/guides/client-guidance/application-protocols/http.md @@ -29,8 +29,9 @@ public interface HttpClient implements ClientTransport` in Java, +`dict` in Python, or similar structures in other languages). A given string key +still maps to a specific value type and serves the same purpose, but now the +context is open to extension without changing core library code. + +While this sort of open map usage may be common in some languages, the lack of +type safety is a significant problem. To retain type safety, it is recommended +to use an interface that encodes the value type within the key itself using +generics or similar type system features. + +```java +/** + * A typed context bag. + */ +public interface Context { + + /** + * A key wrapper that tracks the value type it is expected to be assigned + * to. + */ + final class Key { + private final String name; + + /** + * @param name Name of the value. + */ + public Key(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + static Key key(String name) { + return new Key<>(name); + } + + /** + * Set a property. If it was already present, it is overridden. + * + * @param key Property key. + * @param value Value to set. + * @param Value type. + */ + void put(Key key, T value); + + + /** + * Get a property. + * + * @param key Property key to retrieve the value for. + * @return the value, or null if not present. + * @param Value type. + */ + T get(Key key); +} +``` + +Typed keys can then be statically defined and shared. A compiler or type checker +will validate that usage is correct, catching type mismatches at compile time +rather than runtime. These should be defined in the packages that primarily use +them. + +For example, imagine if retry tracking were extracted to a client plugin. It +could define a static `RETRY_COUNT` property that can be exposed for use by +other plugins. + +```java +public final class RetryTracker { + // Any interested client plugin could use this context key to have + // type-safe and typo-safe access to the context property. + public static final Context.Key RETRY_COUNT = Context.key("retry-count"); + + public void onAttempt(Context context) { + Integer count = context.get(RETRY_COUNT); + if (count == null) { + context.put(RETRY_COUNT, 0); + } + context.put(RETRY_COUNT, count + 1); + } +} +``` + +## Lifecycle + +Each operation invocation should create its own context object. This prevents +unintentionally leaking context into other requests and reduces the chances of +concurrency issues. + +Smithy clients should pass this context object to any integration hooks. +[TODO: link to interceptors documentation.] There should be at least one hook at +the beginning of the request pipeline to enable client plugins to populate the +context as soon as possible. diff --git a/docs/source-2.0/guides/client-guidance/index.md b/docs/source-2.0/guides/client-guidance/index.md index cb5043ba83e..35f8f914fde 100644 --- a/docs/source-2.0/guides/client-guidance/index.md +++ b/docs/source-2.0/guides/client-guidance/index.md @@ -58,4 +58,5 @@ Smithy clients should follow these tenets: :maxdepth: 1 application-protocols/index -``` \ No newline at end of file +context +```