1#[derive(Debug, thiserror::Error)]
5pub enum ForceSyncError {
6 #[error("database pool error: {0}")]
8 Pool(#[from] deadpool_postgres::PoolError),
9
10 #[error("database error: {0}")]
12 Database(#[from] tokio_postgres::Error),
13
14 #[error("sync journal entries require a source cursor")]
16 MissingSourceCursor,
17
18 #[error(
20 "transaction callback failed and rollback also failed: callback={callback}; rollback={rollback}"
21 )]
22 TransactionRollback {
23 callback: Box<Self>,
25 rollback: tokio_postgres::Error,
27 },
28
29 #[error("sync key {part} cannot be empty")]
31 EmptySyncKeyPart {
32 part: &'static str,
34 },
35
36 #[error("missing {entity}")]
38 NotFound {
39 entity: &'static str,
41 },
42
43 #[error("missing required configuration: {field}")]
45 MissingConfiguration {
46 field: &'static str,
48 },
49
50 #[error("lease duration is out of range")]
52 InvalidLeaseDuration,
53
54 #[error("json error: {0}")]
56 Json(#[from] serde_json::Error),
57
58 #[error("pubsub error: {0}")]
60 PubSub(Box<force_pubsub::PubSubError>),
61
62 #[error("invalid outbox operation: {op}")]
64 InvalidOutboxOperation {
65 op: String,
67 },
68
69 #[error("invalid outbox cursor: {cursor}")]
71 InvalidOutboxCursor {
72 cursor: String,
74 },
75
76 #[error("invalid stored value for {field}: {value}")]
78 InvalidStoredValue {
79 field: &'static str,
81 value: String,
83 },
84
85 #[error("not implemented")]
87 NotImplemented,
88}
89
90impl From<force_pubsub::PubSubError> for ForceSyncError {
91 fn from(error: force_pubsub::PubSubError) -> Self {
92 Self::PubSub(Box::new(error))
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_pubsub_error_conversion() {
102 let pubsub_err = force_pubsub::PubSubError::Config("test".to_string());
103 let sync_err: ForceSyncError = pubsub_err.into();
104 assert!(matches!(sync_err, ForceSyncError::PubSub(_)));
105 }
106
107 #[test]
108 fn test_pool_error_conversion() {
109 let pool_err = deadpool_postgres::PoolError::Closed;
110 let sync_err: ForceSyncError = pool_err.into();
111 assert!(matches!(sync_err, ForceSyncError::Pool(_)));
112 }
113
114 #[test]
115 fn test_missing_config_display() {
116 let err = ForceSyncError::MissingConfiguration { field: "tenant_id" };
117 assert_eq!(err.to_string(), "missing required configuration: tenant_id");
118 }
119
120 #[test]
121 fn test_missing_source_cursor_display() {
122 let err = ForceSyncError::MissingSourceCursor;
123 assert_eq!(
124 err.to_string(),
125 "sync journal entries require a source cursor"
126 );
127 }
128
129 #[test]
130 fn test_missing_not_found_display() {
131 let err = ForceSyncError::NotFound { entity: "Account" };
132 assert_eq!(err.to_string(), "missing Account");
133 }
134
135 #[test]
136 fn test_invalid_lease_duration_display() {
137 let err = ForceSyncError::InvalidLeaseDuration;
138 assert_eq!(err.to_string(), "lease duration is out of range");
139 }
140
141 #[test]
142 fn test_not_implemented_display() {
143 let err = ForceSyncError::NotImplemented;
144 assert_eq!(err.to_string(), "not implemented");
145 }
146}