sqlsync_reducer/
types.rs

1use std::{
2    collections::BTreeMap,
3    convert::From,
4    error::Error,
5    fmt::{self, Display, Formatter},
6    panic,
7    str::FromStr,
8};
9
10use log::Level;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14pub type RequestId = u32;
15
16pub type Requests = Option<BTreeMap<RequestId, Request>>;
17pub type Responses = Option<BTreeMap<RequestId, u32>>;
18
19#[derive(Serialize, Deserialize, Debug, Clone)]
20pub enum SqliteValue {
21    Null,
22    Integer(i64),
23    Real(f64),
24    Text(String),
25    Blob(Vec<u8>),
26}
27
28#[derive(Serialize, Deserialize, Debug)]
29pub struct Row(Vec<SqliteValue>);
30
31impl From<Vec<SqliteValue>> for Row {
32    fn from(v: Vec<SqliteValue>) -> Self {
33        Self(v)
34    }
35}
36
37impl FromIterator<SqliteValue> for Row {
38    fn from_iter<T: IntoIterator<Item = SqliteValue>>(iter: T) -> Self {
39        Self(iter.into_iter().collect())
40    }
41}
42
43#[derive(Serialize, Deserialize, Debug)]
44pub enum Request {
45    Query {
46        sql: String,
47        params: Vec<SqliteValue>,
48    },
49    Exec {
50        sql: String,
51        params: Vec<SqliteValue>,
52    },
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56pub struct QueryResponse {
57    pub columns: Vec<String>,
58    pub rows: Vec<Row>,
59}
60
61#[derive(Serialize, Deserialize, Debug)]
62pub struct ExecResponse {
63    pub changes: usize,
64}
65
66#[derive(Serialize, Deserialize, Debug, Error)]
67pub enum ErrorResponse {
68    #[error("SQLite Error({code}): {message}")]
69    SqliteError { code: i32, message: String },
70    #[error("Unknown: {0}")]
71    Unknown(String),
72}
73
74#[derive(Serialize, Deserialize)]
75pub struct LogRecord {
76    level: String,
77    message: String,
78    file: Option<String>,
79    line: Option<u32>,
80}
81
82impl From<&panic::PanicInfo<'_>> for LogRecord {
83    fn from(info: &panic::PanicInfo) -> Self {
84        let loc = info.location();
85        LogRecord {
86            level: log::Level::Error.to_string(),
87            message: info.to_string(),
88            file: loc.map(|l| l.file().to_string()),
89            line: loc.map(|l| l.line()),
90        }
91    }
92}
93
94impl From<&log::Record<'_>> for LogRecord {
95    fn from(record: &log::Record) -> Self {
96        Self {
97            level: record.level().to_string(),
98            message: record.args().to_string(),
99            file: record.file().map(|s| s.to_string()),
100            line: record.line(),
101        }
102    }
103}
104
105impl LogRecord {
106    pub fn log(&self) {
107        log::logger().log(
108            &log::Record::builder()
109                .level(Level::from_str(&self.level).unwrap_or(Level::Error))
110                .file(self.file.as_deref())
111                .line(self.line)
112                .module_path(Some("wasm guest"))
113                .args(format_args!("{}", self.message))
114                .build(),
115        );
116    }
117}
118
119#[derive(Serialize, Deserialize, Debug)]
120pub enum ReducerError {
121    ConversionError {
122        value: SqliteValue,
123        target_type: String,
124    },
125
126    Unknown(String),
127}
128
129impl Display for ReducerError {
130    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
131        write!(f, "ReducerError: {:?}", self)
132    }
133}
134
135impl<E: Error> From<E> for ReducerError {
136    fn from(e: E) -> Self {
137        Self::Unknown(e.to_string())
138    }
139}
140
141// conversion utilities for Row and SqliteValue
142
143impl Row {
144    pub fn get<'a, T>(&'a self, idx: usize) -> Result<T, ReducerError>
145    where
146        T: TryFrom<&'a SqliteValue, Error = ReducerError>,
147    {
148        T::try_from(self.get_value(idx))
149    }
150
151    pub fn maybe_get<'a, T>(&'a self, idx: usize) -> Result<Option<T>, ReducerError>
152    where
153        T: TryFrom<&'a SqliteValue, Error = ReducerError>,
154    {
155        match self.get_value(idx) {
156            SqliteValue::Null => Ok(None),
157            v => Ok(Some(T::try_from(v)?)),
158        }
159    }
160
161    pub fn get_value(&self, idx: usize) -> &SqliteValue {
162        self.0.get(idx).expect("row index out of bounds")
163    }
164}
165
166macro_rules! impl_types_for_sqlvalue {
167    ($e:path, $($t:ty),*) => {
168        $(
169            impl From<$t> for SqliteValue {
170                fn from(t: $t) -> Self {
171                    $e(t.into())
172                }
173            }
174
175            impl TryFrom<&SqliteValue> for $t {
176                type Error = ReducerError;
177
178                fn try_from(value: &SqliteValue) -> Result<Self, Self::Error> {
179                    match value {
180                        $e(i) => Ok(*i as $t),
181                        v => Err(ReducerError::ConversionError {
182                            value: v.clone(),
183                            target_type: stringify!($t).to_owned(),
184                        }),
185                    }
186                }
187            }
188        )*
189    };
190}
191
192impl_types_for_sqlvalue!(SqliteValue::Integer, i8, i16, i32, i64);
193impl_types_for_sqlvalue!(SqliteValue::Real, f32, f64);
194
195impl<T> From<Option<T>> for SqliteValue
196where
197    SqliteValue: From<T>,
198{
199    fn from(o: Option<T>) -> Self {
200        match o {
201            Some(t) => Self::from(t),
202            None => Self::Null,
203        }
204    }
205}
206
207impl From<Vec<u8>> for SqliteValue {
208    fn from(b: Vec<u8>) -> Self {
209        Self::Blob(b)
210    }
211}
212
213impl TryFrom<&SqliteValue> for Vec<u8> {
214    type Error = ReducerError;
215
216    fn try_from(value: &SqliteValue) -> Result<Self, Self::Error> {
217        match value {
218            SqliteValue::Blob(b) => Ok(b.clone()),
219            v => Err(ReducerError::ConversionError {
220                value: v.clone(),
221                target_type: "Vec<u8>".to_owned(),
222            }),
223        }
224    }
225}
226
227impl From<&str> for SqliteValue {
228    fn from(s: &str) -> Self {
229        Self::Text(s.to_string())
230    }
231}
232
233impl<'a> TryFrom<&'a SqliteValue> for &'a str {
234    type Error = ReducerError;
235
236    fn try_from(value: &SqliteValue) -> Result<&str, Self::Error> {
237        match value {
238            SqliteValue::Text(s) => Ok(s.as_str()),
239            v => Err(ReducerError::ConversionError {
240                value: v.clone(),
241                target_type: "&str".to_owned(),
242            }),
243        }
244    }
245}
246
247impl From<String> for SqliteValue {
248    fn from(s: String) -> Self {
249        Self::Text(s)
250    }
251}
252
253impl TryFrom<&SqliteValue> for String {
254    type Error = ReducerError;
255
256    fn try_from(value: &SqliteValue) -> Result<Self, Self::Error> {
257        match value {
258            SqliteValue::Text(s) => Ok(s.clone()),
259            v => Err(ReducerError::ConversionError {
260                value: v.clone(),
261                target_type: "String".to_owned(),
262            }),
263        }
264    }
265}
266
267impl From<bool> for SqliteValue {
268    fn from(b: bool) -> Self {
269        Self::Integer(b as i64)
270    }
271}
272
273impl TryFrom<&SqliteValue> for bool {
274    type Error = ReducerError;
275
276    fn try_from(value: &SqliteValue) -> Result<Self, Self::Error> {
277        match value {
278            SqliteValue::Integer(i) => Ok(*i != 0),
279            v => Err(ReducerError::ConversionError {
280                value: v.clone(),
281                target_type: "bool".to_owned(),
282            }),
283        }
284    }
285}