@@ -27,14 +27,44 @@ impl PowerSyncError {
2727 . into ( )
2828 }
2929
30- pub fn argument_error ( desc : & ' static str ) -> Self {
31- RawPowerSyncError :: ArgumentError { desc } . into ( )
30+ pub fn argument_error ( desc : impl Into < Cow < ' static , str > > ) -> Self {
31+ RawPowerSyncError :: ArgumentError {
32+ desc : desc. into ( ) ,
33+ cause : PowerSyncErrorCause :: Unknown ,
34+ }
35+ . into ( )
36+ }
37+
38+ pub fn json_argument_error ( cause : serde_json:: Error ) -> Self {
39+ RawPowerSyncError :: ArgumentError {
40+ desc : "" . into ( ) ,
41+ cause : PowerSyncErrorCause :: Json ( cause) ,
42+ }
43+ . into ( )
44+ }
45+
46+ pub fn json_local_error ( cause : serde_json:: Error ) -> Self {
47+ RawPowerSyncError :: LocalDataError {
48+ cause : PowerSyncErrorCause :: Json ( cause) ,
49+ }
50+ . into ( )
3251 }
3352
3453 pub fn state_error ( desc : & ' static str ) -> Self {
3554 RawPowerSyncError :: StateError { desc } . into ( )
3655 }
3756
57+ pub fn unknown_internal ( ) -> Self {
58+ Self :: internal ( PowerSyncErrorCause :: Unknown )
59+ }
60+
61+ pub fn internal ( cause : impl Into < PowerSyncErrorCause > ) -> Self {
62+ RawPowerSyncError :: Internal {
63+ cause : cause. into ( ) ,
64+ }
65+ . into ( )
66+ }
67+
3868 pub fn apply_to_ctx ( self , description : & str , ctx : * mut context ) {
3969 let mut desc = self . description ( ctx. db_handle ( ) ) ;
4070 desc. insert_str ( 0 , description) ;
@@ -61,15 +91,10 @@ impl PowerSyncError {
6191
6292 match self . inner . as_ref ( ) {
6393 Sqlite { code, .. } => * code,
64- InvalidPendingStatement { .. }
65- | InvalidBucketPriority
66- | ExpectedJsonObject
67- | ArgumentError { .. }
68- | StateError { .. }
69- | JsonObjectTooBig
70- | CrudVtabOutsideOfTransaction => ResultCode :: MISUSE ,
71- MissingClientId | Internal => ResultCode :: ABORT ,
72- JsonError { .. } | BsonError { .. } => ResultCode :: CONSTRAINT_DATATYPE ,
94+ InvalidBucketPriority | ArgumentError { .. } | StateError { .. } => ResultCode :: MISUSE ,
95+ MissingClientId | SyncProtocolError { .. } => ResultCode :: ABORT ,
96+ LocalDataError { .. } => ResultCode :: CORRUPT ,
97+ Internal { .. } => ResultCode :: INTERNAL ,
7398 }
7499 }
75100}
@@ -98,48 +123,85 @@ impl From<ResultCode> for PowerSyncError {
98123 }
99124}
100125
101- impl From < serde_json:: Error > for PowerSyncError {
102- fn from ( value : serde_json:: Error ) -> Self {
103- RawPowerSyncError :: JsonError ( value) . into ( )
104- }
105- }
106-
107- impl From < BsonError > for PowerSyncError {
108- fn from ( value : BsonError ) -> Self {
109- RawPowerSyncError :: from ( value) . into ( )
110- }
111- }
112-
126+ /// A structured enumeration of possible errors that can occur in the core extension.
113127#[ derive( Error , Debug ) ]
114128pub enum RawPowerSyncError {
129+ /// An internal call to SQLite made by the core extension has failed. We store the original
130+ /// result code and an optional context describing what the core extension was trying to do when
131+ /// the error occurred.
132+ ///
133+ /// We don't call `sqlite3_errstr` at the time the error is created. Instead, we stop using the
134+ /// database, bubble the error up to the outermost function/vtab definition and then use
135+ /// [PowerSyncError::description] to create a detailed error message.
136+ ///
137+ /// This error should _never_ be created for anything but rethrowing underlying SQLite errors.
115138 #[ error( "internal SQLite call returned {code}" ) ]
116139 Sqlite {
117140 code : ResultCode ,
118141 context : Option < Cow < ' static , str > > ,
119142 } ,
120- #[ error( "invalid argument: {desc}" ) ]
121- ArgumentError { desc : & ' static str } ,
143+ /// A user (e.g. the one calling a PowerSync function, likely an SDK) has provided invalid
144+ /// arguments.
145+ ///
146+ /// This always indicates an error in how the core extension is used.
147+ #[ error( "invalid argument: {desc}. {cause}" ) ]
148+ ArgumentError {
149+ desc : Cow < ' static , str > ,
150+ cause : PowerSyncErrorCause ,
151+ } ,
152+ /// A PowerSync function or vtab was used in a state where it's unavailable.
153+ ///
154+ /// This always indicates an error in how the core extension is used.
122155 #[ error( "invalid state: {desc}" ) ]
123156 StateError { desc : & ' static str } ,
124- #[ error( "Function required a JSON object, but got another type of JSON value" ) ]
125- ExpectedJsonObject ,
157+ /// We've received a sync line we couldn't parse, or in a state where it doesn't make sense
158+ /// (e.g. a checkpoint diff before we've ever received a checkpoint).
159+ ///
160+ /// This interrupts a sync iteration as we cannot reasonably continue afterwards (the client and
161+ /// server are necessarily in different states).
162+ #[ error( "Sync protocol error: {desc}. {cause}" ) ]
163+ SyncProtocolError {
164+ desc : & ' static str ,
165+ cause : PowerSyncErrorCause ,
166+ } ,
167+ /// There's invalid local data in the database (like malformed JSON in the oplog table).
168+ #[ error( "invalid local data" ) ]
169+ LocalDataError { cause : PowerSyncErrorCause } ,
126170 #[ error( "No client_id found in ps_kv" ) ]
127171 MissingClientId ,
128- #[ error( "Invalid pending statement for raw table: {description}" ) ]
129- InvalidPendingStatement { description : Cow < ' static , str > } ,
130172 #[ error( "Invalid bucket priority value" ) ]
131173 InvalidBucketPriority ,
132- #[ error( "Internal PowerSync error" ) ]
133- Internal ,
134- #[ error( "Error decoding JSON: {0}" ) ]
135- JsonError ( serde_json:: Error ) ,
136- #[ error( "Error decoding BSON" ) ]
137- BsonError {
138- #[ from]
139- source : BsonError ,
140- } ,
141- #[ error( "Too many arguments passed to json_object_fragment" ) ]
142- JsonObjectTooBig ,
143- #[ error( "No tx_id" ) ]
144- CrudVtabOutsideOfTransaction ,
174+ #[ error( "Internal PowerSync error. {cause}" ) ]
175+ Internal { cause : PowerSyncErrorCause } ,
176+ }
177+
178+ #[ derive( Debug ) ]
179+ pub enum PowerSyncErrorCause {
180+ Json ( serde_json:: Error ) ,
181+ Bson ( BsonError ) ,
182+ Unknown ,
183+ }
184+
185+ impl From < serde_json:: Error > for PowerSyncErrorCause {
186+ fn from ( value : serde_json:: Error ) -> Self {
187+ return PowerSyncErrorCause :: Json ( value) ;
188+ }
189+ }
190+
191+ impl From < BsonError > for PowerSyncErrorCause {
192+ fn from ( value : BsonError ) -> Self {
193+ return PowerSyncErrorCause :: Bson ( value) ;
194+ }
195+ }
196+
197+ impl Display for PowerSyncErrorCause {
198+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
199+ write ! ( f, "cause: " ) ?;
200+
201+ match self {
202+ PowerSyncErrorCause :: Json ( error) => error. fmt ( f) ,
203+ PowerSyncErrorCause :: Bson ( error) => error. fmt ( f) ,
204+ PowerSyncErrorCause :: Unknown => write ! ( f, "unknown" ) ,
205+ }
206+ }
145207}
0 commit comments