diff --git a/crates/atproto-client/src/client.rs b/crates/atproto-client/src/client.rs index 4b0e9cd..9a7ffd8 100644 --- a/crates/atproto-client/src/client.rs +++ b/crates/atproto-client/src/client.rs @@ -4,7 +4,7 @@ //! with support for DPoP (Demonstration of Proof-of-Possession) authentication. use crate::errors::{ClientError, DPoPError}; -use anyhow::{Context, Result}; +use anyhow::Result; use atproto_identity::key::KeyData; use atproto_oauth::dpop::{DpopRetry, request_dpop}; use bytes::Bytes; @@ -77,7 +77,6 @@ pub async fn get_json_with_headers( .get(url) .headers(additional_headers.clone()) .send() - .instrument(tracing::info_span!("get_json_with_headers", url = %url)) .await .map_err(|error| ClientError::HttpRequestFailed { url: url.to_string(), @@ -140,16 +139,18 @@ pub async fn get_bytes_with_headers( .get(url) .headers(additional_headers.clone()) .send() - .instrument(tracing::info_span!("get_bytes_with_headers", url = %url)) .await .map_err(|error| ClientError::HttpRequestFailed { url: url.to_string(), error, })?; - http_response + Ok(http_response .bytes() .await - .context("failed streaming bytes") + .map_err(|error| ClientError::ByteStreamFailed { + url: url.to_string(), + error, + })?) } /// Performs a DPoP-authenticated HTTP GET request and parses the response as JSON. @@ -230,28 +231,15 @@ pub async fn get_dpop_json_with_headers( ) .header("DPoP", &dpop_proof_token) .send() - .instrument(tracing::info_span!("dpop_get_request_with_options", url = %url)) .await - .inspect_err(|err| { - println!("response error: {err:?}"); - }) .map_err(|error| DPoPError::HttpRequestFailed { url: url.to_string(), error, })?; - let status_code = http_response.status(); - println!("status_code {status_code:?}"); - - let headers = http_response.headers(); - println!("headers {headers:?}"); - let value = http_response .json::() .await - .inspect_err(|err| { - println!("json error: {err:?}"); - }) .map_err(|error| DPoPError::JsonParseFailed { url: url.to_string(), error, @@ -376,7 +364,6 @@ pub async fn post_dpop_json_with_headers( .header("DPoP", &dpop_proof_token) .json(&record) .send() - .instrument(tracing::info_span!("dpop_post_request", url = %url)) .await .map_err(|error| DPoPError::HttpRequestFailed { url: url.to_string(), @@ -447,7 +434,6 @@ pub async fn post_json_with_headers( .headers(additional_headers.clone()) .json(&data) .send() - .instrument(tracing::info_span!("post_json_with_headers", url = %url)) .await .map_err(|error| ClientError::HttpRequestFailed { url: url.to_string(), @@ -523,7 +509,6 @@ pub async fn get_apppassword_json_with_headers( .get(url) .headers(headers) .send() - .instrument(tracing::info_span!("get_apppassword_json_with_headers", url = %url)) .await .map_err(|error| ClientError::HttpRequestFailed { url: url.to_string(), @@ -604,7 +589,6 @@ pub async fn post_apppassword_json_with_headers( .headers(headers) .json(&data) .send() - .instrument(tracing::info_span!("post_apppassword_json_with_headers", url = %url)) .await .map_err(|error| ClientError::HttpRequestFailed { url: url.to_string(), @@ -621,3 +605,103 @@ pub async fn post_apppassword_json_with_headers( Ok(value) } + +/// Performs an app password-authenticated HTTP GET request and returns the response as bytes. +/// +/// # Arguments +/// +/// * `http_client` - The HTTP client to use for the request +/// * `app_auth` - App password authentication credentials +/// * `url` - The URL to request +/// * `additional_headers` - Additional HTTP headers to include in the request +/// +/// # Returns +/// +/// The response body as bytes +/// +/// # Errors +/// +/// Returns `ClientError::HttpRequestFailed` if the HTTP request fails, +/// or an error if streaming the response bytes fails. +pub async fn get_apppassword_bytes_with_headers( + http_client: &reqwest::Client, + app_auth: &AppPasswordAuth, + url: &str, + additional_headers: &HeaderMap, +) -> Result { + let mut headers = additional_headers.clone(); + headers.insert( + reqwest::header::AUTHORIZATION, + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?, + ); + let http_response = http_client + .get(url) + .headers(headers) + .send() + .await + .map_err(|error| ClientError::HttpRequestFailed { + url: url.to_string(), + error, + })?; + Ok(http_response + .bytes() + .await + .map_err(|error| ClientError::ByteStreamFailed { + url: url.to_string(), + error, + })?) +} + + +/// Performs an app password-authenticated HTTP POST request with JSON body and returns the response as bytes. +/// +/// This is useful when the server returns binary data such as images, CAR files, +/// or other non-JSON content in response to authenticated POST requests. +/// +/// # Arguments +/// +/// * `http_client` - The HTTP client to use for the request +/// * `app_auth` - App password authentication credentials +/// * `url` - The URL to request +/// * `record` - The JSON data to send in the request body +/// * `additional_headers` - Additional HTTP headers to include in the request +/// +/// # Returns +/// +/// The response body as bytes +/// +/// # Errors +/// +/// Returns `ClientError::HttpRequestFailed` if the HTTP request fails, +/// or an error if streaming the response bytes fails. +pub async fn post_apppassword_bytes_with_headers( + http_client: &reqwest::Client, + app_auth: &AppPasswordAuth, + url: &str, + record: serde_json::Value, + additional_headers: &HeaderMap, +) -> Result { + let mut headers = additional_headers.clone(); + headers.insert( + reqwest::header::AUTHORIZATION, + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?, + ); + let http_response = http_client + .post(url) + .headers(headers) + .json(&record) + .send() + .instrument(tracing::info_span!("get_apppassword_bytes_with_headers", url = %url)) + .await + .map_err(|error| ClientError::HttpRequestFailed { + url: url.to_string(), + error, + })?; + Ok(http_response + .bytes() + .await + .map_err(|error| ClientError::ByteStreamFailed { + url: url.to_string(), + error, + })?) +} \ No newline at end of file diff --git a/crates/atproto-client/src/errors.rs b/crates/atproto-client/src/errors.rs index 1f46d9c..602cba9 100644 --- a/crates/atproto-client/src/errors.rs +++ b/crates/atproto-client/src/errors.rs @@ -75,6 +75,15 @@ pub enum ClientError { /// The underlying parse error error: reqwest::Error, }, + + /// Occurs when streaming response bytes fails + #[error("error-atproto-client-http-3 Failed to stream response bytes: {url} {error}")] + ByteStreamFailed { + /// The URL that was requested + url: String, + /// The underlying streaming error + error: reqwest::Error, + }, } /// Error types that can occur during DPoP authentication operations.