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}