1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use alloc::string::String;
use smol_str::SmolStr;
use serde::{Deserialize, Serialize};
use crate::types::value::Data;
use crate::types::{
aturi::RepoPath,
nsid::Nsid,
recordkey::{RecordKey, RecordKeyType},
};
use crate::xrpc::XrpcResp;
use crate::{BorrowOrShare, Bos, IntoStatic};
/// Trait for a collection of records that can be stored in a repository.
///
/// The records all have the same Lexicon schema.
///
/// Implemented on the record type itself.
pub trait Collection {
/// The NSID for the Lexicon that defines the schema of records in this collection.
const NSID: &'static str;
/// A marker type implementing [`XrpcResp`] that allows typed deserialization of records
/// from this collection. Used by [`AgentSessionExt::get_record`](https://docs.rs/jacquard/latest/jacquard/client/trait.AgentSessionExt.html) to return properly typed responses.
type Record: XrpcResp;
/// Returns the [`Nsid`] for the Lexicon that defines the schema of records in this
/// collection.
///
/// This is a convenience method that parses [`Self::NSID`].
///
/// # Panics
///
/// Panics if [`Self::NSID`] is not a valid NSID.
///
/// [`Nsid`]: crate::types::string::Nsid
fn nsid() -> crate::types::nsid::Nsid<&'static str> {
unsafe { Nsid::unchecked(Self::NSID) }
}
/// Returns the repo path for a record in this collection with the given record key.
///
/// Per the [Repo Data Structure v3] specification:
/// > Repo paths currently have a fixed structure of `<collection>/<record-key>`. This
/// > means a valid, normalized [`Nsid`], followed by a `/`, followed by a valid
/// > [`RecordKey`].
///
/// [Repo Data Structure v3]: https://atproto.com/specs/repository#repo-data-structure-v3
/// [`Nsid`]: crate::types::string::Nsid
fn repo_path<T>(rkey: &crate::types::recordkey::RecordKey<T>) -> RepoPath<&str>
where
T: RecordKeyType + Bos<str> + AsRef<str>,
{
RepoPath {
collection: Self::nsid(),
// Borrow the record key string with the caller's lifetime via CowStr.
rkey: Some(
RecordKey::any(rkey.0.borrow_or_share())
.expect("RecordKey implements RecordKeyType, which guarantees a valid rkey"),
),
}
}
}
/// Generic error type for record retrieval operations.
///
/// Used by generated collection marker types as their error type.
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error, miette::Diagnostic,
)]
#[serde(tag = "error", content = "message")]
#[non_exhaustive]
pub enum RecordError {
/// The requested record was not found
#[error("RecordNotFound")]
#[serde(rename = "RecordNotFound")]
RecordNotFound(Option<String>),
/// An unknown error occurred
#[error("Unknown")]
#[serde(rename = "Unknown")]
Unknown(Data<SmolStr>),
}
impl IntoStatic for RecordError {
type Output = RecordError;
fn into_static(self) -> Self::Output {
match self {
RecordError::RecordNotFound(msg) => RecordError::RecordNotFound(msg),
RecordError::Unknown(data) => RecordError::Unknown(data.into_static()),
}
}
}