use super::{PROVIDER_REGISTRY, ProviderRegistration, file};
use crate::{Result, SecretSpecError};
#[derive(Debug, Clone)]
pub struct ProviderInfo {
pub name: &'static str,
#[cfg_attr(not(any(feature = "cli", test)), allow(dead_code))]
pub description: &'static str,
#[cfg_attr(not(any(feature = "cli", test)), allow(dead_code))]
pub examples: &'static [&'static str],
}
impl ProviderInfo {
#[cfg(any(feature = "cli", test))]
pub fn display_with_examples(&self) -> String {
if self.examples.is_empty() {
format!("{}: {}", self.name, self.description)
} else {
format!(
"{}: {} (e.g., {})",
self.name,
self.description,
self.examples.join(", ")
)
}
}
}
#[cfg(feature = "cli")]
pub fn providers() -> Vec<ProviderInfo> {
PROVIDER_REGISTRY
.iter()
.map(|reg| reg.metadata.info.clone())
.collect()
}
pub(super) fn split_spec(spec: &str) -> (&str, &str) {
match spec.find(':') {
Some(pos) => (&spec[..pos], &spec[pos + 1..]),
None => (spec, ""),
}
}
pub(super) fn registration_for_scheme(scheme: &str) -> Option<&'static ProviderRegistration> {
PROVIDER_REGISTRY
.iter()
.find(|reg| reg.metadata.schemes.contains(&scheme))
}
pub(crate) fn spec_names_known_provider(spec: &str) -> Result<bool> {
let (scheme, rest) = split_spec(spec);
if scheme == "1password" {
return Err(SecretSpecError::ProviderOperationFailed(
"Invalid scheme '1password'. Use 'onepassword' instead (e.g., onepassword://vault)"
.to_string(),
));
}
if scheme == "file" && (rest.is_empty() || rest == "//") {
return Err(SecretSpecError::ProviderOperationFailed(
file::MISSING_DIRECTORY_ERROR.to_string(),
));
}
Ok(registration_for_scheme(scheme).is_some())
}
pub(crate) fn credential_names_for_spec(spec: &str) -> &'static [&'static str] {
let (scheme, _) = split_spec(spec);
registration_for_scheme(scheme).map_or(&[], |reg| reg.metadata.credential_names)
}
#[cfg_attr(not(any(feature = "cli", test)), allow(dead_code))]
pub(crate) fn spec_provider_reads(spec: &str) -> bool {
let (scheme, _) = split_spec(spec);
registration_for_scheme(scheme).is_some_and(|reg| reg.metadata.reads)
}
pub(crate) fn spec_provider_deletes(spec: &str) -> bool {
let (scheme, _) = split_spec(spec);
registration_for_scheme(scheme).is_some_and(|reg| reg.metadata.deletes)
}
pub(crate) fn deleting_provider_names() -> Vec<&'static str> {
let mut names: Vec<&'static str> = PROVIDER_REGISTRY
.iter()
.filter(|reg| reg.metadata.deletes)
.map(|reg| reg.metadata.info.name)
.collect();
names.sort_unstable();
names
}
pub(crate) fn provider_display_name_for_spec(spec: &str) -> String {
let (scheme, _) = split_spec(spec);
registration_for_scheme(scheme)
.map(|reg| reg.metadata.info.name.to_string())
.unwrap_or_else(|| scheme.to_string())
}
#[cfg(test)]
mod tests {
use super::ProviderInfo;
#[test]
fn provider_info_display_with_and_without_examples() {
let with = ProviderInfo {
name: "onepassword",
description: "OnePassword",
examples: &["onepassword://vault", "onepassword://work@Production"],
};
assert_eq!(
with.display_with_examples(),
"onepassword: OnePassword (e.g., onepassword://vault, onepassword://work@Production)"
);
let without = ProviderInfo {
name: "env",
description: "Environment variables",
examples: &[],
};
assert_eq!(
without.display_with_examples(),
"env: Environment variables"
);
}
}