use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use super::error::ConfigurationError;
use super::source::{invalid, parse_strict_json, read_source};
const STUDY_SETTINGS_FILE: &str = "study.json";
#[derive(Clone)]
pub struct StudySettings {
inner: Arc<StudySettingsInner>,
}
impl StudySettings {
pub fn load(study_root: impl Into<PathBuf>) -> Result<Self, ConfigurationError> {
let study_root = study_root.into();
let source_path = study_root.join(STUDY_SETTINGS_FILE);
let source = read_source(&source_path)?;
let document = parse_strict_json(&source_path, &source)?.into_json();
let raw: RawStudySettings = serde_json::from_value(document).map_err(|source| {
ConfigurationError::InvalidConfigurationDocument {
path: source_path.clone(),
reason: source.to_string(),
}
})?;
if raw.replicate_settings.replicates == 0 {
return invalid(
&source_path,
"replicate_settings.replicates must be positive",
);
}
Ok(Self {
inner: Arc::new(StudySettingsInner {
study_root,
source_path,
source: source.into_boxed_slice(),
replicate_settings: ReplicateSettings {
replicates: raw.replicate_settings.replicates,
scheduling: raw.replicate_settings.scheduling,
failure_policy: raw.replicate_settings.failure_policy,
base_seed: raw.replicate_settings.base_seed,
},
application: raw.application,
}),
})
}
pub fn study_root(&self) -> &Path {
&self.inner.study_root
}
pub fn source_path(&self) -> &Path {
&self.inner.source_path
}
pub fn source_json(&self) -> &[u8] {
&self.inner.source
}
pub fn replicate_settings(&self) -> ReplicateSettings {
self.inner.replicate_settings
}
pub fn application<T>(&self) -> Result<T, ConfigurationError>
where
T: DeserializeOwned,
{
serde_json::from_value(Value::Object(self.inner.application.clone())).map_err(|source| {
ConfigurationError::InvalidConfigurationDocument {
path: self.source_path().to_path_buf(),
reason: format!("application settings do not match the requested type: {source}"),
}
})
}
}
impl fmt::Debug for StudySettings {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("StudySettings")
.field("study_root", &self.study_root())
.field("source_path", &self.source_path())
.field("replicate_settings", &self.replicate_settings())
.field("application_fields", &self.inner.application.len())
.finish_non_exhaustive()
}
}
struct StudySettingsInner {
study_root: PathBuf,
source_path: PathBuf,
source: Box<[u8]>,
replicate_settings: ReplicateSettings,
application: Map<String, Value>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReplicateSettings {
replicates: u64,
scheduling: ReplicateScheduling,
failure_policy: ReplicateFailurePolicy,
base_seed: u64,
}
impl ReplicateSettings {
pub const fn replicates(self) -> u64 {
self.replicates
}
pub const fn scheduling(self) -> ReplicateScheduling {
self.scheduling
}
pub const fn failure_policy(self) -> ReplicateFailurePolicy {
self.failure_policy
}
pub const fn base_seed(self) -> u64 {
self.base_seed
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ReplicateScheduling {
Sequential,
Parallel,
}
impl ReplicateScheduling {
pub const fn as_str(self) -> &'static str {
match self {
Self::Sequential => "sequential",
Self::Parallel => "parallel",
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ReplicateFailurePolicy {
FailFast,
FinishAll,
}
impl ReplicateFailurePolicy {
pub const fn as_str(self) -> &'static str {
match self {
Self::FailFast => "fail_fast",
Self::FinishAll => "finish_all",
}
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawStudySettings {
replicate_settings: RawReplicateSettings,
#[serde(default)]
application: Map<String, Value>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawReplicateSettings {
replicates: u64,
scheduling: ReplicateScheduling,
failure_policy: ReplicateFailurePolicy,
base_seed: u64,
}