+106
-22
crates/atproto-client/src/client.rs
+106
-22
crates/atproto-client/src/client.rs
···
4
//! with support for DPoP (Demonstration of Proof-of-Possession) authentication.
5
6
use crate::errors::{ClientError, DPoPError};
7
-
use anyhow::{Context, Result};
8
use atproto_identity::key::KeyData;
9
use atproto_oauth::dpop::{DpopRetry, request_dpop};
10
use bytes::Bytes;
···
77
.get(url)
78
.headers(additional_headers.clone())
79
.send()
80
-
.instrument(tracing::info_span!("get_json_with_headers", url = %url))
81
.await
82
.map_err(|error| ClientError::HttpRequestFailed {
83
url: url.to_string(),
···
140
.get(url)
141
.headers(additional_headers.clone())
142
.send()
143
-
.instrument(tracing::info_span!("get_bytes_with_headers", url = %url))
144
.await
145
.map_err(|error| ClientError::HttpRequestFailed {
146
url: url.to_string(),
147
error,
148
})?;
149
-
http_response
150
.bytes()
151
.await
152
-
.context("failed streaming bytes")
153
}
154
155
/// Performs a DPoP-authenticated HTTP GET request and parses the response as JSON.
···
230
)
231
.header("DPoP", &dpop_proof_token)
232
.send()
233
-
.instrument(tracing::info_span!("dpop_get_request_with_options", url = %url))
234
.await
235
-
.inspect_err(|err| {
236
-
println!("response error: {err:?}");
237
-
})
238
.map_err(|error| DPoPError::HttpRequestFailed {
239
url: url.to_string(),
240
error,
241
})?;
242
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
let value = http_response
250
.json::<serde_json::Value>()
251
.await
252
-
.inspect_err(|err| {
253
-
println!("json error: {err:?}");
254
-
})
255
.map_err(|error| DPoPError::JsonParseFailed {
256
url: url.to_string(),
257
error,
···
376
.header("DPoP", &dpop_proof_token)
377
.json(&record)
378
.send()
379
-
.instrument(tracing::info_span!("dpop_post_request", url = %url))
380
.await
381
.map_err(|error| DPoPError::HttpRequestFailed {
382
url: url.to_string(),
···
447
.headers(additional_headers.clone())
448
.json(&data)
449
.send()
450
-
.instrument(tracing::info_span!("post_json_with_headers", url = %url))
451
.await
452
.map_err(|error| ClientError::HttpRequestFailed {
453
url: url.to_string(),
···
523
.get(url)
524
.headers(headers)
525
.send()
526
-
.instrument(tracing::info_span!("get_apppassword_json_with_headers", url = %url))
527
.await
528
.map_err(|error| ClientError::HttpRequestFailed {
529
url: url.to_string(),
···
604
.headers(headers)
605
.json(&data)
606
.send()
607
-
.instrument(tracing::info_span!("post_apppassword_json_with_headers", url = %url))
608
.await
609
.map_err(|error| ClientError::HttpRequestFailed {
610
url: url.to_string(),
···
621
622
Ok(value)
623
}
···
4
//! with support for DPoP (Demonstration of Proof-of-Possession) authentication.
5
6
use crate::errors::{ClientError, DPoPError};
7
+
use anyhow::Result;
8
use atproto_identity::key::KeyData;
9
use atproto_oauth::dpop::{DpopRetry, request_dpop};
10
use bytes::Bytes;
···
77
.get(url)
78
.headers(additional_headers.clone())
79
.send()
80
.await
81
.map_err(|error| ClientError::HttpRequestFailed {
82
url: url.to_string(),
···
139
.get(url)
140
.headers(additional_headers.clone())
141
.send()
142
.await
143
.map_err(|error| ClientError::HttpRequestFailed {
144
url: url.to_string(),
145
error,
146
})?;
147
+
Ok(http_response
148
.bytes()
149
.await
150
+
.map_err(|error| ClientError::ByteStreamFailed {
151
+
url: url.to_string(),
152
+
error,
153
+
})?)
154
}
155
156
/// Performs a DPoP-authenticated HTTP GET request and parses the response as JSON.
···
231
)
232
.header("DPoP", &dpop_proof_token)
233
.send()
234
.await
235
.map_err(|error| DPoPError::HttpRequestFailed {
236
url: url.to_string(),
237
error,
238
})?;
239
240
let value = http_response
241
.json::<serde_json::Value>()
242
.await
243
.map_err(|error| DPoPError::JsonParseFailed {
244
url: url.to_string(),
245
error,
···
364
.header("DPoP", &dpop_proof_token)
365
.json(&record)
366
.send()
367
.await
368
.map_err(|error| DPoPError::HttpRequestFailed {
369
url: url.to_string(),
···
434
.headers(additional_headers.clone())
435
.json(&data)
436
.send()
437
.await
438
.map_err(|error| ClientError::HttpRequestFailed {
439
url: url.to_string(),
···
509
.get(url)
510
.headers(headers)
511
.send()
512
.await
513
.map_err(|error| ClientError::HttpRequestFailed {
514
url: url.to_string(),
···
589
.headers(headers)
590
.json(&data)
591
.send()
592
.await
593
.map_err(|error| ClientError::HttpRequestFailed {
594
url: url.to_string(),
···
605
606
Ok(value)
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
/// The underlying parse error
76
error: reqwest::Error,
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
+
},
87
}
88
89
/// Error types that can occur during DPoP authentication operations.