Skip to main content

scientific_workflow/configuration/
settings.rs

1//! Strict study-level replicate policy loaded from `study.json`.
2
3use std::fmt;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use serde::Deserialize;
8
9use super::error::ConfigurationError;
10use super::source::{invalid, parse_strict_json, read_source};
11
12const STUDY_SETTINGS_FILE: &str = "study.json";
13
14/// Validated, immutable study-level replicate settings.
15///
16/// This type owns only the library-defined `study.json` grammar. Scientific
17/// parameters remain in `config/parameters.json`, and named paths remain in
18/// `config/paths.json`.
19#[derive(Clone)]
20pub struct StudySettings {
21    inner: Arc<StudySettingsInner>,
22}
23
24impl StudySettings {
25    /// Loads and validates `study.json` directly beneath `study_root`.
26    pub fn load(study_root: impl Into<PathBuf>) -> Result<Self, ConfigurationError> {
27        let study_root = study_root.into();
28        let source_path = study_root.join(STUDY_SETTINGS_FILE);
29        let source = read_source(&source_path)?;
30        let document = parse_strict_json(&source_path, &source)?.into_json();
31        let raw: RawStudySettings = serde_json::from_value(document).map_err(|source| {
32            ConfigurationError::InvalidConfigurationDocument {
33                path: source_path.clone(),
34                reason: source.to_string(),
35            }
36        })?;
37        if raw.replicate_settings.replicates == 0 {
38            return invalid(
39                &source_path,
40                "replicate_settings.replicates must be positive",
41            );
42        }
43
44        Ok(Self {
45            inner: Arc::new(StudySettingsInner {
46                study_root,
47                source_path,
48                source: source.into_boxed_slice(),
49                replicate_settings: ReplicateSettings {
50                    replicates: raw.replicate_settings.replicates,
51                    execution: raw.replicate_settings.execution,
52                    failure_policy: raw.replicate_settings.failure_policy,
53                    seed: raw.replicate_settings.seed,
54                },
55            }),
56        })
57    }
58
59    /// Returns the study root supplied to [`Self::load`].
60    pub fn study_root(&self) -> &Path {
61        &self.inner.study_root
62    }
63
64    /// Returns the exact `study.json` source path.
65    pub fn source_path(&self) -> &Path {
66        &self.inner.source_path
67    }
68
69    /// Borrows the original validated source bytes without reserialization.
70    pub fn source_json(&self) -> &[u8] {
71        &self.inner.source
72    }
73
74    /// Returns the complete replicate policy.
75    pub fn replicate_settings(&self) -> ReplicateSettings {
76        self.inner.replicate_settings
77    }
78}
79
80impl fmt::Debug for StudySettings {
81    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82        formatter
83            .debug_struct("StudySettings")
84            .field("study_root", &self.study_root())
85            .field("source_path", &self.source_path())
86            .field("replicate_settings", &self.replicate_settings())
87            .finish_non_exhaustive()
88    }
89}
90
91struct StudySettingsInner {
92    study_root: PathBuf,
93    source_path: PathBuf,
94    source: Box<[u8]>,
95    replicate_settings: ReplicateSettings,
96}
97
98/// Validated policy for executing one or more isolated study replicates.
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub struct ReplicateSettings {
101    replicates: u64,
102    execution: ReplicateExecutionMode,
103    failure_policy: ReplicateFailurePolicy,
104    seed: u64,
105}
106
107impl ReplicateSettings {
108    /// Returns the positive number of replicate subprocesses.
109    pub const fn replicates(self) -> u64 {
110        self.replicates
111    }
112
113    /// Returns whether replicate subprocesses run sequentially or in parallel.
114    pub const fn execution(self) -> ReplicateExecutionMode {
115        self.execution
116    }
117
118    /// Returns the controller response to a failed replicate subprocess.
119    pub const fn failure_policy(self) -> ReplicateFailurePolicy {
120        self.failure_policy
121    }
122
123    /// Returns the study-level seed used for lazy per-replicate derivation.
124    pub const fn seed(self) -> u64 {
125        self.seed
126    }
127}
128
129/// Process-level scheduling mode for study replicates.
130#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
131#[serde(rename_all = "snake_case")]
132pub enum ReplicateExecutionMode {
133    /// Start and await one replicate subprocess at a time.
134    Sequential,
135    /// Start one subprocess for every replicate before awaiting completion.
136    Parallel,
137}
138
139impl ReplicateExecutionMode {
140    /// Returns the exact `study.json` spelling.
141    pub const fn as_str(self) -> &'static str {
142        match self {
143            Self::Sequential => "sequential",
144            Self::Parallel => "parallel",
145        }
146    }
147}
148
149/// Controller response when a replicate subprocess fails.
150#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
151#[serde(rename_all = "snake_case")]
152pub enum ReplicateFailurePolicy {
153    /// Stop launching sequential work or terminate active parallel children.
154    FailFast,
155    /// Allow every declared replicate subprocess to finish.
156    FinishAll,
157}
158
159impl ReplicateFailurePolicy {
160    /// Returns the exact `study.json` spelling.
161    pub const fn as_str(self) -> &'static str {
162        match self {
163            Self::FailFast => "fail_fast",
164            Self::FinishAll => "finish_all",
165        }
166    }
167}
168
169#[derive(Deserialize)]
170#[serde(deny_unknown_fields)]
171struct RawStudySettings {
172    replicate_settings: RawReplicateSettings,
173}
174
175#[derive(Deserialize)]
176#[serde(deny_unknown_fields)]
177struct RawReplicateSettings {
178    replicates: u64,
179    execution: ReplicateExecutionMode,
180    failure_policy: ReplicateFailurePolicy,
181    seed: u64,
182}