pub trait Provider: Send + Sync {
Show 26 methods
// Required methods
fn convention_address(
&self,
project: &str,
profile: &str,
key: &str,
) -> Result<NativeAddress>;
fn get(&self, addr: Address<'_>) -> Result<Option<SecretString>>;
fn set(&self, addr: Address<'_>, value: &SecretString) -> Result<()>;
fn name(&self) -> &'static str;
fn uri(&self) -> String;
// Provided methods
fn supported_coords(&self) -> &'static [&'static str] { ... }
fn resolve_coords<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>> { ... }
fn entry_coordinates<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>> { ... }
fn set_expiring(
&self,
addr: Address<'_>,
value: &SecretString,
max_age: Duration,
) -> Result<()> { ... }
fn delete(&self, addr: Address<'_>) -> Result<bool> { ... }
fn check_deletable(&self, addr: Address<'_>) -> Result<()> { ... }
fn check_writable(&self, addr: Address<'_>) -> Result<()> { ... }
fn generated_value_persistence(&self) -> ProducedValuePersistence { ... }
fn prompted_value_persistence(&self) -> ProducedValuePersistence { ... }
fn describe_write_target(&self, addr: Address<'_>) -> Result<String> { ... }
fn auth_scope_key(&self) -> Option<String> { ... }
fn storage_identity(&self) -> String { ... }
fn entry_container_identity(&self) -> String { ... }
fn same_entry(
&self,
other: &dyn Provider,
addr: Address<'_>,
) -> Result<bool> { ... }
fn same_entries(
&self,
self_addr: Address<'_>,
other: &dyn Provider,
other_addr: Address<'_>,
) -> Result<bool> { ... }
fn physical_store_path(&self) -> Option<&Path> { ... }
fn set_reason(&self, _reason: Option<String>) { ... }
fn with_base_dir(&mut self, _base_dir: &Path) { ... }
fn with_credentials(&mut self, _credentials: HashMap<String, SecretString>) { ... }
fn reflect(
&self,
_context: DiscoveryContext<'_>,
) -> Result<HashMap<String, Secret>> { ... }
fn get_many(
&self,
requests: &[(&str, Address<'_>)],
) -> Result<HashMap<String, SecretString>> { ... }
}Expand description
Trait defining the interface for secret storage providers.
All secret storage backends must implement this trait to integrate with SecretSpec. The trait is designed to be flexible enough to support various storage mechanisms while maintaining a consistent interface.
§Thread Safety
Providers must be Send + Sync as they may be used across thread boundaries
in multi-threaded applications.
§Profile Support
Providers should support profile-based secret isolation, allowing different values for the same key across environments (e.g., development, staging, production).
§Implementation Guidelines
- Providers should handle their own error cases and return appropriate
Resulttypes - Storage paths should follow the pattern:
{provider}/{project}/{profile}/{key} - Providers may choose to be read-only by overriding
check_writable - Provider names should be lowercase and descriptive
Required Methods§
Sourcefn convention_address(
&self,
project: &str,
profile: &str,
key: &str,
) -> Result<NativeAddress>
fn convention_address( &self, project: &str, profile: &str, key: &str, ) -> Result<NativeAddress>
Compiles SecretSpec’s {project}/{profile}/{key} naming convention into
this store’s native coordinates: the same address space a secret’s
ref uses.
This is the single owner of the provider’s convention layout (format
strings, path shapes, default vaults); the operation methods resolve
every address through resolve_coords and
never re-derive names. Pure naming, no I/O.
§Errors
Returns an error when the convention inputs cannot form a valid name in this store (e.g. empty components, length limits).
Sourcefn get(&self, addr: Address<'_>) -> Result<Option<SecretString>>
fn get(&self, addr: Address<'_>) -> Result<Option<SecretString>>
Retrieves the secret named by addr.
See [Address] for the two naming schemes. A provider that cannot
interpret a Native coordinate (e.g. a field on a
store whose secrets have no sub-components) returns an error naming the
coordinate rather than guessing.
§Returns
Ok(Some(value))if the secret existsOk(None)if the secret doesn’t existErrif there was an error accessing the provider
§Example
let addr = Address::Convention { project: "myapp", profile: "production", key: "DATABASE_URL" };
match provider.get(addr)? {
Some(url) => println!("Database URL: {}", url),
None => println!("DATABASE_URL not found"),
}Sourcefn set(&self, addr: Address<'_>, value: &SecretString) -> Result<()>
fn set(&self, addr: Address<'_>, value: &SecretString) -> Result<()>
Stores a secret value at addr.
§Returns
Ok(())if the secret was successfully storedErrif there was an error or the address is read-only
§Errors
This method should return an error whenever
check_writable does, for the same address.
Sourcefn name(&self) -> &'static str
fn name(&self) -> &'static str
Returns the name of this provider.
This should match the name registered with the provider macro.
Sourcefn uri(&self) -> String
fn uri(&self) -> String
Returns the full URI representation of this provider.
This includes any configuration like vault names, paths, etc. For example: “onepassword://VaultName” or “dotenv://.env.production”
§Contract: the returned URI must be credential-free
The audit log records this URI and the fallback-chain warnings print it,
so it must never contain a secret the user embedded in the source URI
(e.g. a :password or service-account token). Reconstruct the URI from
non-secret attribution only — account, profile, namespace, host, path —
and drop any credential, which authentication resolves from the
environment or a token field instead. This contract is enforced for every
registered scheme by uri_never_echoes_a_userinfo_password in
provider::tests.
Provided Methods§
Sourcefn supported_coords(&self) -> &'static [&'static str]
fn supported_coords(&self) -> &'static [&'static str]
The optional [NativeAddress] coordinates this store can honor, beyond
the universally consumed item (e.g. ["field"]).
Declared as data rather than checked per operation: the default
resolve_coords rejects every coordinate a
provider does not name here, so a store whose secrets have no
sub-components gets the correct behavior from the empty default without
writing any validation.
Sourcefn resolve_coords<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>>
fn resolve_coords<'a>( &self, addr: Address<'a>, ) -> Result<Cow<'a, NativeAddress>>
Resolves any [Address] to this store’s native coordinates: a ref’s
coordinates pass through as-is, a convention address is compiled via
convention_address. Coordinates
outside supported_coords are rejected,
so every operation that resolves an address inherits the check.
Sourcefn entry_coordinates<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>>
fn entry_coordinates<'a>( &self, addr: Address<'a>, ) -> Result<Cow<'a, NativeAddress>>
Resolves the canonical coordinates an operation uses to identify one physical entry. Available since SecretSpec 0.19.
The default is the validated address returned by
resolve_coords. Providers that interpret
an omitted coordinate as a concrete default must override this method
and fill that default, so destructive preflight compares the same
identity that get, set, and delete operate on.
Sourcefn set_expiring(
&self,
addr: Address<'_>,
value: &SecretString,
max_age: Duration,
) -> Result<()>
fn set_expiring( &self, addr: Address<'_>, value: &SecretString, max_age: Duration, ) -> Result<()>
Writes a secret at addr that need not outlive max_age. Available
since SecretSpec 0.17.
The default ignores the hint and writes a plain value, which is always correct: SecretSpec’s own cache envelope carries the expiration time and remains the freshness authority. A provider whose store can drop a value on its own overrides this, so a cached secret stops existing even if SecretSpec never runs again — a store-side bound on how long a copy of someone else’s secret sits there.
A provider that cannot apply the expiry it was asked for must return an error rather than write an unexpiring value: the caller asked for a bounded copy, and silently storing an unbounded one is worse than not caching at all.
Sourcefn delete(&self, addr: Address<'_>) -> Result<bool>
fn delete(&self, addr: Address<'_>) -> Result<bool>
Deletes a secret at addr. Available since SecretSpec 0.17.
Providers opt into deletion explicitly. This is used by cache
invalidation and, starting in SecretSpec 0.18, by secretspec delete
and secretspec import --delete-source. It defaults to a clear
unsupported-operation error so adding the method does not silently make
destructive behavior available to every provider.
Deleting is idempotent: an address that holds nothing is Ok(false),
not an error. The bool reports whether an entry was actually removed,
so callers can tell a real invalidation from a no-op instead of counting
addresses they merely asked about.
Sourcefn check_deletable(&self, addr: Address<'_>) -> Result<()>
fn check_deletable(&self, addr: Address<'_>) -> Result<()>
Reports whether this provider can delete addr, without changing the
store. Available since SecretSpec 0.19.
Destructive multi-secret operations use this during preflight so an
unsupported native address cannot be discovered only after earlier
source entries have already been removed. Providers with deletion
policies beyond coordinate support must override this method and have
delete enforce the same policy.
Sourcefn check_writable(&self, addr: Address<'_>) -> Result<()>
fn check_writable(&self, addr: Address<'_>) -> Result<()>
Reports whether this provider can write to addr, and why not when it
cannot.
Callers use this to refuse a write before prompting for a value, so the
error must be the same one set would return: state
the policy here and have set call this method, rather than writing the
rule twice.
By default, providers are assumed to support writing. Read-only
providers (like environment variables) reject every address; providers
that can write their own layout but not externally managed secrets
reject only Native addresses, and say so — a
generic “provider is read-only” would be untrue of the store as a whole.
§Example
provider.check_writable(addr)?;
provider.set(addr, &value)?;Sourcefn generated_value_persistence(&self) -> ProducedValuePersistence
fn generated_value_persistence(&self) -> ProducedValuePersistence
Controls whether SecretSpec persists a value produced by a declaration’s
generate configuration after this provider’s read route misses.
Available since SecretSpec 0.19.
ProducedValuePersistence::Ephemeral affects only automatic
generation. Ordinary set, deletion, imports, and
provider reads keep their usual behavior. The capability must be pure:
callers may inspect it without running authentication preflight or other
provider I/O.
Sourcefn prompted_value_persistence(&self) -> ProducedValuePersistence
fn prompted_value_persistence(&self) -> ProducedValuePersistence
Controls whether a value entered for a prompt = true declaration is
stored after the provider’s read route misses. Available since
SecretSpec 0.19.
The default persists the answer through Provider::set, making the
prompt a first-use provisioning step. A provider that cannot or must not
retain values can return ProducedValuePersistence::Ephemeral so the
answer is used only by the current run resolution.
Sourcefn describe_write_target(&self, addr: Address<'_>) -> Result<String>
fn describe_write_target(&self, addr: Address<'_>) -> Result<String>
Describes the provider-native destination that a write to addr will
change. Available since SecretSpec 0.19.
The description is intended for a pre-write CLI preview and must not contain credentials. Providers with file-backed or otherwise structured storage should override this when their URI plus native coordinates do not identify the resolved file/container and selector clearly. The default renders the provider-native coordinates.
Sourcefn auth_scope_key(&self) -> Option<String>
fn auth_scope_key(&self) -> Option<String>
Identifies the shared authentication state this instance’s preflight check probes, when that state outlives the instance.
Instances of the same provider returning equal keys share one probe
result process-wide. This matters because a secret’s providers chain
builds a fresh provider instance per (secret, URI) pair — without a
scope key, N secrets would run N identical auth probes (each typically a
CLI round-trip). The default None keeps the probe per-instance.
Sourcefn storage_identity(&self) -> String
fn storage_identity(&self) -> String
Returns a credential-free identity for the physical store this provider addresses.
Unlike Self::uri, this value is not user-facing attribution. It is
used when SecretSpec must decide whether two differently configured
providers can read and write the same storage location, such as when
ensuring a cache is distinct from its authoritative sources. Authentication
choices that do not change the store must therefore not change this
identity, and protocol-compatible provider names should return the same
identity when they target the same store.
Most providers have one public spelling for a store, so the default uses their canonical URI. Providers with equivalent spellings or compatible identities should override this method.
Sourcefn entry_container_identity(&self) -> String
fn entry_container_identity(&self) -> String
Returns the identity of the container holding a resolved secret entry. Available since SecretSpec 0.18.
This differs from Self::storage_identity only for providers whose
public URI contains an addressing template. Cache routing must retain
that template so sibling address spaces remain distinct, while
destructive operations compare the template’s resolved native
coordinates separately and need the identity of the underlying
container here.
Sourcefn same_entry(&self, other: &dyn Provider, addr: Address<'_>) -> Result<bool>
fn same_entry(&self, other: &dyn Provider, addr: Address<'_>) -> Result<bool>
Returns whether self and other resolve addr to the same physical
secret entry. Available since SecretSpec 0.18.
This compatibility method applies one address to both providers. New
cross-endpoint operations should use Self::same_entries when source
and destination can have independent refs.
Sourcefn same_entries(
&self,
self_addr: Address<'_>,
other: &dyn Provider,
other_addr: Address<'_>,
) -> Result<bool>
fn same_entries( &self, self_addr: Address<'_>, other: &dyn Provider, other_addr: Address<'_>, ) -> Result<bool>
Returns whether self and other resolve their respective addresses to
the same physical secret entry. Available since SecretSpec 0.19.
Destructive cross-provider operations must use this instead of comparing
uri strings: one store may have multiple equivalent
spellings, and provider URIs can include convention templates that are
only meaningful after resolving a concrete address. The physical store
and the resolved native coordinates must both match before an entry is
considered shared.
Sourcefn physical_store_path(&self) -> Option<&Path>
fn physical_store_path(&self) -> Option<&Path>
Returns the path that identifies a filesystem-backed store, if any. Available since SecretSpec 0.18.
The path is compared using filesystem identity when it exists, catching
lexical aliases, symlinks, and hard links. Providers that are not backed
by one path keep the default and are identified by
storage_identity.
Sourcefn set_reason(&self, _reason: Option<String>)
fn set_reason(&self, _reason: Option<String>)
Records a human-readable reason for the secrets access happening in this
session (e.g. “secretspec run: deploy”), set via Secrets::with_reason.
Providers that support audit logging use this; for example the Proton Pass
provider forwards it to pass-cli agent sessions, which require a reason
for every audited item operation. The default implementation ignores it.
Takes &self (relying on interior mutability) so it can be applied after
the provider is wrapped in an Arc (as preflight-enabled providers are).
Sourcefn with_base_dir(&mut self, _base_dir: &Path)
fn with_base_dir(&mut self, _base_dir: &Path)
Rebases any relative filesystem paths the provider holds against
base_dir, the directory containing the secretspec.toml that
configured it.
File-backed providers (e.g. dotenv) take paths from the config or its
provider aliases. Those paths must resolve relative to the project root,
not the process’s current working directory — otherwise running from a
subdirectory with --file ../secretspec.toml looks for the .env file
in the wrong place. Secrets calls this once at construction, before
the provider performs any I/O. The default implementation does nothing,
which is correct for providers that hold no relative paths.
Sourcefn with_credentials(&mut self, _credentials: HashMap<String, SecretString>)
fn with_credentials(&mut self, _credentials: HashMap<String, SecretString>)
Hands semantic credentials to the provider.
Called once inside the registration factory, on the concrete provider
value before any Arc/Box wrapping. This must not be a
post-construction call on a Box<dyn Provider>: like with_base_dir,
a &mut self hook cannot be forwarded through the blanket
impl Provider for Arc<T> (an Arc gives no &mut access to its
inner value), so a preflight provider — wrapped as Box<Arc<P>> — would
silently receive the default no-op. The default implementation ignores
the values, which is correct for providers that need no credentials.
Sourcefn reflect(
&self,
_context: DiscoveryContext<'_>,
) -> Result<HashMap<String, Secret>>
fn reflect( &self, _context: DiscoveryContext<'_>, ) -> Result<HashMap<String, Secret>>
Discovers declarations using the project and profile that the new manifest will contain. Available starting with SecretSpec 0.18.
Providers whose namespace does not depend on that context can ignore
it. Hierarchical providers should use it so discovery stays inside the
same namespace as convention_address.
The default implementation returns an unsupported-operation error.
§Example
let context = DiscoveryContext::new("payments", "production");
let secrets = provider.reflect(context)?;
for (name, secret) in secrets {
println!("Found secret: {} = {:?}", name, secret);
}Sourcefn get_many(
&self,
requests: &[(&str, Address<'_>)],
) -> Result<HashMap<String, SecretString>>
fn get_many( &self, requests: &[(&str, Address<'_>)], ) -> Result<HashMap<String, SecretString>>
Retrieves multiple secrets in one batch operation.
Each request pairs a secret name (the key of the returned map) with the
[Address] to fetch it from, so a batch mixes convention secrets and
ref secrets freely. Secrets that don’t exist are omitted from the
result.
§Contract
Requests naming identical addresses (several secrets sharing one ref)
must be fetched once and share the value.
§Default Implementation
The default deduplicates identical addresses and fetches each unique address once, concurrently. Providers with a real batch surface (one listing, a bulk API) should override this to cut round-trips further.
Trait Implementations§
Source§impl TryFrom<String> for Box<dyn Provider>
impl TryFrom<String> for Box<dyn Provider>
Source§fn try_from(s: String) -> Result<Self>
fn try_from(s: String) -> Result<Self>
Creates a provider instance from a URI string.
This function handles various URI formats and normalizes them before parsing. It supports both full URIs and shorthand notations.
§URI Formats
- Full URI:
scheme://authority/path(e.g.,onepassword://Production)
§Special Cases
- 1password: Will error suggesting to use
onepasswordinstead - Bare provider names: Automatically converted to
provider://
§Examples
use std::convert::TryFrom;
// Simple provider name
let provider = Box::<dyn Provider>::try_from("keyring".to_string())?;
// Full URI with configuration
let provider = Box::<dyn Provider>::try_from("onepassword://Production".to_string())?;
// Dotenv with path
let provider = Box::<dyn Provider>::try_from("dotenv:.env.production".to_string())?;Source§type Error = SecretSpecError
type Error = SecretSpecError
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".