at main 5.4 kB view raw
1use serde::{Deserialize, Serialize}; 2use serde_json::value::RawValue; 3 4#[derive(Debug, Hash, Deserialize, Serialize)] 5#[serde(rename_all = "lowercase")] 6pub enum RecordAction { 7 Create, 8 Update, 9 Delete, 10} 11 12#[derive(Debug, Deserialize, Serialize)] 13struct InnerRecordEvent { 14 did: String, 15 rev: String, 16 collection: String, 17 rkey: String, 18 action: RecordAction, 19 record: Option<Box<RawValue>>, 20 cid: Option<String>, 21 live: bool, 22} 23 24#[derive(Debug, Deserialize, Serialize)] 25pub struct RecordEvent { 26 pub id: u64, 27 pub did: String, 28 pub rev: String, 29 pub collection: String, 30 pub rkey: String, 31 pub action: RecordAction, 32 pub record: Option<Box<RawValue>>, 33 pub cid: Option<String>, 34 pub live: bool, 35} 36 37#[derive(Debug, Deserialize, Serialize, sqlx::Type)] 38#[serde(rename_all = "lowercase")] 39#[sqlx(type_name = "identity_status", rename_all = "lowercase")] 40pub enum IdentityStatus { 41 Active, 42 Takendown, 43 Suspended, 44 Deactivated, 45 Deleted, 46} 47 48#[derive(Debug, Deserialize, Serialize)] 49struct InnerIdentityEvent { 50 did: String, 51 handle: String, 52 is_active: bool, 53 status: IdentityStatus, 54} 55 56#[derive(Debug, Deserialize, Serialize)] 57pub struct IdentityEvent { 58 pub id: u64, 59 pub did: String, 60 pub handle: String, 61 pub is_active: bool, 62 pub status: IdentityStatus, 63} 64 65#[derive(Debug, Deserialize, Serialize)] 66#[serde(rename_all = "snake_case")] 67pub enum EventType { 68 Record, 69 Identity, 70} 71 72#[derive(Debug, Deserialize, Serialize)] 73struct InnerEvent { 74 id: u64, 75 r#type: EventType, 76 #[serde(skip_serializing_if = "Option::is_none")] 77 record: Option<InnerRecordEvent>, 78 #[serde(skip_serializing_if = "Option::is_none")] 79 identity: Option<InnerIdentityEvent>, 80} 81 82#[derive(Debug, Deserialize, Serialize)] 83#[serde(try_from = "InnerEvent")] 84pub enum TapEvent { 85 Record(RecordEvent), 86 Identity(IdentityEvent), 87} 88 89impl TapEvent { 90 pub const fn id(&self) -> u64 { 91 match self { 92 Self::Record(record) => record.id, 93 Self::Identity(identity) => identity.id, 94 } 95 } 96} 97 98#[derive(Debug, thiserror::Error)] 99enum EventError { 100 #[error("event with 'record' type should have 'record' property set")] 101 RecordEventMissingRecord, 102 #[error("event with 'record' type should not have 'identity' property set")] 103 RecordEventWithIdentity, 104 #[error("event with 'identity' type should have 'identity' property set")] 105 IdentityEventMissingIdentity, 106 #[error("event with 'identity' type should not have 'record' property set")] 107 IdentityEventWithRecord, 108} 109 110impl TryFrom<InnerEvent> for TapEvent { 111 type Error = EventError; 112 113 fn try_from(value: InnerEvent) -> Result<Self, Self::Error> { 114 match (value.r#type, value.record, value.identity) { 115 (EventType::Record, None, _) => Err(EventError::RecordEventMissingRecord), 116 (EventType::Record, _, Some(_)) => Err(EventError::RecordEventWithIdentity), 117 (EventType::Record, Some(record), None) => { 118 let InnerRecordEvent { 119 did, 120 rev, 121 collection, 122 rkey, 123 action, 124 record, 125 cid, 126 live, 127 } = record; 128 Ok(Self::Record(RecordEvent { 129 id: value.id, 130 did, 131 rev, 132 collection, 133 rkey, 134 action, 135 record, 136 cid, 137 live, 138 })) 139 } 140 (EventType::Identity, _, None) => Err(EventError::IdentityEventMissingIdentity), 141 (EventType::Identity, Some(_), _) => Err(EventError::IdentityEventWithRecord), 142 (EventType::Identity, None, Some(identity)) => { 143 let InnerIdentityEvent { 144 did, 145 handle, 146 is_active, 147 status, 148 } = identity; 149 Ok(Self::Identity(IdentityEvent { 150 id: value.id, 151 did, 152 handle, 153 is_active, 154 status, 155 })) 156 } 157 } 158 } 159} 160 161#[derive(Debug, Deserialize, Serialize)] 162pub struct RepoInfo { 163 pub did: String, 164 pub handle: String, 165 pub state: String, 166 pub rev: String, 167 pub records: u64, 168 pub error: Option<String>, 169 pub retries: Option<u64>, 170} 171 172#[cfg(test)] 173mod tests { 174 use super::*; 175 176 #[test] 177 fn deserialized_record_event() { 178 const SAMPLE: &str = r#"{"id":24431,"type":"record","record":{"live":false,"did":"did:plc:jlplwn5pi4dqrls7i6dx2me7","rev":"3m7xjcvqmch2k","collection":"sh.tangled.repo.issue.comment","rkey":"3lxy6urnngt22","action":"create","record":{"$type":"sh.tangled.repo.issue.comment","body":"x","commentId":66128,"createdAt":"2025-09-04T03:20:53Z","issue":"at://did:plc:qfpnj4og54vl56wngdriaxug/sh.tangled.repo.issue/3ljnffq4axj22","owner":"did:plc:jlplwn5pi4dqrls7i6dx2me7","repo":"at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.repo/3liuighjy2h22"},"cid":"bafyreielwpdea6mnhgqz2r4yfyr5ne6v6h2ucncm46enukpqlclrned5ia"}}"#; 179 let event: TapEvent = serde_json::from_str(SAMPLE).unwrap(); 180 assert!(matches!(event, TapEvent::Record(_))); 181 } 182}