macro_rules! delegate_storage {
(to $field:tt; $($method:ident),* $(,)?) => { ... };
(@one $f:tt, get_client) => { ... };
(@one $f:tt, put_client) => { ... };
(@one $f:tt, compare_and_swap_client) => { ... };
(@one $f:tt, delete_client) => { ... };
(@one $f:tt, put_device_grant) => { ... };
(@one $f:tt, get_device_grant) => { ... };
(@one $f:tt, find_device_grant_by_user_code) => { ... };
(@one $f:tt, take_device_grant) => { ... };
(@one $f:tt, compare_and_swap_device_grant) => { ... };
(@one $f:tt, put_authorization_code) => { ... };
(@one $f:tt, compare_and_swap_authorization_code) => { ... };
(@one $f:tt, take_authorization_code) => { ... };
(@one $f:tt, put_pushed_authorization_request) => { ... };
(@one $f:tt, take_pushed_authorization_request) => { ... };
(@one $f:tt, put_token) => { ... };
(@one $f:tt, get_token) => { ... };
(@one $f:tt, delete_token) => { ... };
(@one $f:tt, put_refresh_token) => { ... };
(@one $f:tt, get_refresh_token) => { ... };
(@one $f:tt, take_refresh_token) => { ... };
(@one $f:tt, revoke_token_family) => { ... };
(@one $f:tt, put_consent) => { ... };
(@one $f:tt, compare_and_swap_consent) => { ... };
(@one $f:tt, get_consent) => { ... };
(@one $f:tt, find_consent) => { ... };
(@one $f:tt, consents_for_subject) => { ... };
(@one $f:tt, revoke_consent) => { ... };
(@one $f:tt, claim_replay_id) => { ... };
(@one $f:tt, sweep_expired) => { ... };
}Expand description
Forward the named crate::store::Storage methods to an inner store.
§Usage
Name the FIELD holding the inner store, then the methods to forward. Anything you do NOT list, you write
yourself in the same impl block. Listing is EXPLICIT rather than “everything except”, and
that is deliberate: macro_rules! cannot subtract a set, and more to the point a host reading
this call should be able to see which methods are their own without cross-referencing the
trait. If you forget one, the compiler names it, which is exactly the signal the trait’s
no-defaults rule exists to produce.
use oauth_as::store::{MemoryStorage, Storage, StorageError, WriteOutcome};
use oauth_as::{ClientId, IssuedToken};
/// Counts issuance, and is otherwise an ordinary in-memory store.
struct CountingStore {
inner: MemoryStorage,
issued: std::sync::atomic::AtomicU64,
}
impl Storage for CountingStore {
// The one method this store is actually for.
async fn put_token(&self, token: IssuedToken) -> Result<WriteOutcome, StorageError> {
let outcome = self.inner.put_token(token).await?;
if outcome.is_applied() {
self.issued.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
Ok(outcome)
}
// Everything else, including the feature-gated methods: each gated one is generated only
// when `oauth-as` itself has the feature, so naming one you have not enabled produces
// nothing rather than an error. Your OWN crate's features are not consulted and do not
// need to exist.
oauth_as::delegate_storage! {
to inner;
get_client, put_client, compare_and_swap_client, delete_client,
put_device_grant, get_device_grant, find_device_grant_by_user_code,
take_device_grant, compare_and_swap_device_grant,
put_authorization_code, compare_and_swap_authorization_code,
take_authorization_code,
put_pushed_authorization_request, take_pushed_authorization_request,
get_token, delete_token,
put_refresh_token, get_refresh_token, take_refresh_token, revoke_token_family,
put_consent, compare_and_swap_consent, get_consent, find_consent,
consents_for_subject, revoke_consent,
claim_replay_id,
sweep_expired,
}
}§What it does not do
It forwards. It does not make two stores atomic with respect to each other, and no macro
could: if clients live in Postgres and codes live in memory, then
crate::store::Storage::delete_client’s cascade spans both, and the trait requires that
cascade to be ONE event. A host splitting a store across backends owns that problem, and
crate::storage_conformance is how they find out whether they have solved it.
The feature-gated methods are forwarded only when the feature is on IN oauth-as, so a name
listed under a feature you have not enabled is simply not generated. Listing one you do not have
is not an error; it produces nothing.
“in oauth-as” is the load-bearing part and it has to be said, because the obvious reading is
wrong and was ALSO what the macro did until this was fixed: the gate is NOT your crate’s feature
set. Your crate does not need a feature called par and almost certainly does not have one.
Note also that this means cargo FEATURE UNIFICATION can turn a forwarder on: an unrelated
dependency enabling consent enables it for your build of oauth-as too, and the macro will
then generate put_consent and the rest. That is the correct outcome, since the trait grew the
methods at the same moment, and it is the reason this macro is worth having.
§Why it takes a FIELD and not an expression
to inner, not to self.inner, and that is macro hygiene rather than taste. A self written
at the call site is a different self from the one in the generated method, so an expression
containing it does not resolve and the error (expected value, found module self) points at
the macro rather than at anything a host can act on. Taking the field name lets the macro build
self.$f itself, out of its own self. A tuple field works too: to 0.