Skip to content

Commit 2022414

Browse files
authored
feat: Add headers() to XrpcClient (#170)
* Update XRPC * Add headers to XrpcClient
1 parent d4a3cbb commit 2022414

File tree

5 files changed

+186
-158
lines changed

5 files changed

+186
-158
lines changed

atrium-api/src/agent/inner.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::{Session, SessionStore};
22
use crate::did_doc::DidDocument;
33
use async_trait::async_trait;
4-
use atrium_xrpc::error::{Error, XrpcErrorKind};
5-
use atrium_xrpc::{HttpClient, OutputDataOrBytes, XrpcClient, XrpcRequest, XrpcResult};
4+
use atrium_xrpc::error::{Error, Result, XrpcErrorKind};
5+
use atrium_xrpc::{HttpClient, OutputDataOrBytes, XrpcClient, XrpcRequest};
66
use http::{Method, Request, Response, Uri};
77
use serde::{de::DeserializeOwned, Serialize};
88
use std::sync::{Arc, RwLock};
@@ -25,7 +25,8 @@ where
2525
async fn send_http(
2626
&self,
2727
request: Request<Vec<u8>>,
28-
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
28+
) -> core::result::Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>>
29+
{
2930
self.inner.send_http(request).await
3031
}
3132
}
@@ -113,7 +114,7 @@ where
113114
&self,
114115
) -> Result<
115116
crate::com::atproto::server::refresh_session::Output,
116-
Error<crate::com::atproto::server::refresh_session::Error>,
117+
crate::com::atproto::server::refresh_session::Error,
117118
> {
118119
let response = self
119120
.inner
@@ -130,7 +131,7 @@ where
130131
_ => Err(Error::UnexpectedResponseType),
131132
}
132133
}
133-
fn is_expired<O, E>(result: &XrpcResult<O, E>) -> bool
134+
fn is_expired<O, E>(result: &Result<OutputDataOrBytes<O>, E>) -> bool
134135
where
135136
O: DeserializeOwned + Send + Sync,
136137
E: DeserializeOwned + Send + Sync,
@@ -156,7 +157,8 @@ where
156157
async fn send_http(
157158
&self,
158159
request: Request<Vec<u8>>,
159-
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
160+
) -> core::result::Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>>
161+
{
160162
self.inner.send_http(request).await
161163
}
162164
}
@@ -171,7 +173,10 @@ where
171173
fn base_uri(&self) -> String {
172174
self.inner.base_uri()
173175
}
174-
async fn send_xrpc<P, I, O, E>(&self, request: &XrpcRequest<P, I>) -> XrpcResult<O, E>
176+
async fn send_xrpc<P, I, O, E>(
177+
&self,
178+
request: &XrpcRequest<P, I>,
179+
) -> Result<OutputDataOrBytes<O>, E>
175180
where
176181
P: Serialize + Send + Sync,
177182
I: Serialize + Send + Sync,

atrium-xrpc/src/error.rs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
#![doc = "Error types."]
22
use http::StatusCode;
3+
use serde::{Deserialize, Serialize};
34
use std::fmt::{self, Debug, Display};
45

6+
/// An enum of possible error kinds.
7+
#[derive(thiserror::Error, Debug)]
8+
pub enum Error<E> {
9+
#[error("xrpc response error: {0}")]
10+
XrpcResponse(XrpcError<E>),
11+
#[error("http request error: {0}")]
12+
HttpRequest(#[from] http::Error),
13+
#[error("http client error: {0}")]
14+
HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
15+
#[error("serde_json error: {0}")]
16+
SerdeJson(#[from] serde_json::Error),
17+
#[error("serde_html_form error: {0}")]
18+
SerdeHtmlForm(#[from] serde_html_form::ser::Error),
19+
#[error("unexpected response type")]
20+
UnexpectedResponseType,
21+
}
22+
23+
/// Type alias to use this library's [`Error`] type in a [`Result`](core::result::Result).
24+
pub type Result<T, E> = core::result::Result<T, Error<E>>;
25+
526
/// [A standard error response schema](https://atproto.com/specs/xrpc#error-responses)
627
///
728
/// ```typescript
@@ -11,7 +32,7 @@ use std::fmt::{self, Debug, Display};
1132
/// })
1233
/// export type ErrorResponseBody = z.infer<typeof errorResponseBody>
1334
/// ```
14-
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
35+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1536
pub struct ErrorResponseBody {
1637
pub error: Option<String>,
1738
pub message: Option<String>,
@@ -30,7 +51,7 @@ impl Display for ErrorResponseBody {
3051
/// An enum of possible XRPC error kinds.
3152
///
3253
/// Error defined in Lexicon schema or other standard error.
33-
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
54+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
3455
#[serde(untagged)]
3556
pub enum XrpcErrorKind<E> {
3657
Custom(E),
@@ -66,20 +87,3 @@ impl<E: Display> Display for XrpcError<E> {
6687
Ok(())
6788
}
6889
}
69-
70-
/// An enum of possible error kinds.
71-
#[derive(thiserror::Error, Debug)]
72-
pub enum Error<E> {
73-
#[error("xrpc response error: {0}")]
74-
XrpcResponse(XrpcError<E>),
75-
#[error("http request error: {0}")]
76-
HttpRequest(#[from] http::Error),
77-
#[error("http client error: {0}")]
78-
HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
79-
#[error("serde_json error: {0}")]
80-
SerdeJson(#[from] serde_json::Error),
81-
#[error("serde_html_form error: {0}")]
82-
SerdeHtmlForm(#[from] serde_html_form::ser::Error),
83-
#[error("unexpected response type")]
84-
UnexpectedResponseType,
85-
}

atrium-xrpc/src/lib.rs

Lines changed: 17 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,19 @@
11
#![doc = include_str!("../README.md")]
22
pub mod error;
3+
mod traits;
4+
mod types;
35

4-
use crate::error::{Error, XrpcError, XrpcErrorKind};
5-
use async_trait::async_trait;
6-
use http::{Method, Request, Response};
7-
use serde::{de::DeserializeOwned, Serialize};
8-
9-
/// A type which can be used as a parameter of [`XrpcRequest`].
10-
///
11-
/// JSON serializable data or raw bytes.
12-
pub enum InputDataOrBytes<T>
13-
where
14-
T: Serialize,
15-
{
16-
Data(T),
17-
Bytes(Vec<u8>),
18-
}
19-
20-
/// A type which can be used as a return value of [`XrpcClient::send_xrpc()`].
21-
///
22-
/// JSON deserializable data or raw bytes.
23-
pub enum OutputDataOrBytes<T>
24-
where
25-
T: DeserializeOwned,
26-
{
27-
Data(T),
28-
Bytes(Vec<u8>),
29-
}
30-
31-
/// An abstract HTTP client.
32-
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
33-
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
34-
pub trait HttpClient {
35-
async fn send_http(
36-
&self,
37-
request: Request<Vec<u8>>,
38-
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>>;
39-
}
40-
41-
/// A request which can be executed with [`XrpcClient::send_xrpc()`].
42-
pub struct XrpcRequest<P, I>
43-
where
44-
I: Serialize,
45-
{
46-
pub method: Method,
47-
pub path: String,
48-
pub parameters: Option<P>,
49-
pub input: Option<InputDataOrBytes<I>>,
50-
pub encoding: Option<String>,
51-
}
52-
53-
pub type XrpcResult<O, E> = Result<OutputDataOrBytes<O>, self::Error<E>>;
54-
55-
/// An abstract XRPC client.
56-
///
57-
/// [`send_xrpc()`](XrpcClient::send_xrpc) method has a default implementation,
58-
/// which wraps the [`HttpClient::send_http()`]` method to handle input and output as an XRPC Request.
59-
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
60-
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
61-
pub trait XrpcClient: HttpClient {
62-
fn base_uri(&self) -> String;
63-
#[allow(unused_variables)]
64-
async fn auth(&self, is_refresh: bool) -> Option<String> {
65-
None
66-
}
67-
async fn send_xrpc<P, I, O, E>(&self, request: &XrpcRequest<P, I>) -> XrpcResult<O, E>
68-
where
69-
P: Serialize + Send + Sync,
70-
I: Serialize + Send + Sync,
71-
O: DeserializeOwned + Send + Sync,
72-
E: DeserializeOwned + Send + Sync,
73-
{
74-
let mut uri = format!("{}/xrpc/{}", self.base_uri(), request.path);
75-
if let Some(p) = &request.parameters {
76-
serde_html_form::to_string(p).map(|qs| {
77-
uri += "?";
78-
uri += &qs;
79-
})?;
80-
};
81-
let mut builder = Request::builder().method(&request.method).uri(&uri);
82-
if let Some(encoding) = &request.encoding {
83-
builder = builder.header(http::header::CONTENT_TYPE, encoding);
84-
}
85-
if let Some(token) = self
86-
.auth(
87-
request.method == Method::POST
88-
&& request.path == "com.atproto.server.refreshSession",
89-
)
90-
.await
91-
{
92-
builder = builder.header(http::header::AUTHORIZATION, format!("Bearer {}", token));
93-
}
94-
let body = if let Some(input) = &request.input {
95-
match input {
96-
InputDataOrBytes::Data(data) => serde_json::to_vec(&data)?,
97-
InputDataOrBytes::Bytes(bytes) => bytes.clone(),
98-
}
99-
} else {
100-
Vec::new()
101-
};
102-
let (parts, body) = self
103-
.send_http(builder.body(body)?)
104-
.await
105-
.map_err(Error::HttpClient)?
106-
.into_parts();
107-
if parts.status.is_success() {
108-
if parts
109-
.headers
110-
.get(http::header::CONTENT_TYPE)
111-
.and_then(|value| value.to_str().ok())
112-
.map_or(false, |content_type| {
113-
content_type.starts_with("application/json")
114-
})
115-
{
116-
Ok(OutputDataOrBytes::Data(serde_json::from_slice(&body)?))
117-
} else {
118-
Ok(OutputDataOrBytes::Bytes(body))
119-
}
120-
} else {
121-
Err(Error::XrpcResponse(XrpcError {
122-
status: parts.status,
123-
error: serde_json::from_slice::<XrpcErrorKind<E>>(&body).ok(),
124-
}))
125-
}
126-
}
127-
}
6+
pub use crate::error::{Error, Result};
7+
pub use crate::traits::{HttpClient, XrpcClient};
8+
pub use crate::types::{InputDataOrBytes, OutputDataOrBytes, XrpcRequest};
1289

12910
#[cfg(test)]
13011
mod tests {
13112
use super::*;
13+
use crate::error::{XrpcError, XrpcErrorKind};
14+
use crate::{HttpClient, XrpcClient};
15+
use async_trait::async_trait;
16+
use http::{Request, Response};
13217
#[cfg(target_arch = "wasm32")]
13318
use wasm_bindgen_test::*;
13419

@@ -144,7 +29,10 @@ mod tests {
14429
async fn send_http(
14530
&self,
14631
_request: Request<Vec<u8>>,
147-
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
32+
) -> core::result::Result<
33+
Response<Vec<u8>>,
34+
Box<dyn std::error::Error + Send + Sync + 'static>,
35+
> {
14836
let mut builder = Response::builder().status(self.status);
14937
if self.json {
15038
builder = builder.header(http::header::CONTENT_TYPE, "application/json")
@@ -162,7 +50,7 @@ mod tests {
16250
mod errors {
16351
use super::*;
16452

165-
async fn get_example<T>(xrpc: &T, params: Parameters) -> Result<Output, crate::Error<Error>>
53+
async fn get_example<T>(xrpc: &T, params: Parameters) -> Result<Output, Error>
16654
where
16755
T: crate::XrpcClient + Send + Sync,
16856
{
@@ -305,10 +193,7 @@ mod tests {
305193
mod bytes {
306194
use super::*;
307195

308-
async fn get_bytes<T>(
309-
xrpc: &T,
310-
params: Parameters,
311-
) -> Result<Vec<u8>, crate::Error<Error>>
196+
async fn get_bytes<T>(xrpc: &T, params: Parameters) -> Result<Vec<u8>, Error>
312197
where
313198
T: crate::XrpcClient + Send + Sync,
314199
{
@@ -387,7 +272,7 @@ mod tests {
387272
mod no_content {
388273
use super::*;
389274

390-
async fn create_data<T>(xrpc: &T, input: Input) -> Result<(), crate::Error<Error>>
275+
async fn create_data<T>(xrpc: &T, input: Input) -> Result<(), Error>
391276
where
392277
T: crate::XrpcClient + Send + Sync,
393278
{
@@ -449,7 +334,7 @@ mod tests {
449334
mod bytes {
450335
use super::*;
451336

452-
async fn create_data<T>(xrpc: &T, input: Vec<u8>) -> Result<Output, crate::Error<Error>>
337+
async fn create_data<T>(xrpc: &T, input: Vec<u8>) -> Result<Output, Error>
453338
where
454339
T: crate::XrpcClient + Send + Sync,
455340
{

0 commit comments

Comments
 (0)