apimock_config/error.rs
1//! Errors surfaced by the config crate.
2//!
3//! See `apimock_routing::error` for the rationale on per-crate error
4//! types. `ConfigError` wraps `RoutingError` via `#[from]` so rule-set
5//! load failures flow through without the caller pattern-matching on
6//! origin.
7//!
8//! # 5.1.0 additions
9//!
10//! - `WorkspaceError` — surfaced by `Workspace::load`.
11//! - `ApplyError` — surfaced by `Workspace::apply`.
12//! - `SaveError` — surfaced by `Workspace::save`.
13//!
14//! Each of the three "operation" errors wraps `ConfigError` via
15//! `#[from]` because the underlying cause of most workspace failures
16//! is a plain config load / write problem.
17
18use std::{io, path::PathBuf};
19
20use crate::view::NodeId;
21
22pub type ConfigResult<T> = Result<T, ConfigError>;
23
24#[derive(Debug, thiserror::Error)]
25pub enum ConfigError {
26 /// The config TOML file could not be read from disk.
27 #[error("failed to read config file `{path}`: {source}")]
28 ConfigRead {
29 path: PathBuf,
30 #[source]
31 source: io::Error,
32 },
33
34 /// The config TOML file was read, but could not be parsed.
35 #[error("invalid TOML in `{path}`{canonical_display}: {source}", canonical_display = match canonical {
36 Some(p) => format!(" ({})", p.display()),
37 None => String::new(),
38 })]
39 ConfigParse {
40 path: PathBuf,
41 canonical: Option<PathBuf>,
42 #[source]
43 source: toml::de::Error,
44 },
45
46 /// A path on disk could not be resolved.
47 #[error("failed to resolve path `{path}`: {source}")]
48 PathResolve {
49 path: PathBuf,
50 #[source]
51 source: io::Error,
52 },
53
54 /// Startup-time validation failed — each individual failure is
55 /// already logged at its call site.
56 #[error("configuration validation failed")]
57 Validation,
58
59 /// A rule-set file failed to load or parse. Wraps the routing
60 /// crate's error type.
61 #[error(transparent)]
62 RuleSet(#[from] apimock_routing::RoutingError),
63}
64
65/// Failure during `Workspace::load`. Currently a thin wrapper around
66/// `ConfigError` — kept as its own type so the `Workspace` API signals
67/// intent at the type level and has room to grow (e.g. "path is not a
68/// directory", "no root config found").
69#[derive(Debug, thiserror::Error)]
70pub enum WorkspaceError {
71 #[error(transparent)]
72 Config(#[from] ConfigError),
73
74 /// Root path was not found or was not a regular file/directory.
75 #[error("workspace root `{path}` is not a valid apimock workspace: {reason}")]
76 InvalidRoot { path: PathBuf, reason: String },
77}
78
79/// Failure during `Workspace::apply`.
80///
81/// # Why these particular variants
82///
83/// Every `EditCommand` variant targets a node by NodeId. The two
84/// failure modes are "that ID doesn't exist" and "the ID exists but
85/// refers to a node of the wrong kind for this command". Everything
86/// else (file-not-found when `AddRuleSet` with a missing path) is a
87/// validation issue reported via `ApplyResult::diagnostics`, not an
88/// error return.
89#[derive(Debug, thiserror::Error)]
90pub enum ApplyError {
91 /// The NodeId in the command wasn't found in the workspace.
92 #[error("unknown node id: {id}")]
93 UnknownNode { id: NodeId },
94
95 /// The NodeId exists but names a node of the wrong kind for this
96 /// command (e.g. `DeleteRule` pointing at a rule-set ID).
97 #[error("node {id} is not of the expected kind for this command: {reason}")]
98 WrongNodeKind { id: NodeId, reason: String },
99
100 /// Invalid command payload (e.g. `MoveRule` with `new_index` past
101 /// end of parent's rule list).
102 #[error("invalid edit payload: {reason}")]
103 InvalidPayload { reason: String },
104}
105
106/// Failure during `Workspace::save`.
107#[derive(Debug, thiserror::Error)]
108pub enum SaveError {
109 /// A TOML file failed to serialise.
110 #[error("failed to serialise `{path}`: {source}")]
111 Serialize {
112 path: PathBuf,
113 #[source]
114 source: toml::ser::Error,
115 },
116 /// Writing the serialised TOML to disk failed.
117 #[error("failed to write `{path}`: {source}")]
118 Write {
119 path: PathBuf,
120 #[source]
121 source: io::Error,
122 },
123 /// The workspace's internal state was inconsistent at save time —
124 /// usually a programmer error in the edit layer.
125 #[error("internal inconsistency: {reason}")]
126 Inconsistent { reason: String },
127}
128