Skip to main content

icydb/
error.rs

1use candid::CandidType;
2use derive_more::Display;
3use icydb_core::{
4    db::{query::QueryError, response::ResponseError},
5    error::{ErrorOrigin as CoreErrorOrigin, InternalError},
6    traits::ViewPatchError,
7};
8use serde::{Deserialize, Serialize};
9use thiserror::Error as ThisError;
10
11///
12/// Error
13/// Public error type with a stable class + origin taxonomy.
14///
15
16#[derive(CandidType, Debug, Deserialize, Serialize, ThisError)]
17#[error("{message}")]
18pub struct Error {
19    pub kind: ErrorKind,
20    pub origin: ErrorOrigin,
21    pub message: String,
22}
23
24impl Error {
25    pub fn new(kind: ErrorKind, origin: ErrorOrigin, message: impl Into<String>) -> Self {
26        Self {
27            kind,
28            origin,
29            message: message.into(),
30        }
31    }
32}
33
34impl From<InternalError> for Error {
35    fn from(err: InternalError) -> Self {
36        Self::new(ErrorKind::Internal, err.origin.into(), err.message)
37    }
38}
39
40impl From<QueryError> for Error {
41    fn from(err: QueryError) -> Self {
42        match err {
43            QueryError::Validate(_) | QueryError::Intent(_) | QueryError::Plan(_) => Self::new(
44                ErrorKind::Query(QueryErrorKind::Invalid),
45                ErrorOrigin::Query,
46                err.to_string(),
47            ),
48
49            QueryError::UnsupportedQueryFeature(_) => Self::new(
50                ErrorKind::Query(QueryErrorKind::Unsupported),
51                ErrorOrigin::Query,
52                err.to_string(),
53            ),
54
55            QueryError::Response(ResponseError::NotFound { .. }) => Self::new(
56                ErrorKind::Query(QueryErrorKind::NotFound),
57                ErrorOrigin::Response,
58                err.to_string(),
59            ),
60
61            QueryError::Response(ResponseError::NotUnique { .. }) => Self::new(
62                ErrorKind::Query(QueryErrorKind::NotUnique),
63                ErrorOrigin::Response,
64                err.to_string(),
65            ),
66
67            QueryError::Execute(err) => err.into(),
68        }
69    }
70}
71
72impl From<ResponseError> for Error {
73    fn from(err: ResponseError) -> Self {
74        match err {
75            ResponseError::NotFound { .. } => Self::new(
76                ErrorKind::Query(QueryErrorKind::NotFound),
77                ErrorOrigin::Query,
78                err.to_string(),
79            ),
80
81            ResponseError::NotUnique { .. } => Self::new(
82                ErrorKind::Query(QueryErrorKind::NotUnique),
83                ErrorOrigin::Query,
84                err.to_string(),
85            ),
86        }
87    }
88}
89
90///
91/// ErrorKind
92/// Public error taxonomy for callers and canister interfaces.
93///
94
95#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
96pub enum ErrorKind {
97    Query(QueryErrorKind),
98    Update(UpdateErrorKind),
99    Store(StoreErrorKind),
100
101    /// The caller cannot remediate this.
102    Internal,
103}
104
105///
106/// QueryErrorKind
107///
108
109#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
110pub enum QueryErrorKind {
111    /// Query shape is invalid (unknown fields, bad predicates).
112    Invalid,
113
114    /// The query is valid but requests an unsupported feature.
115    Unsupported,
116
117    /// Pagination requires ordering but none was provided.
118    UnorderedPagination,
119
120    /// Valid query, but no rows matched.
121    NotFound,
122
123    /// Query expected one row but matched many.
124    NotUnique,
125}
126
127///
128/// UpdateErrorKind
129///
130
131#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
132pub enum UpdateErrorKind {
133    /// Patch application failed for semantic reasons.
134    Patch(PatchError),
135
136    /// Target entity does not exist.
137    NotFound,
138
139    /// Domain or schema constraint violated.
140    ConstraintViolation,
141
142    /// Concurrent or state conflict.
143    Conflict,
144}
145
146///
147/// PatchError
148///
149
150#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
151pub enum PatchError {
152    InvalidShape,
153    MissingKey,
154    CardinalityViolation,
155}
156
157impl From<ViewPatchError> for PatchError {
158    fn from(err: ViewPatchError) -> Self {
159        match err {
160            ViewPatchError::InvalidShape { .. } => Self::InvalidShape,
161            ViewPatchError::MissingKey { .. } => Self::MissingKey,
162            ViewPatchError::CardinalityViolation { .. } => Self::CardinalityViolation,
163            ViewPatchError::Context { source, .. } => (*source).into(),
164        }
165    }
166}
167
168///
169/// StoreErrorKind
170///
171
172#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
173pub enum StoreErrorKind {
174    NotFound,
175    Unavailable,
176}
177
178///
179/// ErrorOrigin
180/// Public origin taxonomy for callers and canister interfaces.
181///
182
183#[derive(CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, PartialEq, Serialize)]
184pub enum ErrorOrigin {
185    Executor,
186    Index,
187    Interface,
188    Query,
189    Response,
190    Serialize,
191    Store,
192}
193
194impl From<CoreErrorOrigin> for ErrorOrigin {
195    fn from(origin: CoreErrorOrigin) -> Self {
196        match origin {
197            CoreErrorOrigin::Executor => Self::Executor,
198            CoreErrorOrigin::Index => Self::Index,
199            CoreErrorOrigin::Interface => Self::Interface,
200            CoreErrorOrigin::Query => Self::Query,
201            CoreErrorOrigin::Response => Self::Response,
202            CoreErrorOrigin::Serialize => Self::Serialize,
203            CoreErrorOrigin::Store => Self::Store,
204        }
205    }
206}