spacetimedb_vm/
errors.rs

1use spacetimedb_lib::operator::OpLogic;
2use spacetimedb_sats::{AlgebraicType, AlgebraicValue};
3use spacetimedb_schema::def::error::{AuthError, RelationError};
4use std::fmt;
5use thiserror::Error;
6
7use crate::expr::SourceId;
8
9#[derive(Error, Debug)]
10pub enum ConfigError {
11    #[error("Config parameter `{0}` not found.")]
12    NotFound(String),
13    #[error("Value for config parameter `{0}` is invalid: `{1:?}`. Expected: `{2:?}`")]
14    TypeError(String, AlgebraicValue, AlgebraicType),
15}
16
17/// Typing Errors
18#[derive(Error, Debug)]
19pub enum ErrorType {
20    #[error("Error Parsing `{value}` into type [{ty}]: {err}")]
21    Parse { value: String, ty: String, err: String },
22    #[error("Type Mismatch Join: `{lhs}` != `{rhs}`")]
23    TypeMismatchJoin { lhs: String, rhs: String },
24    #[error("Type Mismatch: `{lhs}` != `{rhs}`")]
25    TypeMismatch { lhs: String, rhs: String },
26    #[error("Type Mismatch: `{lhs}` {op} `{rhs}`, both sides must be an `{expected}` expression")]
27    TypeMismatchLogic {
28        op: OpLogic,
29        lhs: String,
30        rhs: String,
31        expected: String,
32    },
33}
34
35/// Vm Errors
36#[derive(Error, Debug)]
37pub enum ErrorVm {
38    #[error("TypeError {0}")]
39    Type(#[from] ErrorType),
40    #[error("ErrorLang {0}")]
41    Lang(#[from] ErrorLang),
42    #[error("RelationError {0}")]
43    Rel(#[from] RelationError),
44    #[error("AuthError {0}")]
45    Auth(#[from] AuthError),
46    #[error("Unsupported: {0}")]
47    Unsupported(String),
48    #[error("No source table with index {0:?}")]
49    NoSuchSource(SourceId),
50    #[error("ConfigError: {0}")]
51    Config(#[from] ConfigError),
52    #[error("{0}")]
53    Other(#[from] anyhow::Error),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub enum ErrorKind {
58    Custom(String),
59    Compiler,
60    TypeMismatch,
61    Db,
62    Query,
63    Duplicated,
64    Invalid,
65    NotFound,
66    Params,
67    OutOfBounds,
68    Timeout,
69    Unauthorized,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct ErrorCtx {
74    key: String,
75    value: String,
76}
77
78impl ErrorCtx {
79    pub fn new(key: &str, value: &str) -> Self {
80        Self {
81            key: key.into(),
82            value: value.into(),
83        }
84    }
85}
86
87/// Define the main User Error type for the VM
88#[derive(Error, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub struct ErrorLang {
90    pub kind: ErrorKind,
91    pub msg: Option<String>,
92    /// Optional context for the Error: Which record was not found, what value was invalid, etc.
93    pub context: Option<Vec<ErrorCtx>>,
94}
95
96impl ErrorLang {
97    pub fn new(kind: ErrorKind, msg: Option<&str>) -> Self {
98        Self {
99            kind,
100            msg: msg.map(|x| x.to_string()),
101            context: None,
102        }
103    }
104
105    pub fn with_ctx(self, of: ErrorCtx) -> Self {
106        let mut x = self;
107        if let Some(ref mut s) = x.context {
108            s.push(of)
109        } else {
110            x.context = Some(vec![of])
111        }
112        x
113    }
114}
115
116impl fmt::Display for ErrorLang {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(f, "{:?}Error", self.kind)?;
119        if let Some(msg) = &self.msg {
120            writeln!(f, ": \"{msg}\"")?;
121        }
122        if let Some(err) = self.context.as_deref() {
123            writeln!(f, " Context:")?;
124            for e in err {
125                writeln!(f, " {}: {}", e.key, e.value)?;
126            }
127        }
128        Ok(())
129    }
130}
131
132impl From<ErrorType> for ErrorLang {
133    fn from(x: ErrorType) -> Self {
134        ErrorLang::new(ErrorKind::TypeMismatch, Some(&x.to_string()))
135    }
136}
137
138impl From<ErrorVm> for ErrorLang {
139    fn from(err: ErrorVm) -> Self {
140        match err {
141            ErrorVm::Type(err) => err.into(),
142            ErrorVm::Other(err) => ErrorLang::new(ErrorKind::Db, Some(&err.to_string())),
143            ErrorVm::Rel(err) => ErrorLang::new(ErrorKind::Db, Some(&err.to_string())),
144            ErrorVm::Unsupported(err) => ErrorLang::new(ErrorKind::Compiler, Some(&err)),
145            ErrorVm::Lang(err) => err,
146            ErrorVm::Auth(err) => ErrorLang::new(ErrorKind::Unauthorized, Some(&err.to_string())),
147            ErrorVm::Config(err) => ErrorLang::new(ErrorKind::Db, Some(&err.to_string())),
148            err @ ErrorVm::NoSuchSource(_) => ErrorLang {
149                kind: ErrorKind::Invalid,
150                msg: Some(format!("{err:?}")),
151                context: None,
152            },
153        }
154    }
155}
156
157impl From<RelationError> for ErrorLang {
158    fn from(err: RelationError) -> Self {
159        ErrorVm::Rel(err).into()
160    }
161}