use crate::CowStr;
use crate::IntoStatic;
use crate::types::ident::AtIdentifier;
use crate::types::string::{AtUri, Cid, Did, Handle, Nsid};
use crate::types::value::Data;
use crate::xrpc::{GenericError, XrpcMethod, XrpcRequest, XrpcResp};
use alloc::vec::Vec;
use core::error::Error;
use core::fmt::{self, Display};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ListRecords<'a> {
#[serde(borrow)]
pub collection: Nsid<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(borrow)]
pub cursor: Option<CowStr<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i64>,
#[serde(borrow)]
pub repo: AtIdentifier<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
}
impl IntoStatic for ListRecords<'_> {
type Output = ListRecords<'static>;
fn into_static(self) -> Self::Output {
ListRecords {
collection: self.collection.into_static(),
cursor: self.cursor.into_static(),
limit: self.limit,
repo: self.repo.into_static(),
reverse: self.reverse,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ListRecordsOutput<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(borrow)]
pub cursor: Option<CowStr<'a>>,
#[serde(borrow)]
pub records: Vec<ListRecordsRecord<'a>>,
}
impl IntoStatic for ListRecordsOutput<'_> {
type Output = ListRecordsOutput<'static>;
fn into_static(self) -> Self::Output {
ListRecordsOutput {
cursor: self.cursor.into_static(),
records: self.records.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ListRecordsRecord<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(borrow)]
pub cid: Option<Cid<'a>>,
#[serde(borrow)]
pub uri: AtUri<'a>,
#[serde(borrow)]
pub value: Data<'a>,
}
impl IntoStatic for ListRecordsRecord<'_> {
type Output = ListRecordsRecord<'static>;
fn into_static(self) -> Self::Output {
ListRecordsRecord {
cid: self.cid.into_static(),
uri: self.uri.into_static(),
value: self.value.into_static(),
}
}
}
pub struct ListRecordsResponse;
impl XrpcResp for ListRecordsResponse {
const NSID: &'static str = "com.atproto.repo.listRecords";
const ENCODING: &'static str = "application/json";
type Output<'de> = ListRecordsOutput<'de>;
type Err<'de> = GenericError<'de>;
}
impl<'a> XrpcRequest for ListRecords<'a> {
const NSID: &'static str = "com.atproto.repo.listRecords";
const METHOD: XrpcMethod = XrpcMethod::Query;
type Response = ListRecordsResponse;
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
#[serde(rename_all = "camelCase")]
pub struct GetRecord<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(borrow)]
pub cid: Option<Cid<'a>>,
#[serde(borrow)]
pub collection: Nsid<'a>,
#[serde(borrow)]
pub repo: AtIdentifier<'a>,
#[serde(borrow)]
pub rkey: CowStr<'a>,
}
impl IntoStatic for GetRecord<'_> {
type Output = GetRecord<'static>;
fn into_static(self) -> Self::Output {
GetRecord {
cid: self.cid.into_static(),
collection: self.collection.into_static(),
repo: self.repo.into_static(),
rkey: self.rkey.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
#[serde(rename_all = "camelCase")]
pub struct GetRecordOutput<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(borrow)]
pub cid: Option<Cid<'a>>,
#[serde(borrow)]
pub uri: AtUri<'a>,
#[serde(borrow)]
pub value: Data<'a>,
}
impl IntoStatic for GetRecordOutput<'_> {
type Output = GetRecordOutput<'static>;
fn into_static(self) -> Self::Output {
GetRecordOutput {
cid: self.cid.into_static(),
uri: self.uri.into_static(),
value: self.value.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
#[serde(tag = "error", content = "message")]
#[serde(bound(deserialize = "'de: 'a"))]
pub enum GetRecordError<'a> {
#[serde(rename = "RecordNotFound")]
RecordNotFound(Option<CowStr<'a>>),
}
impl<'a> Display for GetRecordError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecordNotFound(msg) => {
write!(f, "RecordNotFound")?;
if let Some(msg) = msg {
write!(f, ": {}", msg)?;
}
Ok(())
}
}
}
}
impl Error for GetRecordError<'_> {}
impl IntoStatic for GetRecordError<'_> {
type Output = GetRecordError<'static>;
fn into_static(self) -> Self::Output {
match self {
Self::RecordNotFound(msg) => GetRecordError::RecordNotFound(msg.into_static()),
}
}
}
pub struct GetRecordResponse;
impl XrpcResp for GetRecordResponse {
const NSID: &'static str = "com.atproto.repo.getRecord";
const ENCODING: &'static str = "application/json";
type Output<'de> = GetRecordOutput<'de>;
type Err<'de> = GetRecordError<'de>;
}
impl<'a> XrpcRequest for GetRecord<'a> {
const NSID: &'static str = "com.atproto.repo.getRecord";
const METHOD: XrpcMethod = XrpcMethod::Query;
type Response = GetRecordResponse;
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ResolveHandle<'a> {
#[serde(borrow)]
pub handle: Handle<'a>,
}
impl IntoStatic for ResolveHandle<'_> {
type Output = ResolveHandle<'static>;
fn into_static(self) -> Self::Output {
ResolveHandle {
handle: self.handle.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ResolveHandleOutput<'a> {
#[serde(borrow)]
pub did: Did<'a>,
}
impl IntoStatic for ResolveHandleOutput<'_> {
type Output = ResolveHandleOutput<'static>;
fn into_static(self) -> Self::Output {
ResolveHandleOutput {
did: self.did.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "error", content = "message")]
#[allow(missing_docs)]
#[serde(bound(deserialize = "'de: 'a"))]
pub enum ResolveHandleError<'a> {
#[serde(rename = "HandleNotFound")]
HandleNotFound(Option<CowStr<'a>>),
}
impl<'a> Display for ResolveHandleError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HandleNotFound(msg) => {
write!(f, "HandleNotFound")?;
if let Some(msg) = msg {
write!(f, ": {}", msg)?;
}
Ok(())
}
}
}
}
impl Error for ResolveHandleError<'_> {}
impl IntoStatic for ResolveHandleError<'_> {
type Output = ResolveHandleError<'static>;
fn into_static(self) -> Self::Output {
match self {
Self::HandleNotFound(msg) => ResolveHandleError::HandleNotFound(msg.into_static()),
}
}
}
pub struct ResolveHandleResponse;
impl XrpcResp for ResolveHandleResponse {
const NSID: &'static str = "com.atproto.identity.resolveHandle";
const ENCODING: &'static str = "application/json";
type Output<'de> = ResolveHandleOutput<'de>;
type Err<'de> = ResolveHandleError<'de>;
}
impl<'a> XrpcRequest for ResolveHandle<'a> {
const NSID: &'static str = "com.atproto.identity.resolveHandle";
const METHOD: XrpcMethod = XrpcMethod::Query;
type Response = ResolveHandleResponse;
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ResolveDid<'a> {
#[serde(borrow)]
pub did: Did<'a>,
}
impl IntoStatic for ResolveDid<'_> {
type Output = ResolveDid<'static>;
fn into_static(self) -> Self::Output {
ResolveDid {
did: self.did.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct ResolveDidOutput<'a> {
#[serde(borrow)]
pub did_doc: Data<'a>,
}
impl IntoStatic for ResolveDidOutput<'_> {
type Output = ResolveDidOutput<'static>;
fn into_static(self) -> Self::Output {
ResolveDidOutput {
did_doc: self.did_doc.into_static(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "error", content = "message")]
#[serde(bound(deserialize = "'de: 'a"))]
#[allow(missing_docs)]
pub enum ResolveDidError<'a> {
#[serde(rename = "DidNotFound")]
DidNotFound(Option<CowStr<'a>>),
#[serde(rename = "DidDeactivated")]
DidDeactivated(Option<CowStr<'a>>),
}
impl<'a> Display for ResolveDidError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DidNotFound(msg) => {
write!(f, "DidNotFound")?;
if let Some(msg) = msg {
write!(f, ": {}", msg)?;
}
Ok(())
}
Self::DidDeactivated(msg) => {
write!(f, "DidDeactivated")?;
if let Some(msg) = msg {
write!(f, ": {}", msg)?;
}
Ok(())
}
}
}
}
impl Error for ResolveDidError<'_> {}
impl IntoStatic for ResolveDidError<'_> {
type Output = ResolveDidError<'static>;
fn into_static(self) -> Self::Output {
match self {
Self::DidNotFound(msg) => ResolveDidError::DidNotFound(msg.into_static()),
Self::DidDeactivated(msg) => ResolveDidError::DidDeactivated(msg.into_static()),
}
}
}
pub struct ResolveDidResponse;
impl XrpcResp for ResolveDidResponse {
const NSID: &'static str = "com.atproto.identity.resolveDid";
const ENCODING: &'static str = "application/json";
type Output<'de> = ResolveDidOutput<'de>;
type Err<'de> = ResolveDidError<'de>;
}
impl<'a> XrpcRequest for ResolveDid<'a> {
const NSID: &'static str = "com.atproto.identity.resolveDid";
const METHOD: XrpcMethod = XrpcMethod::Query;
type Response = ResolveDidResponse;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::IntoStatic;
#[test]
fn test_list_records_serializes() {
let req = ListRecords {
repo: AtIdentifier::new("test.bsky.social")
.unwrap()
.into_static()
.into(),
collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(),
cursor: None,
limit: Some(50),
reverse: None,
};
let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["repo"], "test.bsky.social");
assert_eq!(json["collection"], "app.bsky.feed.post");
assert_eq!(json["limit"], 50);
assert!(!json.as_object().unwrap().contains_key("cursor"));
}
#[test]
fn test_list_records_output_deserializes() {
let json_str = r#"{
"records": [
{
"uri": "at://did:plc:test/app.bsky.feed.post/123",
"cid": "bafy123",
"value": {}
}
],
"cursor": "page2"
}"#;
let output: ListRecordsOutput = serde_json::from_str(json_str).unwrap();
assert_eq!(output.records.len(), 1);
assert!(output.cursor.is_some());
assert_eq!(output.cursor.as_ref().unwrap().as_str(), "page2");
}
#[test]
fn test_get_record_output_deserializes() {
let json_str = r#"{
"uri": "at://did:plc:test/app.bsky.feed.post/123",
"cid": "bafy123",
"value": {}
}"#;
let output: GetRecordOutput = serde_json::from_str(json_str).unwrap();
assert!(output.cid.is_some());
assert_eq!(output.cid.as_ref().unwrap().as_str(), "bafy123");
}
#[test]
fn test_get_record_error_deserializes() {
let json_str = r#"{"error": "RecordNotFound", "message": "not found"}"#;
let error: GetRecordError = serde_json::from_str(json_str).unwrap();
assert!(matches!(error, GetRecordError::RecordNotFound(Some(_))));
}
#[test]
fn test_resolve_handle_output_deserializes() {
let json_str = r#"{"did": "did:plc:abc123"}"#;
let output: ResolveHandleOutput = serde_json::from_str(json_str).unwrap();
assert_eq!(output.did.as_str(), "did:plc:abc123");
}
#[test]
fn test_resolve_handle_error_deserializes() {
let json_str = r#"{"error": "HandleNotFound", "message": "handle not found"}"#;
let error: ResolveHandleError = serde_json::from_str(json_str).unwrap();
assert!(matches!(error, ResolveHandleError::HandleNotFound(Some(_))));
}
#[test]
fn test_resolve_did_output_deserializes() {
let json_str = r#"{"didDoc": {}}"#;
let output: ResolveDidOutput = serde_json::from_str(json_str).unwrap();
let _ = output;
}
#[test]
fn test_resolve_did_error_deserializes_not_found() {
let json_str = r#"{"error": "DidNotFound", "message": "did not found"}"#;
let error: ResolveDidError = serde_json::from_str(json_str).unwrap();
assert!(matches!(error, ResolveDidError::DidNotFound(Some(_))));
}
#[test]
fn test_resolve_did_error_deserializes_deactivated() {
let json_str = r#"{"error": "DidDeactivated", "message": "did is deactivated"}"#;
let error: ResolveDidError = serde_json::from_str(json_str).unwrap();
assert!(matches!(error, ResolveDidError::DidDeactivated(Some(_))));
}
#[test]
fn test_types_implement_into_static() {
let list_records = ListRecords {
repo: AtIdentifier::new("test.bsky.social")
.unwrap()
.into_static()
.into(),
collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(),
cursor: None,
limit: Some(50),
reverse: None,
};
let _static = list_records.into_static();
let get_record = GetRecord {
repo: AtIdentifier::new("test.bsky.social")
.unwrap()
.into_static()
.into(),
collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(),
rkey: CowStr::from("abc123").into_static(),
cid: None,
};
let _static = get_record.into_static();
let resolve_handle = ResolveHandle {
handle: Handle::new("test.bsky.social").unwrap().into_static(),
};
let _static = resolve_handle.into_static();
let resolve_did = ResolveDid {
did: Did::new("did:plc:abc123").unwrap().into_static(),
};
let _static = resolve_did.into_static();
}
}