Skip to main content

delegate_storage

Macro delegate_storage 

Source
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.