| 1 | //! Provider-configuration support: runtime-preset file snapshots with |
| 2 | //! rollback, and the provider key verification seam |
| 3 | //! (TUI_MODULARIZATION.md slice 8). |
| 4 | |
| 5 | use super::*; |
| 6 | |
| 7 | pub(crate) trait ProviderKeyVerifier { |
| 8 | fn verify<'a>( |
| 9 | &'a self, |
| 10 | provider: ApiProvider, |
| 11 | api_key: &'a str, |
| 12 | base_url: &'a str, |
| 13 | ) -> ProviderKeyVerification<'a>; |
| 14 | } |
| 15 | |
| 16 | pub(crate) struct LiveProviderKeyVerifier; |
| 17 | |
| 18 | impl ProviderKeyVerifier for LiveProviderKeyVerifier { |
| 19 | fn verify<'a>( |
| 20 | &'a self, |
| 21 | provider: ApiProvider, |
| 22 | api_key: &'a str, |
| 23 | base_url: &'a str, |
| 24 | ) -> ProviderKeyVerification<'a> { |
| 25 | Box::pin(crate::client::verify_provider_api_key( |
| 26 | provider, api_key, base_url, |
| 27 | )) |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | pub(crate) struct RuntimePresetFileSnapshot { |
| 32 | pub(crate) path: PathBuf, |
| 33 | pub(crate) contents: Option<Vec<u8>>, |
| 34 | } |
| 35 | |
| 36 | impl RuntimePresetFileSnapshot { |
| 37 | pub(crate) fn capture(path: PathBuf) -> Result<Self> { |
| 38 | let contents = match std::fs::read(&path) { |
| 39 | Ok(contents) => Some(contents), |
| 40 | Err(error) if error.kind() == io::ErrorKind::NotFound => None, |
| 41 | Err(error) => { |
| 42 | return Err(error) |
| 43 | .with_context(|| format!("failed to snapshot {}", path.display())); |
| 44 | } |
| 45 | }; |
| 46 | Ok(Self { path, contents }) |
| 47 | } |
| 48 | |
| 49 | fn restore(&self) -> Result<()> { |
| 50 | match &self.contents { |
| 51 | Some(contents) => crate::utils::write_atomic(&self.path, contents) |
| 52 | .with_context(|| format!("failed to restore {}", self.path.display())), |
| 53 | None => match std::fs::remove_file(&self.path) { |
| 54 | Ok(()) => Ok(()), |
| 55 | Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()), |
| 56 | Err(error) => { |
| 57 | Err(error).with_context(|| format!("failed to remove {}", self.path.display())) |
| 58 | } |
| 59 | }, |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | pub(crate) fn runtime_preset_error_with_rollback( |
| 65 | error: anyhow::Error, |
| 66 | snapshots: &[&RuntimePresetFileSnapshot], |
| 67 | ) -> anyhow::Error { |
| 68 | let rollback_errors = snapshots |
| 69 | .iter() |
| 70 | .filter_map(|snapshot| snapshot.restore().err()) |
| 71 | .map(|error| format!("{error:#}")) |
| 72 | .collect::<Vec<_>>(); |
| 73 | if rollback_errors.is_empty() { |
| 74 | error |
| 75 | } else { |
| 76 | anyhow::anyhow!( |
| 77 | "{error:#}; runtime preset rollback also failed: {}", |
| 78 | rollback_errors.join("; ") |
| 79 | ) |
| 80 | } |
| 81 | } |
| 82 |