crates/kernel/ents-query/src/error.rs
error.rshistorycomment on this file
| 1 | //! Parse-time and evaluation-time error types. |
| 2 | //! |
| 3 | //! [`ParseError`] is a *validation verdict on author input*: an effect |
| 4 | //! definition carrying a trigger that fails to parse must be rejected |
| 5 | //! before it is stored (`effect.validation`), so every variant explains |
| 6 | //! what the author wrote wrong. [`EvalError`] is infrastructure — a |
| 7 | //! store read failed mid-evaluation — and never a statement about the |
| 8 | //! query's meaning. |
| 9 | |
| 10 | use gix_hash::ObjectId; |
| 11 | |
| 12 | /// Everything that makes a `CommitQuery` malformed (`query.grammar`, |
| 13 | /// `query.rev`, `query.meta`). |
| 14 | #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] |
| 15 | #[non_exhaustive] |
| 16 | pub enum ParseError { |
| 17 | /// The input ended where a term was required. |
| 18 | #[error("unexpected end of query; expected a term")] |
| 19 | UnexpectedEnd, |
| 20 | |
| 21 | /// An atom other than `rev`, `results`, or `meta` was named. The |
| 22 | /// grammar deliberately has no content, time, or external-event |
| 23 | /// atoms (`query.no-extensions`). |
| 24 | #[error( |
| 25 | "unknown atom {name:?}: the grammar has exactly rev(), results(), and meta() \ |
| 26 | (query.no-extensions)" |
| 27 | )] |
| 28 | UnknownAtom { |
| 29 | /// The atom name as written. |
| 30 | name: String, |
| 31 | }, |
| 32 | |
| 33 | /// Structurally expected input was missing at `at` (byte offset). |
| 34 | #[error("expected {expected} at byte {at}")] |
| 35 | Expected { |
| 36 | /// What the parser needed. |
| 37 | expected: &'static str, |
| 38 | /// Byte offset into the query text. |
| 39 | at: usize, |
| 40 | }, |
| 41 | |
| 42 | /// A parenthesis never closed. |
| 43 | #[error("unbalanced parenthesis opened at byte {at}")] |
| 44 | Unbalanced { |
| 45 | /// Byte offset of the unmatched `(`. |
| 46 | at: usize, |
| 47 | }, |
| 48 | |
| 49 | /// The query parsed, but input remained. |
| 50 | #[error("trailing input after query: {rest:?}")] |
| 51 | Trailing { |
| 52 | /// The unconsumed tail. |
| 53 | rest: String, |
| 54 | }, |
| 55 | |
| 56 | /// `results()` was given a status outside the closed taxonomy. |
| 57 | #[error("results() status must be pass, fail, error, or any; got {got:?}")] |
| 58 | BadStatus { |
| 59 | /// The status as written. |
| 60 | got: String, |
| 61 | }, |
| 62 | |
| 63 | /// `results()` was given an effect name that is not a valid single |
| 64 | /// ref-path segment (`effect.definition`). |
| 65 | #[error("effect name {got:?} is not a valid ref-path segment")] |
| 66 | BadEffectName { |
| 67 | /// The effect name as written. |
| 68 | got: String, |
| 69 | }, |
| 70 | |
| 71 | /// `results(self, ...)` was written in a trigger. `self` is |
| 72 | /// notation substituted at evaluation time (`query.workset`), never |
| 73 | /// a keyword an author may write. |
| 74 | #[error( |
| 75 | "`self` is substituted at evaluation time (query.workset); it cannot be written \ |
| 76 | in a trigger" |
| 77 | )] |
| 78 | SelfKeyword, |
| 79 | |
| 80 | /// A `rev()` expression named a `refs/meta/*` pattern, which is |
| 81 | /// outside `rev()`'s domain by definition (`query.rev`). |
| 82 | #[error("rev() must not name a refs/meta/* pattern; got {pattern:?} (query.rev)")] |
| 83 | MetaInRev { |
| 84 | /// The offending pattern. |
| 85 | pattern: String, |
| 86 | }, |
| 87 | |
| 88 | /// A `meta()` glob does not start with `refs/meta/`, so it could |
| 89 | /// never match an author-written meta-ref. |
| 90 | #[error("meta() glob must start with refs/meta/; got {glob:?} (query.meta)")] |
| 91 | MetaGlobOutside { |
| 92 | /// The glob as written. |
| 93 | glob: String, |
| 94 | }, |
| 95 | |
| 96 | /// A `meta()` glob could match an effect-written namespace — |
| 97 | /// `refs/meta/results/*` or `refs/meta/index/*` — which must be |
| 98 | /// unreachable from `meta()` (`query.meta`, `query.recursion`). |
| 99 | #[error( |
| 100 | "meta() glob {glob:?} could match the effect-written namespace {namespace} \ |
| 101 | (query.meta)" |
| 102 | )] |
| 103 | MetaGlobEffectWritten { |
| 104 | /// The glob as written. |
| 105 | glob: String, |
| 106 | /// The forbidden namespace prefix it could match. |
| 107 | namespace: &'static str, |
| 108 | }, |
| 109 | |
| 110 | /// `rev()` was given an empty expression. |
| 111 | #[error("empty rev() expression")] |
| 112 | EmptyRev, |
| 113 | |
| 114 | /// `rev()` had only `^`-negated terms; at least one positive term |
| 115 | /// is required to denote a non-trivial set. |
| 116 | #[error("rev() needs at least one positive (non-negated) term")] |
| 117 | NoPositiveRev, |
| 118 | |
| 119 | /// A revspec form this evaluator does not support yet. Unsupported |
| 120 | /// forms are an explicit error, never a silent empty set. |
| 121 | #[error( |
| 122 | "unsupported revspec form {token:?}: supported forms are refnames, refs/ globs, \ |
| 123 | full hex object ids, ^negation, and A..B ranges" |
| 124 | )] |
| 125 | UnsupportedRev { |
| 126 | /// The unsupported token. |
| 127 | token: String, |
| 128 | }, |
| 129 | |
| 130 | /// A ref pattern contained bytes a refname can never contain. |
| 131 | #[error("invalid ref pattern {pattern:?}: {why}")] |
| 132 | BadPattern { |
| 133 | /// The pattern as written. |
| 134 | pattern: String, |
| 135 | /// What is wrong with it. |
| 136 | why: &'static str, |
| 137 | }, |
| 138 | } |
| 139 | |
| 140 | /// Everything that can prevent evaluation from completing — always |
| 141 | /// infrastructure, never a property of the query. |
| 142 | #[derive(Debug, thiserror::Error)] |
| 143 | #[non_exhaustive] |
| 144 | pub enum EvalError { |
| 145 | /// The ref store's read half failed. |
| 146 | #[error("ref store read failed: {0}")] |
| 147 | Refs(#[from] gix_ref_store::Error), |
| 148 | |
| 149 | /// The object store failed while looking up `oid`. |
| 150 | #[error("object lookup failed for {oid}: {source}")] |
| 151 | Object { |
| 152 | /// The object being looked up. |
| 153 | oid: ObjectId, |
| 154 | /// The underlying object-store error. |
| 155 | #[source] |
| 156 | source: gix_object::find::Error, |
| 157 | }, |
| 158 | |
| 159 | /// `oid` was referenced (by a ref tip or a parent edge) but is not |
| 160 | /// in the object store. |
| 161 | #[error("object {oid} is missing from the object store")] |
| 162 | Missing { |
| 163 | /// The absent object. |
| 164 | oid: ObjectId, |
| 165 | }, |
| 166 | |
| 167 | /// `oid` exists but could not be decoded as a commit. |
| 168 | #[error("object {oid} could not be decoded: {detail}")] |
| 169 | Decode { |
| 170 | /// The undecodable object. |
| 171 | oid: ObjectId, |
| 172 | /// What failed, human-readable. |
| 173 | detail: String, |
| 174 | }, |
| 175 | |
| 176 | /// A results ref's tip tree did not deserialize as a recorded |
| 177 | /// [`ents_model::Status`]. Evaluation fails rather than guessing a |
| 178 | /// status (`model.result-taxonomy` is a closed taxonomy). |
| 179 | #[error("results ref {name} has an unreadable status tree: {source}")] |
| 180 | Status { |
| 181 | /// The results refname. |
| 182 | name: String, |
| 183 | /// The typed-tree deserialization error. |
| 184 | #[source] |
| 185 | source: facet_git_tree::Error, |
| 186 | }, |
| 187 | } |
| 188 | |
| 189 | /// The `Result` alias for evaluation operations. |
| 190 | pub type EvalResult<T> = std::result::Result<T, EvalError>; |