/ src / source / remote / traits.rs
traits.rs
  1  //! Core traits and types for remote secret sources.
  2  
  3  use compact_str::CompactString;
  4  use serde::{Deserialize, Serialize};
  5  use std::fmt;
  6  
  7  /// Information about a remote provider.
  8  #[derive(Debug, Clone, Serialize, Deserialize)]
  9  pub struct RemoteProviderInfo {
 10      /// Unique identifier for this provider (e.g., "doppler", "aws").
 11      pub id: CompactString,
 12      /// Human-readable display name (e.g., "Doppler", "AWS Secrets Manager").
 13      pub display_name: CompactString,
 14      /// Short name for UI display (e.g., "DPL", "AWS").
 15      pub short_name: CompactString,
 16      /// Provider description.
 17      pub description: Option<CompactString>,
 18      /// Provider documentation URL.
 19      pub docs_url: Option<String>,
 20  }
 21  
 22  /// Authentication status for a remote source.
 23  #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 24  #[serde(rename_all = "snake_case")]
 25  pub enum AuthStatus {
 26      /// Not authenticated yet.
 27      NotAuthenticated,
 28      /// Authentication in progress.
 29      Authenticating,
 30      /// Successfully authenticated.
 31      Authenticated {
 32          /// Optional identity info (e.g., user email, service account).
 33          identity: Option<CompactString>,
 34          /// When the auth expires, if applicable.
 35          expires_at: Option<u64>,
 36      },
 37      /// Authentication failed.
 38      Failed {
 39          /// Error message describing why auth failed.
 40          reason: CompactString,
 41      },
 42      /// Authentication expired and needs refresh.
 43      Expired,
 44  }
 45  
 46  impl AuthStatus {
 47      pub fn is_authenticated(&self) -> bool {
 48          matches!(self, AuthStatus::Authenticated { .. })
 49      }
 50  
 51      pub fn is_failed(&self) -> bool {
 52          matches!(self, AuthStatus::Failed { .. })
 53      }
 54  }
 55  
 56  impl fmt::Display for AuthStatus {
 57      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 58          match self {
 59              AuthStatus::NotAuthenticated => write!(f, "not_authenticated"),
 60              AuthStatus::Authenticating => write!(f, "authenticating"),
 61              AuthStatus::Authenticated { identity, .. } => {
 62                  if let Some(id) = identity {
 63                      write!(f, "authenticated ({})", id)
 64                  } else {
 65                      write!(f, "authenticated")
 66                  }
 67              }
 68              AuthStatus::Failed { reason } => write!(f, "failed: {}", reason),
 69              AuthStatus::Expired => write!(f, "expired"),
 70          }
 71      }
 72  }
 73  
 74  /// A field required for authentication.
 75  #[derive(Debug, Clone, Serialize, Deserialize)]
 76  pub struct AuthField {
 77      /// Field name/key (e.g., "token", "client_id").
 78      pub name: CompactString,
 79      /// Human-readable label (e.g., "API Token", "Client ID").
 80      pub label: CompactString,
 81      /// Description of what this field is for.
 82      pub description: Option<CompactString>,
 83      /// Whether this field is required.
 84      pub required: bool,
 85      /// Whether this field should be masked in UI (e.g., for tokens/passwords).
 86      pub secret: bool,
 87      /// Environment variable that can provide this value.
 88      pub env_var: Option<CompactString>,
 89      /// Default value, if any.
 90      pub default: Option<CompactString>,
 91  }
 92  
 93  /// Authentication configuration passed to the authenticate method.
 94  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 95  pub struct AuthConfig {
 96      /// Key-value pairs of credentials.
 97      pub credentials: std::collections::HashMap<String, String>,
 98  }
 99  
100  impl AuthConfig {
101      pub fn new() -> Self {
102          Self::default()
103      }
104  
105      pub fn with_credential(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
106          self.credentials.insert(key.into(), value.into());
107          self
108      }
109  
110      pub fn get(&self, key: &str) -> Option<&str> {
111          self.credentials.get(key).map(|s| s.as_str())
112      }
113  }
114  
115  /// A level in the scope hierarchy (e.g., project, environment, folder).
116  #[derive(Debug, Clone, Serialize, Deserialize)]
117  pub struct ScopeLevel {
118      /// Level name/key (e.g., "project", "environment").
119      pub name: CompactString,
120      /// Human-readable label (e.g., "Project", "Environment").
121      pub display_name: CompactString,
122      /// Whether this level is required to fetch secrets.
123      pub required: bool,
124      /// Whether multiple selections are allowed at this level.
125      pub multi_select: bool,
126      /// Optional description.
127      pub description: Option<CompactString>,
128  }
129  
130  /// An option available at a scope level.
131  #[derive(Debug, Clone, Serialize, Deserialize)]
132  pub struct ScopeOption {
133      /// Unique identifier for this option.
134      pub id: CompactString,
135      /// Human-readable display name.
136      pub display_name: CompactString,
137      /// Optional description or metadata.
138      pub description: Option<CompactString>,
139      /// Optional icon or indicator.
140      pub icon: Option<CompactString>,
141  }
142  
143  /// Selected scope for fetching secrets.
144  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
145  pub struct ScopeSelection {
146      /// Map of level name to selected option IDs.
147      pub selections: std::collections::HashMap<String, Vec<String>>,
148  }
149  
150  impl ScopeSelection {
151      pub fn new() -> Self {
152          Self::default()
153      }
154  
155      pub fn with_selection(
156          mut self,
157          level: impl Into<String>,
158          values: Vec<impl Into<String>>,
159      ) -> Self {
160          self.selections
161              .insert(level.into(), values.into_iter().map(|v| v.into()).collect());
162          self
163      }
164  
165      pub fn set(&mut self, level: impl Into<String>, values: Vec<impl Into<String>>) {
166          self.selections
167              .insert(level.into(), values.into_iter().map(|v| v.into()).collect());
168      }
169  
170      pub fn get(&self, level: &str) -> Option<&[String]> {
171          self.selections.get(level).map(|v| v.as_slice())
172      }
173  
174      pub fn get_single(&self, level: &str) -> Option<&str> {
175          self.selections
176              .get(level)
177              .and_then(|v| v.first())
178              .map(|s| s.as_str())
179      }
180  
181      pub fn is_empty(&self) -> bool {
182          self.selections.is_empty()
183      }
184  }
185  
186  /// Provider-specific configuration.
187  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
188  pub struct ProviderConfig {
189      /// Whether this provider is enabled.
190      #[serde(default)]
191      pub enabled: bool,
192      /// Provider-specific settings as key-value pairs.
193      #[serde(flatten)]
194      pub settings: std::collections::HashMap<String, serde_json::Value>,
195  }
196  
197  impl ProviderConfig {
198      pub fn new() -> Self {
199          Self::default()
200      }
201  
202      pub fn enabled(mut self) -> Self {
203          self.enabled = true;
204          self
205      }
206  
207      pub fn with_setting(mut self, key: impl Into<String>, value: impl Into<serde_json::Value>) -> Self {
208          self.settings.insert(key.into(), value.into());
209          self
210      }
211  
212      pub fn get_string(&self, key: &str) -> Option<&str> {
213          self.settings.get(key).and_then(|v| v.as_str())
214      }
215  
216      pub fn get_bool(&self, key: &str) -> Option<bool> {
217          self.settings.get(key).and_then(|v| v.as_bool())
218      }
219  
220      pub fn get_u64(&self, key: &str) -> Option<u64> {
221          self.settings.get(key).and_then(|v| v.as_u64())
222      }
223  }
224  
225  /// Configuration for all remote sources.
226  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
227  pub struct RemoteSourcesConfig {
228      /// Per-provider configurations.
229      #[serde(flatten)]
230      pub providers: std::collections::HashMap<String, ProviderConfig>,
231  }
232  
233  impl RemoteSourcesConfig {
234      pub fn new() -> Self {
235          Self::default()
236      }
237  
238      pub fn with_provider(mut self, name: impl Into<String>, config: ProviderConfig) -> Self {
239          self.providers.insert(name.into(), config);
240          self
241      }
242  
243      pub fn get(&self, provider: &str) -> Option<&ProviderConfig> {
244          self.providers.get(provider)
245      }
246  
247      pub fn enabled_providers(&self) -> impl Iterator<Item = (&String, &ProviderConfig)> {
248          self.providers.iter().filter(|(_, c)| c.enabled)
249      }
250  }
251  
252  /// Summary information about a remote source for UI display.
253  #[derive(Debug, Clone, Serialize, Deserialize)]
254  pub struct RemoteSourceInfo {
255      /// Provider ID.
256      pub id: CompactString,
257      /// Display name.
258      pub display_name: CompactString,
259      /// Short name for compact UI.
260      pub short_name: CompactString,
261      /// Current auth status.
262      pub auth_status: AuthStatus,
263      /// Current scope selection.
264      pub scope: ScopeSelection,
265      /// Number of secrets currently loaded.
266      pub secret_count: usize,
267      /// Last refresh timestamp (Unix millis).
268      pub last_refreshed: Option<u64>,
269      /// Whether secrets are currently being fetched.
270      pub loading: bool,
271      /// Last error, if any.
272      pub last_error: Option<CompactString>,
273  }