openehr_store/error.rs
1//! Errors.
2//!
3//! The rule from the `openehr` crate applies here unchanged and matters more:
4//! **an error must not echo stored content** (`X11.7`). A store error is the
5//! one that reaches a connection-pool log, an APM trace, and a paging alert at
6//! once, so these messages name identifiers, tables, and rules — never a
7//! patient's data.
8
9/// What went wrong.
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum StoreError {
13 /// The commit would produce a history that reads back and does not connect.
14 ///
15 /// Wraps the `openehr` crate's own rules rather than restating them, so the
16 /// database and the library cannot disagree about what a valid history is.
17 #[error("commit refused: {0}")]
18 Commit(#[from] openehr::rm::common::CommitError),
19
20 /// The record, container, or version named does not exist.
21 #[error("no such {kind}: {id}")]
22 NotFound {
23 /// What was looked for — `ehr`, `versioned_object`, `version`.
24 kind: &'static str,
25 /// Its identifier. Identifiers are design-time or system-minted, not
26 /// clinical content, so naming one is safe and is the only way a caller
27 /// can act (`X11.7a`).
28 id: String,
29 },
30
31 /// The record already exists.
32 #[error("{kind} already exists: {id}")]
33 Conflict {
34 /// What was being created.
35 kind: &'static str,
36 /// Its identifier.
37 id: String,
38 },
39
40 /// A stored document failed Reference Model validation on the way in.
41 ///
42 /// The store validates before it writes. A store that accepted an invalid
43 /// composition would make every later reader's `validate()` fail on data it
44 /// cannot fix.
45 #[error("{0}")]
46 Invalid(#[from] openehr::ValidationReport),
47
48 /// A value could not be parsed or built.
49 #[error(transparent)]
50 Parse(#[from] openehr::ParseError),
51
52 /// Canonical JSON could not be written or read.
53 #[error("canonical JSON: {0}")]
54 Json(#[from] serde_json::Error),
55
56 /// The engine reported an error.
57 ///
58 /// A string, because the five drivers have five unrelated error types and
59 /// this crate does not depend on any of them. The engine crate is expected
60 /// to have logged the typed error already.
61 #[error("{engine}: {message}")]
62 Engine {
63 /// Which engine.
64 engine: &'static str,
65 /// What it said. The engine crate MUST NOT put row data in here.
66 message: String,
67 },
68
69 /// The database was installed under a different schema version.
70 ///
71 /// Refusing is the whole point. `install()` is `CREATE TABLE IF NOT EXISTS`,
72 /// so against an older database every statement no-ops and the *first
73 /// commit* fails on a column that is not there. A store that returned `Ok`
74 /// here would report success and then fail inexplicably later, which is
75 /// worse than not starting.
76 ///
77 /// There is no migration mechanism (`O10.14`); a deployment holding data
78 /// under `found` must export, recreate, and reload.
79 #[error(
80 "database is at schema version {found}, this build writes {expected}; \
81 there is no migration path (see spec/databases/10-operations.md O10.14)"
82 )]
83 SchemaVersionMismatch {
84 /// The version recorded in the database.
85 found: i64,
86 /// The version this build writes.
87 expected: i64,
88 },
89
90 /// The operation is defined by this crate and not implemented by this
91 /// engine.
92 ///
93 /// Never a silent no-op. An engine crate at a conformance level below
94 /// `Store` returns this rather than pretending.
95 #[error("unsupported by {engine}: {what} (see {spec_ref})")]
96 Unsupported {
97 /// Which engine.
98 engine: &'static str,
99 /// What was asked for.
100 what: &'static str,
101 /// Where the exclusion is recorded.
102 spec_ref: &'static str,
103 },
104}
105
106/// This crate's result alias.
107pub type Result<T, E = StoreError> = core::result::Result<T, E>;