Undisclosed project number 1234
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 5.7 kB View raw
1use jacquard_common::types::collection::Collection; 2use jacquard_lexicon::schema::LexiconSchema; 3use serde::{Deserialize, Serialize}; 4use serde_json::Value as Json; 5use superjam_core::{AtUri, Cid, Did, Nsid, Rkey}; 6 7use crate::client::Pds; 8use crate::error::{Error, Result}; 9use crate::newtype::WriteOpCount; 10 11pub const APPLY_WRITES_MAX: WriteOpCount = WriteOpCount::new(200); 12 13#[derive(Debug, Clone, PartialEq, Eq)] 14pub struct RecordRef { 15 pub uri: AtUri, 16 pub cid: Cid, 17} 18 19#[derive(Debug, Clone, PartialEq, Eq)] 20pub enum WriteResult { 21 Created(RecordRef), 22 Updated(RecordRef), 23 Deleted, 24} 25 26#[derive(Debug, Clone, Serialize)] 27#[serde(transparent)] 28pub struct Write(WriteInner); 29 30#[derive(Debug, Clone, Serialize)] 31#[serde(tag = "$type")] 32enum WriteInner { 33 #[serde(rename = "com.atproto.repo.applyWrites#create")] 34 Create { 35 collection: Nsid, 36 #[serde(skip_serializing_if = "Option::is_none")] 37 rkey: Option<Rkey>, 38 value: Json, 39 }, 40 #[serde(rename = "com.atproto.repo.applyWrites#update")] 41 Update { 42 collection: Nsid, 43 rkey: Rkey, 44 value: Json, 45 }, 46 #[serde(rename = "com.atproto.repo.applyWrites#delete")] 47 Delete { collection: Nsid, rkey: Rkey }, 48} 49 50impl Write { 51 pub fn create<R>(rkey: Option<Rkey>, record: &R) -> Result<Self> 52 where 53 R: Collection + Serialize + LexiconSchema, 54 { 55 record 56 .validate() 57 .map_err(|e| Error::LexiconValidation(e.to_string()))?; 58 Ok(Self(WriteInner::Create { 59 collection: collection_of::<R>(), 60 rkey, 61 value: serde_json::to_value(record)?, 62 })) 63 } 64 65 pub fn update<R>(rkey: Rkey, record: &R) -> Result<Self> 66 where 67 R: Collection + Serialize + LexiconSchema, 68 { 69 record 70 .validate() 71 .map_err(|e| Error::LexiconValidation(e.to_string()))?; 72 Ok(Self(WriteInner::Update { 73 collection: collection_of::<R>(), 74 rkey, 75 value: serde_json::to_value(record)?, 76 })) 77 } 78 79 pub fn delete<R: Collection>(rkey: Rkey) -> Self { 80 Self(WriteInner::Delete { 81 collection: collection_of::<R>(), 82 rkey, 83 }) 84 } 85} 86 87fn collection_of<R: Collection>() -> Nsid { 88 Nsid::new_static(R::NSID).expect("codegen-generated NSID is well-formed") 89} 90 91impl<S> Pds<S> 92where 93 S: jacquard_common::session::SessionStore<Did, superjam_oauth::Session> 94 + Clone 95 + Send 96 + Sync 97 + 'static, 98{ 99 pub async fn create_record<R>(&mut self, rkey: Option<&Rkey>, record: &R) -> Result<RecordRef> 100 where 101 R: Collection + Serialize + LexiconSchema, 102 { 103 self.validate_record(record)?; 104 let nsid = 105 Nsid::new_static("com.atproto.repo.createRecord").expect("static NSID well-formed"); 106 let url = self.xrpc_url(&nsid)?; 107 let body = RecordRequest { 108 repo: self.did().clone(), 109 collection: collection_of::<R>(), 110 rkey, 111 record, 112 validate: None, 113 }; 114 let resp: RecordResponse = self.post_json(url, &body).await?; 115 Ok(RecordRef { 116 uri: resp.uri, 117 cid: resp.cid, 118 }) 119 } 120 121 pub async fn apply_writes(&mut self, writes: Vec<Write>) -> Result<Vec<WriteResult>> { 122 let count = WriteOpCount::new(writes.len()); 123 if writes.is_empty() { 124 return Err(Error::ApplyWritesEmpty); 125 } 126 if count > APPLY_WRITES_MAX { 127 return Err(Error::ApplyWritesLimit { 128 got: count, 129 max: APPLY_WRITES_MAX, 130 }); 131 } 132 let nsid = 133 Nsid::new_static("com.atproto.repo.applyWrites").expect("static NSID well-formed"); 134 let url = self.xrpc_url(&nsid)?; 135 let body = ApplyWritesRequest { 136 repo: self.did().clone(), 137 validate: None, 138 writes: &writes, 139 }; 140 let resp: ApplyWritesResponse = self.post_json(url, &body).await?; 141 Ok(resp.results.into_iter().map(WriteResult::from).collect()) 142 } 143} 144 145impl From<ApplyWritesRawResult> for WriteResult { 146 fn from(r: ApplyWritesRawResult) -> Self { 147 match r { 148 ApplyWritesRawResult::Create { uri, cid } => { 149 WriteResult::Created(RecordRef { uri, cid }) 150 } 151 ApplyWritesRawResult::Update { uri, cid } => { 152 WriteResult::Updated(RecordRef { uri, cid }) 153 } 154 ApplyWritesRawResult::Delete {} => WriteResult::Deleted, 155 } 156 } 157} 158 159#[derive(Serialize)] 160#[serde(rename_all = "camelCase")] 161struct RecordRequest<'a, R: Serialize + ?Sized> { 162 repo: Did, 163 collection: Nsid, 164 #[serde(skip_serializing_if = "Option::is_none")] 165 rkey: Option<&'a Rkey>, 166 record: &'a R, 167 #[serde(skip_serializing_if = "Option::is_none")] 168 validate: Option<bool>, 169} 170 171#[derive(Serialize)] 172#[serde(rename_all = "camelCase")] 173struct ApplyWritesRequest<'a> { 174 repo: Did, 175 #[serde(skip_serializing_if = "Option::is_none")] 176 validate: Option<bool>, 177 writes: &'a [Write], 178} 179 180#[derive(Deserialize)] 181#[serde(rename_all = "camelCase")] 182struct RecordResponse { 183 uri: AtUri, 184 cid: Cid, 185} 186 187#[derive(Deserialize)] 188struct ApplyWritesResponse { 189 #[serde(default)] 190 results: Vec<ApplyWritesRawResult>, 191} 192 193#[derive(Deserialize)] 194#[serde(tag = "$type")] 195enum ApplyWritesRawResult { 196 #[serde(rename = "com.atproto.repo.applyWrites#createResult")] 197 Create { uri: AtUri, cid: Cid }, 198 #[serde(rename = "com.atproto.repo.applyWrites#updateResult")] 199 Update { uri: AtUri, cid: Cid }, 200 #[serde(rename = "com.atproto.repo.applyWrites#deleteResult")] 201 Delete {}, 202}