Skip to main content

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 terminal parameter path was declared as both fixed and swept, or a
90    /// scalar/array leaf structurally conflicts with a descendant path.
91    ///
92    /// Fixed values are never defaults or override targets. Keeping the two key
93    /// sets disjoint makes every resolved lookup unambiguous.
94    #[error(
95        "parameter `{key}` appears in both fixed configuration `{fixed_path}` and sweep configuration `{sweep_path}`"
96    )]
97    FixedSweepKeyConflict {
98        /// Canonical colliding parameter path or conflicting path pair.
99        key: String,
100        /// Standard fixed-parameter source path.
101        fixed_path: PathBuf,
102        /// Standard sweep-definition source path.
103        sweep_path: PathBuf,
104    },
105
106    /// Multiplying Cartesian axis lengths exceeded the supported `u64` task
107    /// count.
108    #[error("parameter sweep task count overflows u64 while adding axis `{axis}`")]
109    TaskCountOverflow {
110        /// Axis whose candidate count caused the checked product to overflow.
111        axis: String,
112    },
113
114    /// Indexed task lookup addressed an ordinal outside the generated space.
115    #[error(
116        "task ordinal {ordinal} is out of bounds for a parameter space containing {task_count} tasks"
117    )]
118    TaskOrdinalOutOfBounds {
119        /// Requested zero-based task ordinal.
120        ordinal: u64,
121        /// Total number of deterministic task combinations.
122        task_count: u64,
123    },
124
125    /// Task selection named a fixed or absent key instead of a sweep key.
126    #[error("task selection key `{key}` is not declared by sweep.json")]
127    UnknownSweepParameter {
128        /// Exact, case-sensitive selection key supplied by the caller.
129        key: String,
130    },
131
132    /// A caller-provided typed selector could not be represented as JSON.
133    #[error("failed to encode task selection value for sweep parameter `{key}`")]
134    EncodeTaskSelection {
135        /// Exact sweep key whose target value was being encoded.
136        key: String,
137        /// Underlying Serde JSON conversion failure.
138        #[source]
139        source: serde_json::Error,
140    },
141
142    /// No generated task has the requested exact sweep value.
143    #[error("no task configuration matches sweep parameter `{key}`")]
144    NoMatchingTaskConfiguration {
145        /// Exact sweep key used for selection.
146        key: String,
147    },
148
149    /// One key/value selector matched more than one generated task.
150    #[error("more than one task configuration matches sweep parameter `{key}`")]
151    AmbiguousTaskConfiguration {
152        /// Exact sweep key that was insufficient to identify one task.
153        key: String,
154    },
155
156    /// A resolved task dictionary does not contain the requested exact key.
157    #[error("task ordinal {task_ordinal} does not contain parameter `{key}`")]
158    UnknownTaskParameter {
159        /// Resolved task from which the parameter was requested.
160        task_ordinal: u64,
161        /// Exact, case-sensitive lookup key supplied by the caller.
162        key: String,
163    },
164
165    /// A present JSON value could not be decoded into the caller's requested
166    /// Rust type.
167    #[error("failed to decode parameter `{key}` from task ordinal {task_ordinal}")]
168    DecodeTaskParameter {
169        /// Resolved task containing the source value.
170        task_ordinal: u64,
171        /// Exact parameter key whose value was decoded.
172        key: String,
173        /// Underlying Serde JSON type or data-model failure.
174        #[source]
175        source: serde_json::Error,
176    },
177
178    /// A resolved task dictionary could not be serialized as JSON.
179    #[error("failed to serialize resolved parameters for task ordinal {task_ordinal}")]
180    SerializeTaskParameters {
181        /// Resolved task whose logical fixed/sweep union was being serialized.
182        task_ordinal: u64,
183        /// Underlying JSON serialization failure.
184        #[source]
185        source: serde_json::Error,
186    },
187
188    /// Resolved-task export found different existing content.
189    #[error("resolved task configuration destination `{path}` already contains different data")]
190    ResolvedTaskConfigConflict {
191        /// Existing destination that was preserved.
192        path: PathBuf,
193    },
194
195    /// A project path lookup addressed an undeclared exact key.
196    #[error("project paths do not contain key `{key}`")]
197    UnknownProjectPath {
198        /// Exact, case-sensitive path name supplied by the caller.
199        key: String,
200    },
201
202    /// Exact source configuration could not be written to its destination.
203    #[error("failed to write project configuration file `{path}`")]
204    WriteConfigurationFile {
205        /// Destination file being created, written, synchronized, or renamed.
206        path: PathBuf,
207        /// Underlying filesystem failure.
208        #[source]
209        source: io::Error,
210    },
211}