scientific_workflow/
project.rs1use std::fmt;
23use std::path::{Path, PathBuf};
24
25use serde::Serialize;
26use thiserror::Error;
27
28use crate::configuration::{
29 ConfigurationError, MatchingTaskConfigIter, ParameterSpace, ProjectConfig, ProjectPaths,
30 TaskConfig, TaskConfigIter,
31};
32use crate::system_state::{StateError, SystemStateSchema};
33
34const STATE_SCHEMA_FILE: &str = "state.json";
36
37#[derive(Debug, Error)]
39#[non_exhaustive]
40pub enum ScientificProjectError {
41 #[error(transparent)]
43 Configuration(#[from] ConfigurationError),
44 #[error(transparent)]
46 State(#[from] StateError),
47}
48
49#[derive(Clone)]
54pub struct ScientificProject {
55 configuration: ProjectConfig,
56 state_schema: SystemStateSchema,
57}
58
59impl ScientificProject {
60 pub fn load(project_root: impl Into<PathBuf>) -> Result<Self, ScientificProjectError> {
62 let project_root = project_root.into();
63 let configuration = ProjectConfig::load(&project_root)?;
64 let state_schema = SystemStateSchema::load_json_template(
65 configuration
66 .configuration_directory()
67 .join(STATE_SCHEMA_FILE),
68 )?;
69 Ok(Self {
70 configuration,
71 state_schema,
72 })
73 }
74
75 pub fn load_with_state_schema(
84 project_root: impl Into<PathBuf>,
85 state_schema: SystemStateSchema,
86 ) -> Result<Self, ScientificProjectError> {
87 let configuration = ProjectConfig::load(project_root.into())?;
88 Ok(Self {
89 configuration,
90 state_schema,
91 })
92 }
93
94 pub fn project_root(&self) -> &Path {
96 self.configuration.project_root()
97 }
98
99 pub fn configuration_directory(&self) -> &Path {
101 self.configuration.configuration_directory()
102 }
103
104 pub fn parameters(&self) -> &ParameterSpace {
106 self.configuration.parameters()
107 }
108
109 pub fn paths(&self) -> &ProjectPaths {
111 self.configuration.paths()
112 }
113
114 pub fn resolve_path(&self, key: &str) -> Result<PathBuf, ConfigurationError> {
116 self.configuration.paths().resolve_path(key)
117 }
118
119 pub fn task_count(&self) -> u64 {
121 self.configuration.task_count()
122 }
123
124 pub fn task_config(&self, ordinal: u64) -> Result<TaskConfig, ConfigurationError> {
126 self.configuration.task_config(ordinal)
127 }
128
129 pub fn task_configs(&self) -> TaskConfigIter {
131 self.configuration.task_configs()
132 }
133
134 pub fn task_configs_matching<V>(
136 &self,
137 key: impl Into<String>,
138 value: V,
139 ) -> Result<MatchingTaskConfigIter, ConfigurationError>
140 where
141 V: Serialize,
142 {
143 self.configuration.task_configs_matching(key, value)
144 }
145
146 pub fn unique_task_config_matching<V>(
148 &self,
149 key: impl Into<String>,
150 value: V,
151 ) -> Result<TaskConfig, ConfigurationError>
152 where
153 V: Serialize,
154 {
155 self.configuration.unique_task_config_matching(key, value)
156 }
157
158 pub fn state_schema(&self) -> &SystemStateSchema {
160 &self.state_schema
161 }
162
163 pub fn configuration(&self) -> &ProjectConfig {
165 &self.configuration
166 }
167
168 pub fn into_parts(self) -> (ProjectConfig, SystemStateSchema) {
170 (self.configuration, self.state_schema)
171 }
172}
173
174impl fmt::Debug for ScientificProject {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177 formatter
178 .debug_struct("ScientificProject")
179 .field("project_root", &self.project_root())
180 .field("parameters", &self.parameters().parameter_count())
181 .field("tasks", &self.task_count())
182 .field("paths", &self.paths().len())
183 .field("state_fields", &self.state_schema().len())
184 .finish()
185 }
186}