scientific_workflow/configuration/error.rs
1//! Errors produced while loading, expanding, inspecting, and exporting project
2//! configuration.
3//!
4//! This module defines the complete public failure vocabulary for the standard
5//! `config/fixed.json`, `config/sweep.json`, and `config/paths.json` workflow.
6//! Errors retain owned paths, task indices, and exact JSON keys so callers may
7//! report them after the originating [`ParameterSpace`](super::ParameterSpace)
8//! or [`ProjectConfig`](super::ProjectConfig) has been dropped.
9//!
10//! # Error boundaries
11//!
12//! Filesystem and JSON mechanics preserve their original errors through
13//! [`std::error::Error::source`]. Semantic failures—such as a fixed/sweep key
14//! collision or an out-of-range task ordinal—carry their complete context
15//! directly because no lower-level error produced them.
16//!
17//! Configuration errors never contain a resolved task dictionary or scientific
18//! payload. In particular, a typed parameter-decoding failure retains the task
19//! index and parameter name but not the potentially large JSON value.
20
21use std::io;
22use std::path::PathBuf;
23
24use thiserror::Error;
25
26/// A failure encountered while loading or using standardized project
27/// configuration.
28///
29/// Variants are grouped conceptually by source-file IO, source-document
30/// validation, task-space expansion, resolved task access, and exact source
31/// export. The enum is non-exhaustive so later configuration formats can add
32/// precise diagnostics without forcing downstream exhaustive matches.
33#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum ConfigurationError {
36 /// One of the three standard JSON files could not be read.
37 #[error("failed to read project configuration file `{path}`")]
38 ReadConfigurationFile {
39 /// Exact source path selected by the standard project layout.
40 path: PathBuf,
41 /// Underlying filesystem failure.
42 #[source]
43 source: io::Error,
44 },
45
46 /// A readable configuration file did not contain valid JSON in its
47 /// required document shape.
48 ///
49 /// Duplicate object keys are detected during deserialization and reported
50 /// through this variant rather than silently retaining the final value.
51 #[error("failed to parse project configuration file `{path}`")]
52 ParseConfigurationFile {
53 /// Source document containing malformed or structurally invalid JSON.
54 path: PathBuf,
55 /// Underlying JSON syntax or data-model failure.
56 #[source]
57 source: serde_json::Error,
58 },
59
60 /// A syntactically valid source document violated a configuration
61 /// invariant.
62 ///
63 /// Examples include an empty parameter name, a Cartesian axis without
64 /// candidates, inconsistent explicit-case key sets, or a non-string path
65 /// value. `reason` is intended for diagnostics; callers that need stable
66 /// programmatic distinctions should match one of the dedicated variants
67 /// below where available.
68 #[error("invalid project configuration in `{path}`: {reason}")]
69 InvalidConfigurationDocument {
70 /// Configuration file whose semantic content was rejected.
71 path: PathBuf,
72 /// Concise description of the violated invariant.
73 reason: String,
74 },
75
76 /// One JSON object repeated an exact key.
77 ///
78 /// JSON parsers often retain only the last duplicate entry. Scientific
79 /// configuration rejects that ambiguity before constructing a parameter
80 /// space or path table.
81 #[error("project configuration file `{path}` repeats key `{key}`")]
82 DuplicateConfigurationKey {
83 /// Source document containing the duplicate declaration.
84 path: PathBuf,
85 /// Exact, unnormalized JSON key that appeared more than once.
86 key: String,
87 },
88
89 /// A parameter was declared as both fixed and swept.
90 ///
91 /// Fixed values are never defaults or override targets. Keeping the two key
92 /// sets disjoint makes every resolved lookup unambiguous.
93 #[error(
94 "parameter `{key}` appears in both fixed configuration `{fixed_path}` and sweep configuration `{sweep_path}`"
95 )]
96 FixedSweepKeyConflict {
97 /// Exact colliding parameter name.
98 key: String,
99 /// Standard fixed-parameter source path.
100 fixed_path: PathBuf,
101 /// Standard sweep-definition source path.
102 sweep_path: PathBuf,
103 },
104
105 /// Multiplying Cartesian axis lengths exceeded the supported `u64` task
106 /// count.
107 #[error("parameter sweep task count overflows u64 while adding axis `{axis}`")]
108 TaskCountOverflow {
109 /// Axis whose candidate count caused the checked product to overflow.
110 axis: String,
111 },
112
113 /// Indexed task lookup addressed an ordinal outside the generated space.
114 #[error(
115 "task ordinal {ordinal} is out of bounds for a parameter space containing {task_count} tasks"
116 )]
117 TaskOrdinalOutOfBounds {
118 /// Requested zero-based task ordinal.
119 ordinal: u64,
120 /// Total number of deterministic task combinations.
121 task_count: u64,
122 },
123
124 /// Task selection named a fixed or absent key instead of a sweep key.
125 #[error("task selection key `{key}` is not declared by sweep.json")]
126 UnknownSweepParameter {
127 /// Exact, case-sensitive selection key supplied by the caller.
128 key: String,
129 },
130
131 /// A caller-provided typed selector could not be represented as JSON.
132 #[error("failed to encode task selection value for sweep parameter `{key}`")]
133 EncodeTaskSelection {
134 /// Exact sweep key whose target value was being encoded.
135 key: String,
136 /// Underlying Serde JSON conversion failure.
137 #[source]
138 source: serde_json::Error,
139 },
140
141 /// No generated task has the requested exact sweep value.
142 #[error("no task configuration matches sweep parameter `{key}`")]
143 NoMatchingTaskConfiguration {
144 /// Exact sweep key used for selection.
145 key: String,
146 },
147
148 /// One key/value selector matched more than one generated task.
149 #[error("more than one task configuration matches sweep parameter `{key}`")]
150 AmbiguousTaskConfiguration {
151 /// Exact sweep key that was insufficient to identify one task.
152 key: String,
153 },
154
155 /// A resolved task dictionary does not contain the requested exact key.
156 #[error("task ordinal {task_ordinal} does not contain parameter `{key}`")]
157 UnknownTaskParameter {
158 /// Resolved task from which the parameter was requested.
159 task_ordinal: u64,
160 /// Exact, case-sensitive lookup key supplied by the caller.
161 key: String,
162 },
163
164 /// A present JSON value could not be decoded into the caller's requested
165 /// Rust type.
166 #[error("failed to decode parameter `{key}` from task ordinal {task_ordinal}")]
167 DecodeTaskParameter {
168 /// Resolved task containing the source value.
169 task_ordinal: u64,
170 /// Exact parameter key whose value was decoded.
171 key: String,
172 /// Underlying Serde JSON type or data-model failure.
173 #[source]
174 source: serde_json::Error,
175 },
176
177 /// A resolved task dictionary could not be serialized as JSON.
178 #[error("failed to serialize resolved parameters for task ordinal {task_ordinal}")]
179 SerializeTaskParameters {
180 /// Resolved task whose logical fixed/sweep union was being serialized.
181 task_ordinal: u64,
182 /// Underlying JSON serialization failure.
183 #[source]
184 source: serde_json::Error,
185 },
186
187 /// A project path lookup addressed an undeclared exact key.
188 #[error("project paths do not contain key `{key}`")]
189 UnknownProjectPath {
190 /// Exact, case-sensitive path name supplied by the caller.
191 key: String,
192 },
193
194 /// Exact source configuration could not be written to its destination.
195 #[error("failed to write project configuration file `{path}`")]
196 WriteConfigurationFile {
197 /// Destination file being created, written, synchronized, or renamed.
198 path: PathBuf,
199 /// Underlying filesystem failure.
200 #[source]
201 source: io::Error,
202 },
203}