diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-13 00:50:54 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-13 00:50:54 +0200 |
| commit | 6723829a3398b3c9dd6dc6ae79124f46000606ee (patch) | |
| tree | a1ec535eddd711a4557e4bcc5b94382c3623504c /crates/turtle/src/atuin_client | |
| parent | chore(treewide): Cleanup themes (diff) | |
| download | atuin-6723829a3398b3c9dd6dc6ae79124f46000606ee.zip | |
chore(treewide): Remove `cargo` warnings to 0
There are still the `clippy` warnings, but they are for a future date.
Diffstat (limited to 'crates/turtle/src/atuin_client')
| -rw-r--r-- | crates/turtle/src/atuin_client/api_client.rs | 16 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/encryption.rs | 4 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/history.rs | 6 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/history/store.rs | 78 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/record/encryption.rs | 22 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/record/sqlite_store.rs | 100 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/record/sync.rs | 219 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/secrets.rs | 2 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/settings.rs | 68 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_client/settings/watcher.rs | 4 |
10 files changed, 118 insertions, 401 deletions
diff --git a/crates/turtle/src/atuin_client/api_client.rs b/crates/turtle/src/atuin_client/api_client.rs index 15d96d93..bd5bf59e 100644 --- a/crates/turtle/src/atuin_client/api_client.rs +++ b/crates/turtle/src/atuin_client/api_client.rs @@ -19,8 +19,8 @@ static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); pub(crate) struct Client<'a> { sync_addr: &'a str, - client: reqwest::Client, user_id: Uuid, + inner: reqwest::Client, } fn make_url(address: &str, path: &str, user_id: Uuid) -> Result<String> { @@ -28,7 +28,7 @@ fn make_url(address: &str, path: &str, user_id: Uuid) -> Result<String> { // `join()` expects a trailing `/` in order to join paths // e.g. it treats `http://host:port/subdir` as a file called `subdir` - let address = &format!("{address}/api/v0/{}/", user_id.to_string()); + let address = &format!("{address}/api/v0/{user_id}/"); // passing a path with a leading `/` will cause `join()` to replace the entire URL path let path = path.strip_prefix("/").unwrap_or(path); @@ -81,7 +81,7 @@ async fn handle_resp_error(resp: Response) -> Result<Response> { } if !status.is_success() { - if let Ok(error) = resp.json::<ErrorResponse>().await { + if let Ok(error) = resp.json::<ErrorResponse<'_>>().await { let reason = error.reason; if status.is_client_error() { @@ -117,7 +117,7 @@ impl<'a> Client<'a> { Ok(Client { user_id, sync_addr, - client: reqwest::Client::builder() + inner: reqwest::Client::builder() .user_agent(APP_USER_AGENT) .default_headers(headers) .connect_timeout(Duration::new(connect_timeout, 0)) @@ -130,7 +130,7 @@ impl<'a> Client<'a> { let url = make_url(self.sync_addr, "/store", self.user_id)?; let url = Url::parse(url.as_str())?; - let resp = self.client.delete(url).send().await?; + let resp = self.inner.delete(url).send().await?; handle_resp_error(resp).await?; @@ -143,7 +143,7 @@ impl<'a> Client<'a> { debug!("uploading {} records to {url}", records.len()); - let resp = self.client.post(url).json(records).send().await?; + let resp = self.inner.post(url).json(records).send().await?; handle_resp_error(resp).await?; Ok(()) @@ -169,7 +169,7 @@ impl<'a> Client<'a> { let url = Url::parse(url.as_str())?; - let resp = self.client.get(url).send().await?; + let resp = self.inner.get(url).send().await?; let resp = handle_resp_error(resp).await?; let records = resp.json::<Vec<Record<EncryptedData>>>().await?; @@ -181,7 +181,7 @@ impl<'a> Client<'a> { let url = make_url(self.sync_addr, "/record", self.user_id)?; let url = Url::parse(url.as_str())?; - let resp = self.client.get(url).send().await?; + let resp = self.inner.get(url).send().await?; let resp = handle_resp_error(resp).await?; if !ensure_version(&resp)? { diff --git a/crates/turtle/src/atuin_client/encryption.rs b/crates/turtle/src/atuin_client/encryption.rs index e9c8d7e9..661a6669 100644 --- a/crates/turtle/src/atuin_client/encryption.rs +++ b/crates/turtle/src/atuin_client/encryption.rs @@ -75,7 +75,7 @@ pub(crate) fn decode_key(key: String) -> Result<Key> { if let Ok(key) = <[u8; 32]>::try_from(&*buf) { Ok(key.into()) } else { - let mut bytes = rmp::decode::Bytes::new(&buf); + let mut bytes = decode::Bytes::new(&buf); match Marker::from_u8(buf[0]) { Marker::Bin8 => { @@ -91,7 +91,7 @@ pub(crate) fn decode_key(key: String) -> Result<Key> { let mut key = Key::default(); for i in &mut key { - *i = rmp::decode::read_int(&mut bytes).map_err(|err| eyre!("{err:?}"))?; + *i = decode::read_int(&mut bytes).map_err(|err| eyre!("{err:?}"))?; } Ok(key) } diff --git a/crates/turtle/src/atuin_client/history.rs b/crates/turtle/src/atuin_client/history.rs index 1f89cd71..11d60548 100644 --- a/crates/turtle/src/atuin_client/history.rs +++ b/crates/turtle/src/atuin_client/history.rs @@ -272,7 +272,7 @@ impl History { Ok(History { id: id.to_owned().into(), - timestamp: OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)?, + timestamp: OffsetDateTime::from_unix_timestamp_nanos(i128::from(timestamp))?, duration, exit, command: command.to_owned(), @@ -282,7 +282,7 @@ impl History { author: Self::author_from_hostname(hostname), intent: None, deleted_at: deleted_at - .map(|t| OffsetDateTime::from_unix_timestamp_nanos(t as i128)) + .map(|t| OffsetDateTime::from_unix_timestamp_nanos(i128::from(t))) .transpose()?, }) } @@ -553,7 +553,7 @@ mod tests { fn disable_secrets() { let settings = Settings { secrets_filter: false, - ..Settings::utc() + ..Settings::new().unwrap() }; let stripe_key: History = History::capture() diff --git a/crates/turtle/src/atuin_client/history/store.rs b/crates/turtle/src/atuin_client/history/store.rs index c6e079f3..9c7771cc 100644 --- a/crates/turtle/src/atuin_client/history/store.rs +++ b/crates/turtle/src/atuin_client/history/store.rs @@ -27,12 +27,12 @@ pub(crate) enum HistoryRecord { } impl HistoryRecord { - /// Serialize a history record, returning DecryptedData + /// Serialize a history record, returning `DecryptedData` /// The record will be of a certain type /// We map those like so: /// - /// HistoryRecord::Create -> 0 - /// HistoryRecord::Delete-> 1 + /// `HistoryRecord::Create` -> 0 + /// `HistoryRecord::Delete`-> 1 /// /// This numeric identifier is then written as the first byte to the buffer. For history, we /// append the serialized history right afterwards, to avoid having to handle serialization @@ -47,7 +47,7 @@ impl HistoryRecord { let mut output = vec![]; match self { - HistoryRecord::Create(history) => { + Self::Create(history) => { // 0 -> a history create encode::write_u8(&mut output, 0)?; @@ -55,12 +55,12 @@ impl HistoryRecord { encode::write_bin(&mut output, &bytes.0)?; } - HistoryRecord::Delete(id) => { + Self::Delete(id) => { // 1 -> a history delete encode::write_u8(&mut output, 1)?; encode::write_str(&mut output, id.0.as_str())?; } - }; + } Ok(DecryptedData(output)) } @@ -81,11 +81,11 @@ impl HistoryRecord { 0 => { // not super useful to us atm, but perhaps in the future // written by write_bin above - let _ = decode::read_bin_len(&mut bytes).map_err(error_report)?; + decode::read_bin_len(&mut bytes).map_err(error_report)?; let record = History::deserialize(bytes.remaining_slice(), version)?; - Ok(HistoryRecord::Create(record)) + Ok(Self::Create(record)) } // 1 -> HistoryRecord::Delete @@ -99,7 +99,7 @@ impl HistoryRecord { ); } - Ok(HistoryRecord::Delete(id.to_string().into())) + Ok(Self::Delete(id.to_string().into())) } n => { @@ -111,7 +111,7 @@ impl HistoryRecord { impl HistoryStore { pub(crate) fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> Self { - HistoryStore { + Self { store, host_id, encryption_key, @@ -182,7 +182,7 @@ impl HistoryStore { } /// Delete a batch of history entries via the record store. - /// Returns the record IDs so the caller can run incremental_build when ready. + /// Returns the record IDs so the caller can run `incremental_build` when ready. pub(crate) async fn delete_entries( &self, entries: impl IntoIterator<Item = History>, @@ -209,7 +209,7 @@ impl HistoryStore { let records = self.store.all_tagged(HISTORY_TAG).await?; let mut ret = Vec::with_capacity(records.len()); - for record in records.into_iter() { + for record in records { let hist = match record.version.as_str() { HISTORY_VERSION_V0 | HISTORY_VERSION => { let version = record.version.clone(); @@ -266,33 +266,28 @@ impl HistoryStore { for id in ids { let record = self.store.get(*id).await; - let record = match record { - Ok(record) => record, - _ => { + if let Ok(record) = record { + if record.tag != HISTORY_TAG { continue; } - }; - if record.tag != HISTORY_TAG { - continue; - } + let version = record.version.clone(); + let decrypted = record.decrypt::<PASETO_V4>(&self.encryption_key)?; + let record = match version.as_str() { + HISTORY_VERSION_V0 | HISTORY_VERSION => { + HistoryRecord::deserialize(&decrypted.data, version.as_str())? + } + version => bail!("unknown history version {version:?}"), + }; - let version = record.version.clone(); - let decrypted = record.decrypt::<PASETO_V4>(&self.encryption_key)?; - let record = match version.as_str() { - HISTORY_VERSION_V0 | HISTORY_VERSION => { - HistoryRecord::deserialize(&decrypted.data, version.as_str())? - } - version => bail!("unknown history version {version:?}"), - }; - - match record { - HistoryRecord::Create(h) => { - // TODO: benchmark CPU time/memory tradeoff of batch commit vs one at a time - database.save(&h).await?; - } - HistoryRecord::Delete(id) => { - database.delete_rows(&[id]).await?; + match record { + HistoryRecord::Create(h) => { + // TODO: benchmark CPU time/memory tradeoff of batch commit vs one at a time + database.save(&h).await?; + } + HistoryRecord::Delete(id) => { + database.delete_rows(&[id]).await?; + } } } } @@ -306,10 +301,13 @@ impl HistoryStore { pub(crate) async fn history_ids(&self) -> Result<HashSet<HistoryId>> { let history = self.history().await?; - let ret = HashSet::from_iter(history.iter().map(|h| match h { - HistoryRecord::Create(h) => h.id.clone(), - HistoryRecord::Delete(id) => id.clone(), - })); + let ret = history + .iter() + .map(|h| match h { + HistoryRecord::Create(h) => h.id.clone(), + HistoryRecord::Delete(id) => id.clone(), + }) + .collect::<HashSet<_>>(); Ok(ret) } @@ -320,7 +318,7 @@ impl HistoryStore { ProgressStyle::with_template("{spinner:.blue} {msg}") .unwrap() .with_key("eta", |state: &ProgressState, w: &mut dyn Write| { - write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap() + write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap(); }) .progress_chars("#>-"), ); diff --git a/crates/turtle/src/atuin_client/record/encryption.rs b/crates/turtle/src/atuin_client/record/encryption.rs index 70723bb7..afac5b02 100644 --- a/crates/turtle/src/atuin_client/record/encryption.rs +++ b/crates/turtle/src/atuin_client/record/encryption.rs @@ -54,16 +54,16 @@ that happens in the background can make the network calls to the HSM impl Encryption for PASETO_V4 { fn re_encrypt( mut data: EncryptedData, - _ad: AdditionalData, + _ad: AdditionalData<'_>, old_key: &[u8; 32], new_key: &[u8; 32], ) -> Result<EncryptedData> { - let cek = Self::decrypt_cek(data.content_encryption_key, old_key)?; + let cek = Self::decrypt_cek(&data.content_encryption_key, old_key)?; data.content_encryption_key = Self::encrypt_cek(cek, new_key); Ok(data) } - fn encrypt(data: DecryptedData, ad: AdditionalData, key: &[u8; 32]) -> EncryptedData { + fn encrypt(data: DecryptedData, ad: AdditionalData<'_>, key: &[u8; 32]) -> EncryptedData { // generate a random key for this entry // aka content-encryption-key (CEK) let random_key = Key::<V4, Local>::new_os_random(); @@ -91,9 +91,13 @@ impl Encryption for PASETO_V4 { } } - fn decrypt(data: EncryptedData, ad: AdditionalData, key: &[u8; 32]) -> Result<DecryptedData> { + fn decrypt( + data: EncryptedData, + ad: AdditionalData<'_>, + key: &[u8; 32], + ) -> Result<DecryptedData> { let token = data.data; - let cek = Self::decrypt_cek(data.content_encryption_key, key)?; + let cek = Self::decrypt_cek(&data.content_encryption_key, key)?; // encode the implicit assertions let assertions = Assertions::from(ad).encode(); @@ -114,12 +118,12 @@ impl Encryption for PASETO_V4 { } impl PASETO_V4 { - fn decrypt_cek(wrapped_cek: String, key: &[u8; 32]) -> Result<Key<V4, Local>> { + fn decrypt_cek(wrapped_cek: &str, key: &[u8; 32]) -> Result<Key<V4, Local>> { let wrapping_key = Key::<V4, Local>::from_bytes(*key); // let wrapping_key = PasetoSymmetricKey::from(Key::from(key)); - let AtuinFooter { kid, wpk } = serde_json::from_str(&wrapped_cek) + let AtuinFooter { kid, wpk } = serde_json::from_str(wrapped_cek) .context("wrapped cek did not contain the correct contents")?; // check that the wrapping key matches the required key to decrypt. @@ -262,7 +266,7 @@ mod tests { let data = DecryptedData(vec![1, 2, 3, 4]); let encrypted = PASETO_V4::encrypt(data, ad, &key.to_bytes()); - let _ = PASETO_V4::decrypt(encrypted, ad, &fake_key.to_bytes()).unwrap_err(); + drop(PASETO_V4::decrypt(encrypted, ad, &fake_key.to_bytes()).unwrap_err()); } #[test] @@ -285,7 +289,7 @@ mod tests { id: &RecordId(uuid_v7()), ..ad }; - let _ = PASETO_V4::decrypt(encrypted, ad, &key.to_bytes()).unwrap_err(); + drop(PASETO_V4::decrypt(encrypted, ad, &key.to_bytes()).unwrap_err()); } #[test] diff --git a/crates/turtle/src/atuin_client/record/sqlite_store.rs b/crates/turtle/src/atuin_client/record/sqlite_store.rs index 24188443..12f9fc4e 100644 --- a/crates/turtle/src/atuin_client/record/sqlite_store.rs +++ b/crates/turtle/src/atuin_client/record/sqlite_store.rs @@ -353,18 +353,17 @@ impl SqliteStore { pub(crate) async fn purge(&self, key: &[u8; 32]) -> Result<()> { let all = self.load_all().await?; - for record in all.iter() { - match record.clone().decrypt::<PASETO_V4>(key) { - Ok(_) => continue, - Err(_) => { - println!( - "Failed to decrypt {}, deleting", - record.id.0.as_hyphenated() - ); - - self.delete(record.id).await?; - } + for record in &all { + if record.clone().decrypt::<PASETO_V4>(key).is_ok() { + continue; } + + println!( + "Failed to decrypt {}, deleting", + record.id.0.as_hyphenated() + ); + + self.delete(record.id).await?; } Ok(()) @@ -379,7 +378,6 @@ mod tests { settings::test_local_timeout, }, atuin_common::{ - self, record::{DecryptedData, EncryptedData, Host, HostId, Record}, utils::uuid_v7, }, @@ -389,9 +387,9 @@ mod tests { fn test_record() -> Record<EncryptedData> { Record::builder() - .host(Host::new(HostId(atuin_common::utils::uuid_v7()))) + .host(Host::new(HostId(uuid_v7()))) .version("v1".into()) - .tag(atuin_common::utils::uuid_v7().simple().to_string()) + .tag(uuid_v7().simple().to_string()) .data(EncryptedData { data: "1234".into(), content_encryption_key: "1234".into(), @@ -507,80 +505,6 @@ mod tests { } #[tokio::test] - async fn len_different_tags() { - let db = SqliteStore::new(":memory:", test_local_timeout()) - .await - .unwrap(); - - // these have different tags, so the len should be the same - // we model multiple stores within one database - // new store = new tag = independent length - let first = test_record(); - let second = test_record(); - - db.push(&first).await.unwrap(); - db.push(&second).await.unwrap(); - - let first_len = db.len(first.host.id, first.tag.as_str()).await.unwrap(); - let second_len = db.len(second.host.id, second.tag.as_str()).await.unwrap(); - - assert_eq!(first_len, 1, "expected length of 1 after insert"); - assert_eq!(second_len, 1, "expected length of 1 after insert"); - } - - #[tokio::test] - async fn append_a_bunch() { - let db = SqliteStore::new(":memory:", test_local_timeout()) - .await - .unwrap(); - - let mut tail = test_record(); - db.push(&tail).await.expect("failed to push record"); - - for _ in 1..100 { - tail = tail.append(vec![1, 2, 3, 4]).encrypt::<PASETO_V4>(&[0; 32]); - db.push(&tail).await.unwrap(); - } - - assert_eq!( - db.len(tail.host.id, tail.tag.as_str()).await.unwrap(), - 100, - "failed to insert 100 records" - ); - - assert_eq!( - db.len_tag(tail.tag.as_str()).await.unwrap(), - 100, - "failed to insert 100 records" - ); - } - - #[tokio::test] - async fn append_a_big_bunch() { - let db = SqliteStore::new(":memory:", test_local_timeout()) - .await - .unwrap(); - - let mut records: Vec<Record<EncryptedData>> = Vec::with_capacity(10000); - - let mut tail = test_record(); - records.push(tail.clone()); - - for _ in 1..10000 { - tail = tail.append(vec![1, 2, 3]).encrypt::<PASETO_V4>(&[0; 32]); - records.push(tail.clone()); - } - - db.push_batch(records.iter()).await.unwrap(); - - assert_eq!( - db.len(tail.host.id, tail.tag.as_str()).await.unwrap(), - 10000, - "failed to insert 10k records" - ); - } - - #[tokio::test] async fn re_encrypt() { let store = SqliteStore::new(":memory:", test_local_timeout()) .await diff --git a/crates/turtle/src/atuin_client/record/sync.rs b/crates/turtle/src/atuin_client/record/sync.rs index a86fc7a9..3057bb10 100644 --- a/crates/turtle/src/atuin_client/record/sync.rs +++ b/crates/turtle/src/atuin_client/record/sync.rs @@ -14,9 +14,6 @@ use indicatif::{ProgressBar, ProgressState, ProgressStyle}; #[derive(Error, Debug)] pub(crate) enum SyncError { - #[error("the local store is ahead of the remote, but for another host. has remote lost data?")] - LocalAheadOtherHost, - #[error("an issue with the local database occurred: {msg:?}")] LocalStoreError { msg: String }, @@ -319,7 +316,7 @@ pub(crate) async fn sync_remote( downloaded.append(&mut d); } - Operation::Noop { .. } => continue, + Operation::Noop { .. } => (), } } @@ -376,14 +373,13 @@ pub(crate) async fn sync( #[cfg(test)] mod tests { + use crate::atuin_client::record::sync::Operation; use crate::atuin_common::record::{Diff, EncryptedData, HostId, Record}; - use pretty_assertions::assert_eq; use crate::atuin_client::{ record::{ - encryption::PASETO_V4, sqlite_store::SqliteStore, - sync::{self, Operation}, + sync::{self}, }, settings::test_local_timeout, }; @@ -442,7 +438,7 @@ mod tests { assert_eq!(diff.len(), 1); - let operations = sync::operations(diff, &store).await.unwrap(); + let operations = sync::operations(diff, &store).unwrap(); assert_eq!(operations.len(), 1); @@ -456,211 +452,4 @@ mod tests { } ); } - - #[tokio::test] - async fn build_two_way_diff() { - // a diff where local is ahead of remote for one, and remote for - // another. One upload, one download - - let shared_record = test_record(); - let remote_ahead = test_record(); - - let local_ahead = shared_record - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - - assert_eq!(local_ahead.idx, 1); - - let local = vec![shared_record.clone(), local_ahead.clone()]; // local knows about the already synced, and something newer in the same store - let remote = vec![shared_record.clone(), remote_ahead.clone()]; // remote knows about the already-synced, and one new record in a new store - - let (store, diff) = build_test_diff(local, remote).await; - let operations = sync::operations(diff, &store).await.unwrap(); - - assert_eq!(operations.len(), 2); - - assert_eq!( - operations, - vec![ - // Or in otherwords, local is ahead by one - Operation::Upload { - host: local_ahead.host.id, - tag: local_ahead.tag, - local: 1, - remote: Some(0), - }, - // Or in other words, remote knows of a record in an entirely new store (tag) - Operation::Download { - host: remote_ahead.host.id, - tag: remote_ahead.tag, - local: None, - remote: 0, - }, - ] - ); - } - - #[tokio::test] - async fn build_complex_diff() { - // One shared, ahead but known only by remote - // One known only by local - // One known only by remote - - let shared_record = test_record(); - let local_only = test_record(); - - let local_only_20 = test_record(); - let local_only_21 = local_only_20 - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - let local_only_22 = local_only_21 - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - let local_only_23 = local_only_22 - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - - let remote_only = test_record(); - - let remote_only_20 = test_record(); - let remote_only_21 = remote_only_20 - .append(vec![2, 3, 2]) - .encrypt::<PASETO_V4>(&[0; 32]); - let remote_only_22 = remote_only_21 - .append(vec![2, 3, 2]) - .encrypt::<PASETO_V4>(&[0; 32]); - let remote_only_23 = remote_only_22 - .append(vec![2, 3, 2]) - .encrypt::<PASETO_V4>(&[0; 32]); - let remote_only_24 = remote_only_23 - .append(vec![2, 3, 2]) - .encrypt::<PASETO_V4>(&[0; 32]); - - let second_shared = test_record(); - let second_shared_remote_ahead = second_shared - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - let second_shared_remote_ahead2 = second_shared_remote_ahead - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - - let third_shared = test_record(); - let third_shared_local_ahead = third_shared - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - let third_shared_local_ahead2 = third_shared_local_ahead - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - - let fourth_shared = test_record(); - let fourth_shared_remote_ahead = fourth_shared - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - let fourth_shared_remote_ahead2 = fourth_shared_remote_ahead - .append(vec![1, 2, 3]) - .encrypt::<PASETO_V4>(&[0; 32]); - - let local = vec![ - shared_record.clone(), - second_shared.clone(), - third_shared.clone(), - fourth_shared.clone(), - fourth_shared_remote_ahead.clone(), - // single store, only local has it - local_only.clone(), - // bigger store, also only known by local - local_only_20.clone(), - local_only_21.clone(), - local_only_22.clone(), - local_only_23.clone(), - // another shared store, but local is ahead on this one - third_shared_local_ahead.clone(), - third_shared_local_ahead2.clone(), - ]; - - let remote = vec![ - remote_only.clone(), - remote_only_20.clone(), - remote_only_21.clone(), - remote_only_22.clone(), - remote_only_23.clone(), - remote_only_24.clone(), - shared_record.clone(), - second_shared.clone(), - third_shared.clone(), - second_shared_remote_ahead.clone(), - second_shared_remote_ahead2.clone(), - fourth_shared.clone(), - fourth_shared_remote_ahead.clone(), - fourth_shared_remote_ahead2.clone(), - ]; // remote knows about the already-synced, and one new record in a new store - - let (store, diff) = build_test_diff(local, remote).await; - let operations = sync::operations(diff, &store).await.unwrap(); - - assert_eq!(operations.len(), 7); - - let mut result_ops = vec![ - // We started with a shared record, but the remote knows of two newer records in the - // same store - Operation::Download { - local: Some(0), - remote: 2, - host: second_shared_remote_ahead.host.id, - tag: second_shared_remote_ahead.tag, - }, - // We have a shared record, local knows of the first two but not the last - Operation::Download { - local: Some(1), - remote: 2, - host: fourth_shared_remote_ahead2.host.id, - tag: fourth_shared_remote_ahead2.tag, - }, - // Remote knows of a store with a single record that local does not have - Operation::Download { - local: None, - remote: 0, - host: remote_only.host.id, - tag: remote_only.tag, - }, - // Remote knows of a store with a bunch of records that local does not have - Operation::Download { - local: None, - remote: 4, - host: remote_only_20.host.id, - tag: remote_only_20.tag, - }, - // Local knows of a record in a store that remote does not have - Operation::Upload { - local: 0, - remote: None, - host: local_only.host.id, - tag: local_only.tag, - }, - // Local knows of 4 records in a store that remote does not have - Operation::Upload { - local: 3, - remote: None, - host: local_only_20.host.id, - tag: local_only_20.tag, - }, - // Local knows of 2 more records in a shared store that remote only has one of - Operation::Upload { - local: 2, - remote: Some(0), - host: third_shared.host.id, - tag: third_shared.tag, - }, - ]; - - result_ops.sort_by_key(|op| match op { - Operation::Noop { host, tag } => (0, *host, tag.clone()), - - Operation::Upload { host, tag, .. } => (1, *host, tag.clone()), - - Operation::Download { host, tag, .. } => (2, *host, tag.clone()), - }); - - assert_eq!(result_ops, operations); - } } diff --git a/crates/turtle/src/atuin_client/secrets.rs b/crates/turtle/src/atuin_client/secrets.rs index 30723890..74d47ea6 100644 --- a/crates/turtle/src/atuin_client/secrets.rs +++ b/crates/turtle/src/atuin_client/secrets.rs @@ -16,7 +16,7 @@ type SpType<'a> = &'a [(&'a str, &'a str, TestValue<'a>)]; type SpType<'a> = &'a [(&'a str, &'a str)]; /// A list of `(name, regex, test)`, where `test` should match against `regex`. -pub(crate) static SECRET_PATTERNS: SpType = &[ +pub(crate) static SECRET_PATTERNS: SpType<'_> = &[ ( "AWS Access Key ID", "A[KS]IA[0-9A-Z]{16}", diff --git a/crates/turtle/src/atuin_client/settings.rs b/crates/turtle/src/atuin_client/settings.rs index c966ba67..074b2634 100644 --- a/crates/turtle/src/atuin_client/settings.rs +++ b/crates/turtle/src/atuin_client/settings.rs @@ -47,7 +47,7 @@ pub(crate) enum SearchMode { } impl SearchMode { - pub(crate) fn as_str(&self) -> &'static str { + pub(crate) fn as_str(self) -> &'static str { match self { SearchMode::Prefix => "PREFIX", SearchMode::FullText => "FULLTXT", @@ -56,7 +56,7 @@ impl SearchMode { SearchMode::DaemonFuzzy => "DAEMON", } } - pub(crate) fn next(&self, settings: &Settings) -> Self { + pub(crate) fn next(self, settings: &Settings) -> Self { match self { SearchMode::Prefix => SearchMode::FullText, // if the user is using skim, we go to skim @@ -94,7 +94,7 @@ pub(crate) enum FilterMode { } impl FilterMode { - pub(crate) fn as_str(&self) -> &'static str { + pub(crate) fn as_str(self) -> &'static str { match self { FilterMode::Global => "GLOBAL", FilterMode::Host => "HOST", @@ -427,7 +427,7 @@ pub(crate) struct Search { pub(crate) frecency_score_multiplier: f64, } -/// Log level for file logging. Maps to tracing's LevelFilter. +/// Log level for file logging. Maps to tracing's [`LevelFilter`]. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub(crate) enum LogLevel { @@ -440,8 +440,8 @@ pub(crate) enum LogLevel { } impl LogLevel { - /// Convert to a tracing directive string for use with EnvFilter. - pub(crate) fn as_directive(&self) -> &'static str { + /// Convert to a tracing directive string for use with [`EnvFilter`]. + pub(crate) fn as_directive(self) -> &'static str { match self { LogLevel::Trace => "trace", LogLevel::Debug => "debug", @@ -478,7 +478,7 @@ pub(crate) struct Logs { pub(crate) dir: String, /// Default log level for file logging. Defaults to "info". - /// Note: ATUIN_LOG environment variable overrides this. + /// Note: [`ATUIN_LOG`] environment variable overrides this. #[serde(default)] pub(crate) level: LogLevel, @@ -641,7 +641,7 @@ pub(crate) enum UiColumnType { impl UiColumnType { /// Returns the default width for this column type (in characters). /// The Command column returns 0 as it expands to fill remaining space. - pub(crate) fn default_width(&self) -> u16 { + pub(crate) fn default_width(self) -> u16 { match self { UiColumnType::Duration => 5, // "814ms" UiColumnType::Time => 9, // "459ms ago" @@ -683,7 +683,7 @@ impl UiColumn { // Custom deserialize to handle both string and object formats: // "duration" or { type = "duration", width = 8, expand = true } -impl<'de> serde::Deserialize<'de> for UiColumn { +impl<'de> Deserialize<'de> for UiColumn { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, @@ -695,7 +695,7 @@ impl<'de> serde::Deserialize<'de> for UiColumn { impl<'de> Visitor<'de> for UiColumnVisitor { type Value = UiColumn; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str( "a column type string or an object with 'type' and optional 'width'/'expand'", ) @@ -706,7 +706,7 @@ impl<'de> serde::Deserialize<'de> for UiColumn { E: de::Error, { let column_type: UiColumnType = - serde::Deserialize::deserialize(serde::de::value::StrDeserializer::new(value))?; + Deserialize::deserialize(de::value::StrDeserializer::new(value))?; Ok(UiColumn::new(column_type)) } @@ -730,7 +730,7 @@ impl<'de> serde::Deserialize<'de> for UiColumn { expand = Some(map.next_value()?); } _ => { - let _: serde::de::IgnoredAny = map.next_value()?; + let _: de::IgnoredAny = map.next_value()?; } } } @@ -979,18 +979,19 @@ impl Settings { } pub(crate) fn builder() -> Result<ConfigBuilder<DefaultState>> { - Self::builder_with_data_dir(&crate::atuin_common::utils::data_dir()) + Self::builder_with_data_dir(&utils::data_dir()) } + #[expect(clippy::too_many_lines)] fn builder_with_data_dir(data_dir: &std::path::Path) -> Result<ConfigBuilder<DefaultState>> { let db_path = data_dir.join("history.db"); let record_store_path = data_dir.join("records.db"); let kv_path = data_dir.join("kv.db"); let scripts_path = data_dir.join("scripts.db"); let ai_sessions_path = data_dir.join("ai_sessions.db"); - let socket_path = crate::atuin_common::utils::runtime_dir().join("atuin.sock"); + let socket_path = utils::runtime_dir().join("atuin.sock"); let pidfile_path = data_dir.join("atuin-daemon.pid"); - let logs_dir = crate::atuin_common::utils::logs_dir(); + let logs_dir = utils::logs_dir(); let key_path = data_dir.join("key"); let meta_path = data_dir.join("meta.db"); @@ -1091,10 +1092,10 @@ impl Settings { .set_default("tmux.height", "60%")? .set_default( "prefers_reduced_motion", - std::env::var("NO_MOTION") - .ok() - .map(|_| config::Value::new(None, config::ValueKind::Boolean(true))) - .unwrap_or_else(|| config::Value::new(None, config::ValueKind::Boolean(false))), + std::env::var("NO_MOTION").ok().map_or_else( + || config::Value::new(None, config::ValueKind::Boolean(false)), + |_| config::Value::new(None, config::ValueKind::Boolean(true)), + ), )? .set_default("no_mouse", false)? .add_source( @@ -1105,18 +1106,19 @@ impl Settings { } pub(crate) fn get_config_path() -> Result<PathBuf> { - let config_dir = crate::atuin_common::utils::config_dir(); + let config_dir = utils::config_dir(); create_dir_all(&config_dir) .wrap_err_with(|| format!("could not create dir {}", config_dir.display()))?; - let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") { - PathBuf::from(p) - } else { - let mut config_file = PathBuf::new(); - config_file.push(config_dir); - config_file - }; + let mut config_file = std::env::var("ATUIN_CONFIG_DIR").map_or_else( + |_| { + let mut config_file = PathBuf::new(); + config_file.push(config_dir); + config_file + }, + PathBuf::from, + ); config_file.push("config.toml"); @@ -1162,16 +1164,16 @@ impl Settings { .map_err(|e| eyre!("failed to expand data_dir path: {}", e))?; PathBuf::from(expanded.as_ref()) } - None => crate::atuin_common::utils::data_dir(), + None => utils::data_dir(), } } else { - crate::atuin_common::utils::data_dir() + utils::data_dir() }; DATA_DIR.set(effective_data_dir.clone()).ok(); create_dir_all(&effective_data_dir) - .wrap_err_with(|| format!("could not create dir {effective_data_dir:?}"))?; + .wrap_err_with(|| format!("could not create dir {}", effective_data_dir.display()))?; let mut config_builder = Self::builder_with_data_dir(&effective_data_dir)?; @@ -1205,7 +1207,7 @@ impl Settings { ] .iter() .map(|key| (key, built.get_string(key).unwrap_or_default())) - .filter_map(|(key, value)| match Self::expand_path(value) { + .filter_map(|(key, value)| match Self::expand_path(&value) { Ok(expanded) => Some((key, expanded)), Err(e) => { log::warn!("failed to expand path for {key}: {e}"); @@ -1263,7 +1265,7 @@ impl Settings { let full_key = if prefix.is_empty() { k.clone() } else { - format!("{}.{}", prefix, k) + format!("{prefix}.{k}") }; match &v.kind { @@ -1302,7 +1304,7 @@ impl Settings { Ok(settings) } - fn expand_path(path: String) -> Result<String> { + fn expand_path(path: &str) -> Result<String> { shellexpand::full(&path) .map(|p| p.to_string()) .map_err(|e| eyre!("failed to expand path: {}", e)) diff --git a/crates/turtle/src/atuin_client/settings/watcher.rs b/crates/turtle/src/atuin_client/settings/watcher.rs index e280480c..5eec3692 100644 --- a/crates/turtle/src/atuin_client/settings/watcher.rs +++ b/crates/turtle/src/atuin_client/settings/watcher.rs @@ -134,7 +134,7 @@ impl SettingsWatcher { warn!( "config watcher: event has no paths, triggering reload to be safe" ); - let _ = debounce_tx.send(()); + debounce_tx.send(()).expect("should still be active"); return; } @@ -163,7 +163,7 @@ impl SettingsWatcher { ) { debug!("config file event detected: {event:?}"); // Send to debounce channel (ignore send errors - receiver might be gone) - let _ = debounce_tx.send(()); + debounce_tx.send(()).ok(); } } Err(e) => { |
