Skip to main content

aws_smithy_runtime_api_macros/
lib.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_cfg))]
8/* End of automatically managed default lints */
9
10//! Proc macros for `aws-smithy-runtime-api`.
11
12use proc_macro::TokenStream;
13use quote::quote;
14use syn::{parse_macro_input, ImplItem, ItemImpl};
15
16// If you update this list, also update:
17//   - `OverriddenHooks` constants in `aws-smithy-runtime-api/src/client/interceptors.rs`
18//   - Hook methods on the `Intercept` trait in the same file
19const KNOWN_HOOKS: &[&str] = &[
20    "read_before_execution",
21    "modify_before_serialization",
22    "read_before_serialization",
23    "read_after_serialization",
24    "modify_before_retry_loop",
25    "read_before_attempt",
26    "modify_before_signing",
27    "read_before_signing",
28    "read_after_signing",
29    "modify_before_transmit",
30    "read_before_transmit",
31    "read_after_transmit",
32    "modify_before_deserialization",
33    "read_before_deserialization",
34    "read_after_deserialization",
35    "modify_before_attempt_completion",
36    "read_after_attempt",
37    "modify_before_completion",
38    "read_after_execution",
39];
40
41const _: () = assert!(
42    KNOWN_HOOKS.len() <= 32,
43    "OverriddenHooks uses a u32 bitmask; widen to u64 in interceptors.rs if more hooks are needed"
44);
45
46/// Expands to the canonical list of interceptor hook names, in lifecycle order.
47///
48/// A proc-macro crate cannot export a `const`, so this is how `KNOWN_HOOKS` is made available to
49/// callers -- notably the hook expectation matrix test, which asserts its rows against this list so
50/// that adding a hook cannot silently leave it untested.
51#[proc_macro]
52pub fn known_hooks(_input: TokenStream) -> TokenStream {
53    let names = KNOWN_HOOKS.iter();
54    quote::quote! { &[#(#names),*] }.into()
55}
56
57/// Automatically generates an `overridden_hooks()` method on an `impl Intercept` block
58/// based on which hook methods are overridden.
59///
60/// This attribute must be placed on an `impl Intercept for T` block. It inspects
61/// which hook methods are overridden and generates a corresponding
62/// `overridden_hooks()` method that returns the correct `OverriddenHooks` bitmask.
63///
64/// # Example
65/// ```ignore
66/// #[dyn_dispatch_hint]
67/// impl Intercept for MyInterceptor {
68///     fn name(&self) -> &'static str { "MyInterceptor" }
69///     fn modify_before_signing(...) -> ... { ... }
70/// }
71/// // Generates: fn overridden_hooks(&self) -> OverriddenHooks { OverriddenHooks::MODIFY_BEFORE_SIGNING }
72/// ```
73#[proc_macro_attribute]
74pub fn dyn_dispatch_hint(_attr: TokenStream, item: TokenStream) -> TokenStream {
75    let mut impl_block = parse_macro_input!(item as ItemImpl);
76
77    let overridden: Vec<String> = impl_block
78        .items
79        .iter()
80        .filter_map(|item| {
81            if let ImplItem::Fn(method) = item {
82                let name = method.sig.ident.to_string();
83                if KNOWN_HOOKS.contains(&name.as_str()) {
84                    Some(name)
85                } else {
86                    None
87                }
88            } else {
89                None
90            }
91        })
92        .collect();
93
94    let flags: Vec<proc_macro2::TokenStream> = overridden
95        .iter()
96        .map(|name| {
97            let upper = name.to_uppercase();
98            let ident = syn::Ident::new(&upper, proc_macro2::Span::call_site());
99            quote! { ::aws_smithy_runtime_api::client::interceptors::OverriddenHooks::#ident }
100        })
101        .collect();
102
103    let body = if flags.is_empty() {
104        quote! { ::aws_smithy_runtime_api::client::interceptors::OverriddenHooks::none() }
105    } else {
106        quote! { #(#flags)|* }
107    };
108
109    let method: ImplItem = syn::parse_quote! {
110        fn overridden_hooks(&self) -> ::aws_smithy_runtime_api::client::interceptors::OverriddenHooks {
111            #body
112        }
113    };
114    impl_block.items.push(method);
115
116    quote! { #impl_block }.into()
117}