gestura_core_foundation/
secrets.rs

1//! Secret (API key) retrieval abstractions.
2//!
3//! These are pure abstractions with no dependency on secure storage implementations.
4//! Concrete implementations that depend on OS keychain / secure storage live in
5//! `gestura-core::secrets`.
6
7/// A strongly-typed identifier for a secret used by the core.
8///
9/// These map to keys in secure storage.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum SecretKey {
12    /// The general OpenAI API key.
13    OpenAi,
14    /// The OpenAI API key specifically for voice/STT.
15    VoiceOpenAi,
16    /// The Anthropic API key.
17    Anthropic,
18    /// The Grok (xAI) API key.
19    Grok,
20    /// The Google Gemini API key.
21    Gemini,
22}
23
24impl SecretKey {
25    /// Returns the secure-storage key name used to store this secret.
26    ///
27    /// This matches the canonical secure-storage key names used across the
28    /// application (and the `AppConfig` secret-migration logic).
29    pub const fn storage_key(self) -> &'static str {
30        match self {
31            Self::OpenAi => "gestura_llm_openai_api_key",
32            Self::VoiceOpenAi => "gestura_voice_openai_api_key",
33            Self::Anthropic => "gestura_llm_anthropic_api_key",
34            Self::Grok => "gestura_llm_grok_api_key",
35            Self::Gemini => "gestura_llm_gemini_api_key",
36        }
37    }
38
39    /// Legacy secure-storage key name used by older releases.
40    ///
41    /// New writes should always use [`SecretKey::storage_key`]. This exists only
42    /// for backwards-compatible reads + optional self-heal migration.
43    pub const fn legacy_storage_key(self) -> Option<&'static str> {
44        match self {
45            Self::OpenAi => Some("gestura_api_key_openai"),
46            Self::VoiceOpenAi => Some("gestura_api_key_voice_openai"),
47            Self::Anthropic => Some("gestura_api_key_anthropic"),
48            Self::Grok => Some("gestura_api_key_grok"),
49            Self::Gemini => Some("gestura_api_key_gemini"),
50        }
51    }
52}
53
54/// A source of secrets used by core provider selection and execution.
55///
56/// Implementations should treat missing secrets as `None` and should not panic.
57///
58/// Note: this is async to support implementations backed by secure storage.
59#[async_trait::async_trait]
60pub trait SecretProvider: Send + Sync {
61    /// Retrieve a secret by key.
62    ///
63    /// Implementations should return `None` if the secret does not exist or is
64    /// unavailable.
65    async fn get_secret(&self, key: SecretKey) -> Option<String>;
66}
67
68/// A `SecretProvider` that always returns `None`.
69///
70/// Useful for contexts where secure storage is not configured or should not be
71/// consulted.
72#[derive(Debug, Default)]
73pub struct NullSecretProvider;
74
75#[async_trait::async_trait]
76impl SecretProvider for NullSecretProvider {
77    async fn get_secret(&self, _key: SecretKey) -> Option<String> {
78        None
79    }
80}