Skip to content

Credential listing

rtb_app::credentials is how a tool tells the framework which credentials it knows about, so credentials list and friends have something to show.

use rtb_app::credentials::{list_or_empty, CredentialProvider, NoCredentials};

What this module does not do

It enumerates credentials. It does not resolve them, read them from a keychain, validate them, or redact them. Resolution lives in rtb-credentials; this module stores a listing so App::credentials() has an answer.

CredentialProvider

pub trait CredentialProvider: Send + Sync {
    fn list(&self) -> Vec<(String, CredentialRef)>;
}

The object App stores, as Option<Arc<dyn CredentialProvider>>. It returns owned pairs, which is the whole reason it exists as a separate trait from rtb_credentials::CredentialBearing.

You implement CredentialBearing, not this

There is a blanket implementation:

impl<T> CredentialProvider for T
where T: CredentialBearing + Send + Sync + 'static

So the thing to write on your config struct is CredentialBearing:

use rtb_credentials::{CredentialBearing, CredentialRef};

impl CredentialBearing for MyConfig {
    fn credentials(&self) -> Vec<(&'static str, &CredentialRef)> {
        vec![("anthropic", &self.anthropic), ("github", &self.github)]
    }
}

Arc::new(my_config) then coerces to Arc<dyn CredentialProvider> with no further work.

The consequence of a blanket impl is worth stating plainly: you cannot write your own impl CredentialProvider for a type that already implements CredentialBearing. The two would overlap and the compiler rejects it. Implement CredentialProvider directly only for types that are not CredentialBearing.

Why there are two traits at all

CredentialBearing::credentials returns Vec<(&'static str, &CredentialRef)> — borrows tied to the provider's lifetime. That is the right shape to implement on a config struct, and the wrong shape to store behind a dyn on App, because there is no lifetime for those borrows to anchor to. CredentialProvider is the owned dual, and the blanket impl does the conversion so nobody writes it twice.

NoCredentials

#[derive(Default)]
pub struct NoCredentials;

A provider that lists nothing. Useful when a type is required but there is nothing to report. Note that it is not what App::for_testing installs — that leaves credentials_provider as None. The two are indistinguishable from the outside, since both produce an empty listing.

list_or_empty

pub fn list_or_empty(provider: Option<&Arc<dyn CredentialProvider>>)
    -> Vec<(String, CredentialRef)>;

The helper behind App::credentials(): the provider's listing when Some, an empty Vec when None. Exposed so tests can exercise the same path without an App.

What an empty listing means

An empty result is not an error and is not distinguishable from "no provider wired". A tool that has not implemented CredentialBearing yet reports the empty set, and credentials list shows nothing rather than failing — which is the right behaviour for a tool that has not declared any credentials, but does mean an empty listing cannot be read as "this tool has no credentials configured".