diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-20 12:57:36 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-20 12:57:36 +0200 |
| commit | 73be69cd99f5a4784fe69f5d78f423c72e837284 (patch) | |
| tree | cb47246d2c7edd8cd828abb25a033b732d2289ae /crates/client/src/atuin_client/api_client.rs | |
| parent | chore: Move more stuff out of atuin-client (diff) | |
| download | atuin-73be69cd99f5a4784fe69f5d78f423c72e837284.zip | |
chore: Commit
Diffstat (limited to 'crates/client/src/atuin_client/api_client.rs')
| -rw-r--r-- | crates/client/src/atuin_client/api_client.rs | 197 |
1 files changed, 0 insertions, 197 deletions
diff --git a/crates/client/src/atuin_client/api_client.rs b/crates/client/src/atuin_client/api_client.rs deleted file mode 100644 index bd5bf59e..00000000 --- a/crates/client/src/atuin_client/api_client.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::env; -use std::time::Duration; - -use eyre::{Result, bail, eyre}; -use reqwest::{Response, StatusCode, Url, header::HeaderMap}; -use tracing::debug; -use uuid::Uuid; - -use crate::atuin_common::{api::ErrorResponse, record::RecordStatus}; -use crate::atuin_common::{ - api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ATUIN_VERSION}, - record::{EncryptedData, HostId, Record, RecordIdx}, - tls::ensure_crypto_provider, -}; - -use semver::Version; - -static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); - -pub(crate) struct Client<'a> { - sync_addr: &'a str, - user_id: Uuid, - inner: reqwest::Client, -} - -fn make_url(address: &str, path: &str, user_id: Uuid) -> Result<String> { - let address = address.strip_suffix('/').unwrap_or(address); - - // `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}/"); - - // passing a path with a leading `/` will cause `join()` to replace the entire URL path - let path = path.strip_prefix("/").unwrap_or(path); - - let url = Url::parse(address) - .map(|url| url.join(path))? - .map_err(|_| eyre!("invalid address"))?; - - Ok(url.to_string()) -} - -pub(crate) fn ensure_version(response: &Response) -> Result<bool> { - let version = response.headers().get(ATUIN_HEADER_VERSION); - - let version = if let Some(version) = version { - match version.to_str() { - Ok(v) => Version::parse(v), - Err(e) => bail!("failed to parse server version: {:?}", e), - } - } else { - bail!("Server not reporting its version: it is either too old or unhealthy"); - }?; - - // If the client is newer than the server - if version.major < ATUIN_VERSION.major { - println!( - "Atuin version mismatch! In order to successfully sync, the server needs to run a newer version of Atuin" - ); - println!("Client: {ATUIN_CARGO_VERSION}"); - println!("Server: {version}"); - - return Ok(false); - } - - Ok(true) -} - -async fn handle_resp_error(resp: Response) -> Result<Response> { - let status = resp.status(); - let url = resp.url().to_string(); - - if status == StatusCode::SERVICE_UNAVAILABLE { - bail!( - "Service unavailable: check https://status.atuin.sh (or get in touch with your host)" - ); - } - - if status == StatusCode::TOO_MANY_REQUESTS { - bail!("Rate limited; please wait before doing that again"); - } - - if !status.is_success() { - if let Ok(error) = resp.json::<ErrorResponse<'_>>().await { - let reason = error.reason; - - if status.is_client_error() { - bail!("Invalid request to the service at {url}, {status} - {reason}.") - } - - bail!( - "There was an error with the atuin sync service at {url}, server error {status}: {reason}.\nIf the problem persists, contact the host" - ) - } - - bail!( - "There was an error with the atuin sync service at {url}, Status {status:?}.\nIf the problem persists, contact the host" - ) - } - - Ok(resp) -} - -impl<'a> Client<'a> { - pub(crate) fn new( - sync_addr: &'a str, - connect_timeout: u64, - timeout: u64, - user_id: Uuid, - ) -> Result<Self> { - ensure_crypto_provider(); - let mut headers = HeaderMap::new(); - - // used for semver server check - headers.insert(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION.parse()?); - - Ok(Client { - user_id, - sync_addr, - inner: reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .default_headers(headers) - .connect_timeout(Duration::new(connect_timeout, 0)) - .timeout(Duration::new(timeout, 0)) - .build()?, - }) - } - - pub(crate) async fn delete_store(&self) -> Result<()> { - let url = make_url(self.sync_addr, "/store", self.user_id)?; - let url = Url::parse(url.as_str())?; - - let resp = self.inner.delete(url).send().await?; - - handle_resp_error(resp).await?; - - Ok(()) - } - - pub(crate) async fn post_records(&self, records: &[Record<EncryptedData>]) -> Result<()> { - let url = make_url(self.sync_addr, "/record", self.user_id)?; - let url = Url::parse(url.as_str())?; - - debug!("uploading {} records to {url}", records.len()); - - let resp = self.inner.post(url).json(records).send().await?; - handle_resp_error(resp).await?; - - Ok(()) - } - - pub(crate) async fn next_records( - &self, - host: HostId, - tag: String, - start: RecordIdx, - count: u64, - ) -> Result<Vec<Record<EncryptedData>>> { - debug!("fetching record/s from host {}/{}/{}", host.0, tag, start); - - let url = make_url( - self.sync_addr, - &format!( - "/record/next?host={}&tag={}&count={}&start={}", - host.0, tag, count, start - ), - self.user_id, - )?; - - let url = Url::parse(url.as_str())?; - - let resp = self.inner.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - let records = resp.json::<Vec<Record<EncryptedData>>>().await?; - - Ok(records) - } - - pub(crate) async fn record_status(&self) -> Result<RecordStatus> { - let url = make_url(self.sync_addr, "/record", self.user_id)?; - let url = Url::parse(url.as_str())?; - - let resp = self.inner.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - if !ensure_version(&resp)? { - bail!("could not sync records due to version mismatch"); - } - - let index = resp.json().await?; - - debug!("got remote index {index:?}"); - - Ok(index) - } -} |
