|
| 1 | +use core::fmt::Display; |
| 2 | + |
1 | 3 | use alloc::{ |
2 | 4 | borrow::Cow, |
| 5 | + boxed::Box, |
3 | 6 | format, |
4 | 7 | string::{String, ToString}, |
5 | 8 | }; |
6 | | -use core::error::Error; |
7 | 9 | use sqlite_nostd::{context, sqlite3, Connection, Context, ResultCode}; |
| 10 | +use thiserror::Error; |
8 | 11 |
|
9 | 12 | use crate::bson::BsonError; |
10 | 13 |
|
11 | | -#[derive(Debug)] |
12 | | -pub struct SQLiteError(pub ResultCode, pub Option<Cow<'static, str>>); |
| 14 | +/// A [RawPowerSyncError], but boxed. |
| 15 | +/// |
| 16 | +/// We allocate errors in boxes to avoid large [Result] types returning these. |
| 17 | +pub struct PowerSyncError { |
| 18 | + inner: Box<RawPowerSyncError>, |
| 19 | +} |
13 | 20 |
|
14 | | -impl SQLiteError { |
15 | | - pub fn with_description(code: ResultCode, message: impl Into<Cow<'static, str>>) -> Self { |
16 | | - Self(code, Some(message.into())) |
| 21 | +impl PowerSyncError { |
| 22 | + pub fn from_sqlite(code: ResultCode, context: impl Into<Cow<'static, str>>) -> Self { |
| 23 | + RawPowerSyncError::Sqlite { |
| 24 | + code, |
| 25 | + context: Some(context.into()), |
| 26 | + } |
| 27 | + .into() |
17 | 28 | } |
18 | 29 |
|
19 | | - pub fn misuse(message: impl Into<Cow<'static, str>>) -> Self { |
20 | | - Self::with_description(ResultCode::MISUSE, message) |
| 30 | + pub fn argument_error(desc: &'static str) -> Self { |
| 31 | + RawPowerSyncError::ArgumentError { desc }.into() |
21 | 32 | } |
22 | | -} |
23 | 33 |
|
24 | | -impl core::fmt::Display for SQLiteError { |
25 | | - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
26 | | - write!(f, "SQLiteError: {:?}", self.0)?; |
27 | | - if let Some(desc) = &self.1 { |
28 | | - write!(f, ", desc: {}", desc)?; |
29 | | - } |
30 | | - Ok(()) |
| 34 | + pub fn state_error(desc: &'static str) -> Self { |
| 35 | + RawPowerSyncError::StateError { desc }.into() |
31 | 36 | } |
32 | | -} |
33 | 37 |
|
34 | | -impl SQLiteError { |
35 | 38 | pub fn apply_to_ctx(self, description: &str, ctx: *mut context) { |
36 | | - let SQLiteError(code, message) = self; |
37 | | - |
38 | | - if let Some(msg) = message { |
39 | | - ctx.result_error(&format!("{:} {:}", description, msg)); |
40 | | - } else { |
41 | | - let error = ctx.db_handle().errmsg().unwrap(); |
42 | | - if error == "not an error" { |
43 | | - ctx.result_error(&format!("{:}", description)); |
44 | | - } else { |
45 | | - ctx.result_error(&format!("{:} {:}", description, error)); |
| 39 | + let mut desc = self.description(ctx.db_handle()); |
| 40 | + desc.insert_str(0, description); |
| 41 | + desc.insert_str(description.len(), ": "); |
| 42 | + |
| 43 | + ctx.result_error(&desc); |
| 44 | + ctx.result_error_code(self.sqlite_error_code()); |
| 45 | + } |
| 46 | + |
| 47 | + /// Obtains a description of this error, fetching it from SQLite if necessary. |
| 48 | + pub fn description(&self, db: *mut sqlite3) -> String { |
| 49 | + if let RawPowerSyncError::Sqlite { .. } = &*self.inner { |
| 50 | + let message = db.errmsg().unwrap_or(String::from("Conversion error")); |
| 51 | + if message != "not an error" { |
| 52 | + return format!("{}, caused by: {message}", self.inner); |
46 | 53 | } |
47 | 54 | } |
48 | | - ctx.result_error_code(code); |
| 55 | + |
| 56 | + self.inner.to_string() |
49 | 57 | } |
50 | | -} |
51 | 58 |
|
52 | | -impl Error for SQLiteError {} |
| 59 | + pub fn sqlite_error_code(&self) -> ResultCode { |
| 60 | + use RawPowerSyncError::*; |
53 | 61 |
|
54 | | -pub trait PSResult<T> { |
55 | | - fn into_db_result(self, db: *mut sqlite3) -> Result<T, SQLiteError>; |
| 62 | + match self.inner.as_ref() { |
| 63 | + 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, |
| 73 | + } |
| 74 | + } |
56 | 75 | } |
57 | 76 |
|
58 | | -impl<T> PSResult<T> for Result<T, ResultCode> { |
59 | | - fn into_db_result(self, db: *mut sqlite3) -> Result<T, SQLiteError> { |
60 | | - if let Err(code) = self { |
61 | | - let message = db.errmsg().unwrap_or(String::from("Conversion error")); |
62 | | - if message == "not an error" { |
63 | | - Err(SQLiteError(code, None)) |
64 | | - } else { |
65 | | - Err(SQLiteError(code, Some(message.into()))) |
66 | | - } |
67 | | - } else if let Ok(r) = self { |
68 | | - Ok(r) |
69 | | - } else { |
70 | | - Err(SQLiteError(ResultCode::ABORT, None)) |
71 | | - } |
| 77 | +impl Display for PowerSyncError { |
| 78 | + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 79 | + self.inner.fmt(f) |
72 | 80 | } |
73 | 81 | } |
74 | 82 |
|
75 | | -impl From<ResultCode> for SQLiteError { |
76 | | - fn from(value: ResultCode) -> Self { |
77 | | - SQLiteError(value, None) |
| 83 | +impl From<RawPowerSyncError> for PowerSyncError { |
| 84 | + fn from(value: RawPowerSyncError) -> Self { |
| 85 | + return PowerSyncError { |
| 86 | + inner: Box::new(value), |
| 87 | + }; |
78 | 88 | } |
79 | 89 | } |
80 | 90 |
|
81 | | -impl From<serde_json::Error> for SQLiteError { |
82 | | - fn from(value: serde_json::Error) -> Self { |
83 | | - SQLiteError::with_description(ResultCode::ABORT, value.to_string()) |
| 91 | +impl From<ResultCode> for PowerSyncError { |
| 92 | + fn from(value: ResultCode) -> Self { |
| 93 | + return RawPowerSyncError::Sqlite { |
| 94 | + code: value, |
| 95 | + context: None, |
| 96 | + } |
| 97 | + .into(); |
84 | 98 | } |
85 | 99 | } |
86 | 100 |
|
87 | | -impl From<core::fmt::Error> for SQLiteError { |
88 | | - fn from(value: core::fmt::Error) -> Self { |
89 | | - SQLiteError::with_description(ResultCode::INTERNAL, format!("{}", value)) |
| 101 | +impl From<serde_json::Error> for PowerSyncError { |
| 102 | + fn from(value: serde_json::Error) -> Self { |
| 103 | + RawPowerSyncError::JsonError(value).into() |
90 | 104 | } |
91 | 105 | } |
92 | 106 |
|
93 | | -impl From<BsonError> for SQLiteError { |
| 107 | +impl From<BsonError> for PowerSyncError { |
94 | 108 | fn from(value: BsonError) -> Self { |
95 | | - SQLiteError::with_description(ResultCode::ERROR, value.to_string()) |
| 109 | + RawPowerSyncError::from(value).into() |
96 | 110 | } |
97 | 111 | } |
| 112 | + |
| 113 | +#[derive(Error, Debug)] |
| 114 | +pub enum RawPowerSyncError { |
| 115 | + #[error("internal SQLite call returned {code}")] |
| 116 | + Sqlite { |
| 117 | + code: ResultCode, |
| 118 | + context: Option<Cow<'static, str>>, |
| 119 | + }, |
| 120 | + #[error("invalid argument: {desc}")] |
| 121 | + ArgumentError { desc: &'static str }, |
| 122 | + #[error("invalid state: {desc}")] |
| 123 | + StateError { desc: &'static str }, |
| 124 | + #[error("Function required a JSON object, but got another type of JSON value")] |
| 125 | + ExpectedJsonObject, |
| 126 | + #[error("No client_id found in ps_kv")] |
| 127 | + MissingClientId, |
| 128 | + #[error("Invalid pending statement for raw table: {description}")] |
| 129 | + InvalidPendingStatement { description: Cow<'static, str> }, |
| 130 | + #[error("Invalid bucket priority value")] |
| 131 | + 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, |
| 145 | +} |
0 commit comments