model.rs (11670B)
1 #![forbid(unsafe_code)] 2 3 use crate::RadrootsOutboxError; 4 use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent}; 5 6 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 7 pub enum RadrootsOutboxOperationStatus { 8 Queued, 9 Complete, 10 FailedTerminal, 11 Cancelled, 12 } 13 14 impl RadrootsOutboxOperationStatus { 15 pub fn as_str(self) -> &'static str { 16 match self { 17 Self::Queued => "queued", 18 Self::Complete => "complete", 19 Self::FailedTerminal => "failed_terminal", 20 Self::Cancelled => "cancelled", 21 } 22 } 23 24 pub fn parse(value: &str) -> Result<Self, RadrootsOutboxError> { 25 match value { 26 "queued" => Ok(Self::Queued), 27 "complete" => Ok(Self::Complete), 28 "failed_terminal" => Ok(Self::FailedTerminal), 29 "cancelled" => Ok(Self::Cancelled), 30 _ => Err(RadrootsOutboxError::InvalidStoredEnum { 31 field: "outbox_operation.status", 32 value: value.to_owned(), 33 }), 34 } 35 } 36 } 37 38 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 39 pub enum RadrootsOutboxEventState { 40 DraftQueued, 41 Signing, 42 Signed, 43 Publishing, 44 Published, 45 SignRetryable, 46 PublishRetryable, 47 FailedTerminal, 48 Cancelled, 49 } 50 51 impl RadrootsOutboxEventState { 52 pub fn as_str(self) -> &'static str { 53 match self { 54 Self::DraftQueued => "draft_queued", 55 Self::Signing => "signing", 56 Self::Signed => "signed", 57 Self::Publishing => "publishing", 58 Self::Published => "published", 59 Self::SignRetryable => "sign_retryable", 60 Self::PublishRetryable => "publish_retryable", 61 Self::FailedTerminal => "failed_terminal", 62 Self::Cancelled => "cancelled", 63 } 64 } 65 66 pub fn parse(value: &str) -> Result<Self, RadrootsOutboxError> { 67 match value { 68 "draft_queued" => Ok(Self::DraftQueued), 69 "signing" => Ok(Self::Signing), 70 "signed" => Ok(Self::Signed), 71 "publishing" => Ok(Self::Publishing), 72 "published" => Ok(Self::Published), 73 "sign_retryable" => Ok(Self::SignRetryable), 74 "publish_retryable" => Ok(Self::PublishRetryable), 75 "failed_terminal" => Ok(Self::FailedTerminal), 76 "cancelled" => Ok(Self::Cancelled), 77 _ => Err(RadrootsOutboxError::InvalidStoredEnum { 78 field: "outbox_event.state", 79 value: value.to_owned(), 80 }), 81 } 82 } 83 84 pub fn is_terminal(self) -> bool { 85 matches!( 86 self, 87 Self::Published | Self::FailedTerminal | Self::Cancelled 88 ) 89 } 90 } 91 92 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 93 pub enum RadrootsOutboxRelayStatus { 94 Pending, 95 Accepted, 96 FailedRetryable, 97 FailedTerminal, 98 } 99 100 impl RadrootsOutboxRelayStatus { 101 pub fn as_str(self) -> &'static str { 102 match self { 103 Self::Pending => "pending", 104 Self::Accepted => "accepted", 105 Self::FailedRetryable => "failed_retryable", 106 Self::FailedTerminal => "failed_terminal", 107 } 108 } 109 110 pub fn parse(value: &str) -> Result<Self, RadrootsOutboxError> { 111 match value { 112 "pending" => Ok(Self::Pending), 113 "accepted" => Ok(Self::Accepted), 114 "failed_retryable" => Ok(Self::FailedRetryable), 115 "failed_terminal" => Ok(Self::FailedTerminal), 116 _ => Err(RadrootsOutboxError::InvalidStoredEnum { 117 field: "outbox_event_relay_status.status", 118 value: value.to_owned(), 119 }), 120 } 121 } 122 } 123 124 #[derive(Clone, Debug, PartialEq, Eq)] 125 pub struct RadrootsOutboxOperationInput { 126 pub operation_kind: String, 127 pub draft: RadrootsFrozenEventDraft, 128 pub target_relays: Vec<String>, 129 pub idempotency_key: Option<String>, 130 pub allow_empty_target_relays: bool, 131 pub created_at_ms: i64, 132 } 133 134 impl RadrootsOutboxOperationInput { 135 pub fn new( 136 operation_kind: impl Into<String>, 137 draft: RadrootsFrozenEventDraft, 138 target_relays: Vec<String>, 139 created_at_ms: i64, 140 ) -> Self { 141 Self { 142 operation_kind: operation_kind.into(), 143 draft, 144 target_relays, 145 idempotency_key: None, 146 allow_empty_target_relays: false, 147 created_at_ms, 148 } 149 } 150 151 pub fn with_idempotency_key(mut self, idempotency_key: impl Into<String>) -> Self { 152 self.idempotency_key = Some(idempotency_key.into()); 153 self 154 } 155 156 pub fn allow_empty_target_relays(mut self) -> Self { 157 self.allow_empty_target_relays = true; 158 self 159 } 160 } 161 162 #[derive(Clone, Debug, PartialEq, Eq)] 163 pub struct RadrootsOutboxSignedOperationInput { 164 pub operation_kind: String, 165 pub draft: RadrootsFrozenEventDraft, 166 pub signed_event: RadrootsSignedNostrEvent, 167 pub target_relays: Vec<String>, 168 pub idempotency_key: Option<String>, 169 pub allow_empty_target_relays: bool, 170 pub event_store_inserted: bool, 171 pub event_store_ingested_at_ms: i64, 172 pub created_at_ms: i64, 173 } 174 175 impl RadrootsOutboxSignedOperationInput { 176 pub fn new( 177 operation_kind: impl Into<String>, 178 draft: RadrootsFrozenEventDraft, 179 signed_event: RadrootsSignedNostrEvent, 180 target_relays: Vec<String>, 181 event_store_inserted: bool, 182 event_store_ingested_at_ms: i64, 183 created_at_ms: i64, 184 ) -> Self { 185 Self { 186 operation_kind: operation_kind.into(), 187 draft, 188 signed_event, 189 target_relays, 190 idempotency_key: None, 191 allow_empty_target_relays: false, 192 event_store_inserted, 193 event_store_ingested_at_ms, 194 created_at_ms, 195 } 196 } 197 198 pub fn with_idempotency_key(mut self, idempotency_key: impl Into<String>) -> Self { 199 self.idempotency_key = Some(idempotency_key.into()); 200 self 201 } 202 203 pub fn allow_empty_target_relays(mut self) -> Self { 204 self.allow_empty_target_relays = true; 205 self 206 } 207 } 208 209 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 210 pub enum RadrootsOutboxEnqueueStatus { 211 Inserted, 212 Existing, 213 } 214 215 #[derive(Clone, Debug, PartialEq, Eq)] 216 pub struct RadrootsOutboxEnqueueReceipt { 217 pub status: RadrootsOutboxEnqueueStatus, 218 pub operation_id: i64, 219 pub outbox_event_id: i64, 220 pub expected_event_id: String, 221 pub idempotency_digest: String, 222 } 223 224 #[derive(Clone, Debug, PartialEq, Eq)] 225 pub struct RadrootsOutboxOperationRecord { 226 pub operation_id: i64, 227 pub operation_kind: String, 228 pub expected_pubkey: String, 229 pub idempotency_key: Option<String>, 230 pub idempotency_digest: String, 231 pub status: RadrootsOutboxOperationStatus, 232 pub created_at_ms: i64, 233 pub updated_at_ms: i64, 234 } 235 236 #[derive(Clone, Debug, PartialEq, Eq)] 237 pub struct RadrootsOutboxEventRecord { 238 pub outbox_event_id: i64, 239 pub operation_id: i64, 240 pub event_id: String, 241 pub expected_pubkey: String, 242 pub draft: RadrootsFrozenEventDraft, 243 pub signed_event: Option<RadrootsSignedNostrEvent>, 244 pub raw_event_json: Option<String>, 245 pub state: RadrootsOutboxEventState, 246 pub accepted_quorum: i64, 247 pub attempt_count: i64, 248 pub claim_token: Option<String>, 249 pub claim_owner: Option<String>, 250 pub claim_expires_at_ms: Option<i64>, 251 pub next_attempt_after_ms: i64, 252 pub last_error: Option<String>, 253 pub event_store_ingested: bool, 254 pub event_store_inserted: bool, 255 pub event_store_ingested_at_ms: Option<i64>, 256 pub created_at_ms: i64, 257 pub updated_at_ms: i64, 258 } 259 260 #[derive(Clone, Debug, PartialEq, Eq)] 261 pub struct RadrootsOutboxRelayStatusRecord { 262 pub outbox_event_id: i64, 263 pub relay_url: String, 264 pub status: RadrootsOutboxRelayStatus, 265 pub attempt_count: i64, 266 pub last_attempt_at_ms: Option<i64>, 267 pub acknowledged_at_ms: Option<i64>, 268 pub last_error: Option<String>, 269 } 270 271 #[derive(Clone, Debug, PartialEq, Eq)] 272 pub struct RadrootsOutboxClaimedEvent { 273 pub outbox_event_id: i64, 274 pub operation_id: i64, 275 pub expected_event_id: String, 276 pub attempt_count: i64, 277 pub state: RadrootsOutboxEventState, 278 pub claim_token: String, 279 pub draft: RadrootsFrozenEventDraft, 280 pub signed_event: Option<RadrootsSignedNostrEvent>, 281 pub target_relays: Vec<String>, 282 } 283 284 #[derive(Clone, Debug, PartialEq, Eq)] 285 pub struct RadrootsOutboxEventStoreIngestReceipt { 286 pub outbox_event_id: i64, 287 pub event_id: String, 288 pub already_ingested: bool, 289 pub event_store_inserted: bool, 290 } 291 292 #[derive(Clone, Debug, PartialEq, Eq)] 293 pub struct RadrootsOutboxStatusSummary { 294 pub total_events: i64, 295 pub pending_events: i64, 296 pub retryable_events: i64, 297 pub terminal_events: i64, 298 pub failed_terminal_events: i64, 299 pub ready_signed_events: i64, 300 pub publishing_events: i64, 301 pub last_attempt_at_ms: Option<i64>, 302 pub last_error: Option<String>, 303 } 304 305 #[cfg(test)] 306 mod tests { 307 use super::*; 308 309 #[test] 310 fn operation_event_and_relay_status_values_round_trip() { 311 for (status, expected) in [ 312 (RadrootsOutboxOperationStatus::Queued, "queued"), 313 (RadrootsOutboxOperationStatus::Complete, "complete"), 314 ( 315 RadrootsOutboxOperationStatus::FailedTerminal, 316 "failed_terminal", 317 ), 318 (RadrootsOutboxOperationStatus::Cancelled, "cancelled"), 319 ] { 320 assert_eq!(status.as_str(), expected); 321 assert_eq!( 322 RadrootsOutboxOperationStatus::parse(expected).expect("status"), 323 status 324 ); 325 } 326 assert!(RadrootsOutboxOperationStatus::parse("bad").is_err()); 327 328 for (state, expected, terminal) in [ 329 (RadrootsOutboxEventState::DraftQueued, "draft_queued", false), 330 (RadrootsOutboxEventState::Signing, "signing", false), 331 (RadrootsOutboxEventState::Signed, "signed", false), 332 (RadrootsOutboxEventState::Publishing, "publishing", false), 333 (RadrootsOutboxEventState::Published, "published", true), 334 ( 335 RadrootsOutboxEventState::SignRetryable, 336 "sign_retryable", 337 false, 338 ), 339 ( 340 RadrootsOutboxEventState::PublishRetryable, 341 "publish_retryable", 342 false, 343 ), 344 ( 345 RadrootsOutboxEventState::FailedTerminal, 346 "failed_terminal", 347 true, 348 ), 349 (RadrootsOutboxEventState::Cancelled, "cancelled", true), 350 ] { 351 assert_eq!(state.as_str(), expected); 352 assert_eq!( 353 RadrootsOutboxEventState::parse(expected).expect("state"), 354 state 355 ); 356 assert_eq!(state.is_terminal(), terminal); 357 } 358 assert!(RadrootsOutboxEventState::parse("bad").is_err()); 359 360 for (status, expected) in [ 361 (RadrootsOutboxRelayStatus::Pending, "pending"), 362 (RadrootsOutboxRelayStatus::Accepted, "accepted"), 363 ( 364 RadrootsOutboxRelayStatus::FailedRetryable, 365 "failed_retryable", 366 ), 367 (RadrootsOutboxRelayStatus::FailedTerminal, "failed_terminal"), 368 ] { 369 assert_eq!(status.as_str(), expected); 370 assert_eq!( 371 RadrootsOutboxRelayStatus::parse(expected).expect("relay status"), 372 status 373 ); 374 } 375 assert!(RadrootsOutboxRelayStatus::parse("bad").is_err()); 376 } 377 }