+106
-22
crates/atproto-client/src/client.rs
+106
-22
crates/atproto-client/src/client.rs
···
4
4
//! with support for DPoP (Demonstration of Proof-of-Possession) authentication.
5
5
6
6
use crate::errors::{ClientError, DPoPError};
7
-
use anyhow::{Context, Result};
7
+
use anyhow::Result;
8
8
use atproto_identity::key::KeyData;
9
9
use atproto_oauth::dpop::{DpopRetry, request_dpop};
10
10
use bytes::Bytes;
···
77
77
.get(url)
78
78
.headers(additional_headers.clone())
79
79
.send()
80
-
.instrument(tracing::info_span!("get_json_with_headers", url = %url))
81
80
.await
82
81
.map_err(|error| ClientError::HttpRequestFailed {
83
82
url: url.to_string(),
···
140
139
.get(url)
141
140
.headers(additional_headers.clone())
142
141
.send()
143
-
.instrument(tracing::info_span!("get_bytes_with_headers", url = %url))
144
142
.await
145
143
.map_err(|error| ClientError::HttpRequestFailed {
146
144
url: url.to_string(),
147
145
error,
148
146
})?;
149
-
http_response
147
+
Ok(http_response
150
148
.bytes()
151
149
.await
152
-
.context("failed streaming bytes")
150
+
.map_err(|error| ClientError::ByteStreamFailed {
151
+
url: url.to_string(),
152
+
error,
153
+
})?)
153
154
}
154
155
155
156
/// Performs a DPoP-authenticated HTTP GET request and parses the response as JSON.
···
230
231
)
231
232
.header("DPoP", &dpop_proof_token)
232
233
.send()
233
-
.instrument(tracing::info_span!("dpop_get_request_with_options", url = %url))
234
234
.await
235
-
.inspect_err(|err| {
236
-
println!("response error: {err:?}");
237
-
})
238
235
.map_err(|error| DPoPError::HttpRequestFailed {
239
236
url: url.to_string(),
240
237
error,
241
238
})?;
242
239
243
-
let status_code = http_response.status();
244
-
println!("status_code {status_code:?}");
245
-
246
-
let headers = http_response.headers();
247
-
println!("headers {headers:?}");
248
-
249
240
let value = http_response
250
241
.json::<serde_json::Value>()
251
242
.await
252
-
.inspect_err(|err| {
253
-
println!("json error: {err:?}");
254
-
})
255
243
.map_err(|error| DPoPError::JsonParseFailed {
256
244
url: url.to_string(),
257
245
error,
···
376
364
.header("DPoP", &dpop_proof_token)
377
365
.json(&record)
378
366
.send()
379
-
.instrument(tracing::info_span!("dpop_post_request", url = %url))
380
367
.await
381
368
.map_err(|error| DPoPError::HttpRequestFailed {
382
369
url: url.to_string(),
···
447
434
.headers(additional_headers.clone())
448
435
.json(&data)
449
436
.send()
450
-
.instrument(tracing::info_span!("post_json_with_headers", url = %url))
451
437
.await
452
438
.map_err(|error| ClientError::HttpRequestFailed {
453
439
url: url.to_string(),
···
523
509
.get(url)
524
510
.headers(headers)
525
511
.send()
526
-
.instrument(tracing::info_span!("get_apppassword_json_with_headers", url = %url))
527
512
.await
528
513
.map_err(|error| ClientError::HttpRequestFailed {
529
514
url: url.to_string(),
···
604
589
.headers(headers)
605
590
.json(&data)
606
591
.send()
607
-
.instrument(tracing::info_span!("post_apppassword_json_with_headers", url = %url))
608
592
.await
609
593
.map_err(|error| ClientError::HttpRequestFailed {
610
594
url: url.to_string(),
···
621
605
622
606
Ok(value)
623
607
}
608
+
609
+
/// Performs an app password-authenticated HTTP GET request and returns the response as bytes.
610
+
///
611
+
/// # Arguments
612
+
///
613
+
/// * `http_client` - The HTTP client to use for the request
614
+
/// * `app_auth` - App password authentication credentials
615
+
/// * `url` - The URL to request
616
+
/// * `additional_headers` - Additional HTTP headers to include in the request
617
+
///
618
+
/// # Returns
619
+
///
620
+
/// The response body as bytes
621
+
///
622
+
/// # Errors
623
+
///
624
+
/// Returns `ClientError::HttpRequestFailed` if the HTTP request fails,
625
+
/// or an error if streaming the response bytes fails.
626
+
pub async fn get_apppassword_bytes_with_headers(
627
+
http_client: &reqwest::Client,
628
+
app_auth: &AppPasswordAuth,
629
+
url: &str,
630
+
additional_headers: &HeaderMap,
631
+
) -> Result<Bytes> {
632
+
let mut headers = additional_headers.clone();
633
+
headers.insert(
634
+
reqwest::header::AUTHORIZATION,
635
+
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
636
+
);
637
+
let http_response = http_client
638
+
.get(url)
639
+
.headers(headers)
640
+
.send()
641
+
.await
642
+
.map_err(|error| ClientError::HttpRequestFailed {
643
+
url: url.to_string(),
644
+
error,
645
+
})?;
646
+
Ok(http_response
647
+
.bytes()
648
+
.await
649
+
.map_err(|error| ClientError::ByteStreamFailed {
650
+
url: url.to_string(),
651
+
error,
652
+
})?)
653
+
}
654
+
655
+
656
+
/// Performs an app password-authenticated HTTP POST request with JSON body and returns the response as bytes.
657
+
///
658
+
/// This is useful when the server returns binary data such as images, CAR files,
659
+
/// or other non-JSON content in response to authenticated POST requests.
660
+
///
661
+
/// # Arguments
662
+
///
663
+
/// * `http_client` - The HTTP client to use for the request
664
+
/// * `app_auth` - App password authentication credentials
665
+
/// * `url` - The URL to request
666
+
/// * `record` - The JSON data to send in the request body
667
+
/// * `additional_headers` - Additional HTTP headers to include in the request
668
+
///
669
+
/// # Returns
670
+
///
671
+
/// The response body as bytes
672
+
///
673
+
/// # Errors
674
+
///
675
+
/// Returns `ClientError::HttpRequestFailed` if the HTTP request fails,
676
+
/// or an error if streaming the response bytes fails.
677
+
pub async fn post_apppassword_bytes_with_headers(
678
+
http_client: &reqwest::Client,
679
+
app_auth: &AppPasswordAuth,
680
+
url: &str,
681
+
record: serde_json::Value,
682
+
additional_headers: &HeaderMap,
683
+
) -> Result<Bytes> {
684
+
let mut headers = additional_headers.clone();
685
+
headers.insert(
686
+
reqwest::header::AUTHORIZATION,
687
+
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
688
+
);
689
+
let http_response = http_client
690
+
.post(url)
691
+
.headers(headers)
692
+
.json(&record)
693
+
.send()
694
+
.instrument(tracing::info_span!("get_apppassword_bytes_with_headers", url = %url))
695
+
.await
696
+
.map_err(|error| ClientError::HttpRequestFailed {
697
+
url: url.to_string(),
698
+
error,
699
+
})?;
700
+
Ok(http_response
701
+
.bytes()
702
+
.await
703
+
.map_err(|error| ClientError::ByteStreamFailed {
704
+
url: url.to_string(),
705
+
error,
706
+
})?)
707
+
}
+9
crates/atproto-client/src/errors.rs
+9
crates/atproto-client/src/errors.rs
···
75
75
/// The underlying parse error
76
76
error: reqwest::Error,
77
77
},
78
+
79
+
/// Occurs when streaming response bytes fails
80
+
#[error("error-atproto-client-http-3 Failed to stream response bytes: {url} {error}")]
81
+
ByteStreamFailed {
82
+
/// The URL that was requested
83
+
url: String,
84
+
/// The underlying streaming error
85
+
error: reqwest::Error,
86
+
},
78
87
}
79
88
80
89
/// Error types that can occur during DPoP authentication operations.