Skip to main content

cliban_core/
error.rs

1//! Error type for the store + contexts.
2//!
3//! Mirrors the Elixir contexts' error vocabulary: changeset-style validation
4//! failures become [`Error::Validation`] (a list of `(field, message)`
5//! pairs, matching `Ecto.Changeset.errors`), domain rejections become typed
6//! variants (e.g. [`Error::ProjectNotFound`], [`Error::NotFound`]), and
7//! anything from rusqlite/serde bubbles up untyped.
8
9use thiserror::Error;
10
11/// A single changeset-style validation error: the field name and a message.
12/// Mirrors one entry of `Ecto.Changeset.errors`.
13pub type FieldError = (String, String);
14
15#[derive(Debug, Error)]
16pub enum Error {
17    /// One or more changeset-style validation failures. The Elixir side
18    /// returns `{:error, %Ecto.Changeset{errors: [...]}}`; we carry the
19    /// equivalent `(field, message)` pairs so callers (and tests) can assert
20    /// on the same field/message contract.
21    #[error("validation failed: {0:?}")]
22    Validation(Vec<FieldError>),
23
24    /// `{:error, :project_not_found}` / `Repo.rollback(:project_not_found)`.
25    #[error("not found: {0}")]
26    ProjectNotFound(String),
27
28    /// A lookup that can name the missing identity.
29    #[error("not found: {0}")]
30    NamedNotFound(String),
31
32    /// `{:error, :not_found}`.
33    #[error("not found")]
34    NotFound,
35
36    /// The writer task is gone (channel closed). Should not happen in normal
37    /// operation; surfaced so callers can fail loudly rather than hang.
38    #[error("store writer task unavailable")]
39    WriterGone,
40
41    /// The `schema_migrations` ledger holds a version this build has never
42    /// heard of and that is *older* than its own — the database was not
43    /// written by any cliban we recognize. (A *newer* version is fine; see
44    /// [`crate::migrations::run`].)
45    #[error(
46        "unrecognized database schema version(s) {found:?} (this build expects {expected}); \
47         the database at this path was not written by a known cliban"
48    )]
49    SchemaUnknown { found: Vec<i64>, expected: i64 },
50
51    #[error(transparent)]
52    Sqlite(#[from] rusqlite::Error),
53
54    #[error(transparent)]
55    Json(#[from] serde_json::Error),
56}
57
58impl Error {
59    /// Construct a single-field validation error, mirroring
60    /// `Ecto.Changeset.add_error(cs, field, msg)`.
61    pub fn validation(field: &str, message: &str) -> Self {
62        Error::Validation(vec![(field.to_string(), message.to_string())])
63    }
64
65    /// True if this is a validation error mentioning `field` with a message
66    /// that contains `needle`. Test convenience that mirrors the Elixir tests'
67    /// `cs.errors[:field] =~ needle` assertions.
68    pub fn has_validation(&self, field: &str, needle: &str) -> bool {
69        match self {
70            Error::Validation(errs) => errs.iter().any(|(f, m)| f == field && m.contains(needle)),
71            _ => false,
72        }
73    }
74}
75
76pub type Result<T> = std::result::Result<T, Error>;