use std::collections::HashMap;
use anyhow::Result;
use bytes::Bytes;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use crate::{
client::{DPoPAuth, get_bytes, get_dpop_json, get_json, post_dpop_json},
errors::SimpleError,
url::URLBuilder,
};
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum GetRecordResponse {
Record {
uri: String,
cid: String,
value: serde_json::Value,
#[serde(flatten)]
extra: HashMap<String, serde_json::Value>,
},
Error(SimpleError),
}
pub async fn get_blob(
http_client: &reqwest::Client,
base_url: &str,
did: &str,
cid: &str,
) -> Result<Bytes> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.sync.getBlob");
url_builder.param("did", did);
url_builder.param("cid", cid);
let url = url_builder.build();
get_bytes(http_client, &url).await
}
pub async fn get_record(
http_client: &reqwest::Client,
dpop_auth: Option<&DPoPAuth>,
base_url: &str,
repo: &str,
collection: &str,
rkey: &str,
cid: Option<&str>,
) -> Result<GetRecordResponse> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.repo.getRecord");
url_builder.param("repo", repo);
url_builder.param("collection", collection);
url_builder.param("rkey", rkey);
if let Some(cid) = cid {
url_builder.param("cid", cid);
}
let url = url_builder.build();
if let Some(dpop_auth) = dpop_auth {
get_dpop_json(http_client, dpop_auth, &url)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
} else {
get_json(http_client, &url)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
pub struct ListRecord<T> {
pub uri: String,
pub cid: String,
pub value: T,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
pub struct ListRecordsResponse<T> {
pub cursor: Option<String>,
pub records: Vec<ListRecord<T>>,
}
#[derive(Default)]
pub struct ListRecordsParams {
pub limit: Option<u32>,
pub cursor: Option<String>,
pub reverse: Option<bool>,
}
impl ListRecordsParams {
pub fn new() -> Self {
Self::default()
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn cursor(mut self, cursor: String) -> Self {
self.cursor = Some(cursor);
self
}
pub fn reverse(mut self, reverse: bool) -> Self {
self.reverse = Some(reverse);
self
}
}
pub async fn list_records<T: DeserializeOwned>(
http_client: &reqwest::Client,
dpop_auth: Option<&DPoPAuth>,
base_url: &str,
repo: String,
collection: String,
params: ListRecordsParams,
) -> Result<ListRecordsResponse<T>> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.repo.listRecords");
url_builder.param("repo", &repo);
url_builder.param("collection", &collection);
if let Some(limit) = params.limit {
url_builder.param("limit", &limit.to_string());
}
if let Some(cursor) = params.cursor {
url_builder.param("cursor", &cursor);
}
if let Some(reverse) = params.reverse {
url_builder.param("reverse", &reverse.to_string());
}
let url = url_builder.build();
if let Some(dpop_auth) = dpop_auth {
get_dpop_json(http_client, dpop_auth, &url)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
} else {
get_json(http_client, &url)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Serialize, Deserialize, Clone)]
#[serde(bound = "T: Serialize + DeserializeOwned")]
pub struct CreateRecordRequest<T: DeserializeOwned> {
pub repo: String,
pub collection: String,
#[serde(skip_serializing_if = "Option::is_none", default, rename = "rkey")]
pub record_key: Option<String>,
pub validate: bool,
pub record: T,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "swapCommit"
)]
pub swap_commit: Option<String>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum CreateRecordResponse {
StrongRef {
uri: String,
cid: String,
#[serde(flatten)]
extra: HashMap<String, serde_json::Value>,
},
Error(SimpleError),
}
pub async fn create_record<T: DeserializeOwned + Serialize>(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
base_url: &str,
record: CreateRecordRequest<T>,
) -> Result<CreateRecordResponse> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.repo.createRecord");
let url = url_builder.build();
let value = serde_json::to_value(record)?;
post_dpop_json(http_client, dpop_auth, &url, value)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Serialize, Deserialize, Clone)]
#[serde(bound = "T: Serialize + DeserializeOwned")]
pub struct PutRecordRequest<T: DeserializeOwned> {
pub repo: String,
pub collection: String,
#[serde(rename = "rkey")]
pub record_key: String,
pub validate: bool,
pub record: T,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "swapCommit"
)]
pub swap_commit: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "swapRecord"
)]
pub swap_record: Option<String>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum PutRecordResponse {
StrongRef {
uri: String,
cid: String,
#[serde(flatten)]
extra: HashMap<String, serde_json::Value>,
},
Error(SimpleError),
}
pub async fn put_record<T: DeserializeOwned + Serialize>(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
base_url: &str,
record: PutRecordRequest<T>,
) -> Result<PutRecordResponse> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.repo.putRecord");
let url = url_builder.build();
let value = serde_json::to_value(record)?;
post_dpop_json(http_client, dpop_auth, &url, value)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Serialize, Deserialize, Clone)]
pub struct DeleteRecordRequest {
pub repo: String,
pub collection: String,
#[serde(rename = "rkey")]
pub record_key: String,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "swapCommit"
)]
pub swap_commit: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "swapRecord"
)]
pub swap_record: Option<String>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum DeleteRecordResponse {
Commit {
#[serde(flatten)]
commit: HashMap<String, serde_json::Value>,
},
Error(SimpleError),
}
pub async fn delete_record(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
base_url: &str,
record: DeleteRecordRequest,
) -> Result<DeleteRecordResponse> {
let mut url_builder = URLBuilder::new(base_url);
url_builder.path("/xrpc/com.atproto.repo.deleteRecord");
let url = url_builder.build();
let value = serde_json::to_value(record)?;
post_dpop_json(http_client, dpop_auth, &url, value)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}