phasesmith_workflows/
tof_project.rs1use std::collections::BTreeSet;
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6
7use phasesmith_model::{DomainError, ProjectRecord, RecordId};
8
9use crate::{TofLeBailCheckpoint, TofLeBailError, TofLeBailInput, TofLeBailOptions};
10
11#[derive(Clone, Debug, PartialEq)]
13pub struct TofLeBailAnalysis {
14 pub histogram_id: RecordId,
16 pub input: TofLeBailInput,
18 pub options: TofLeBailOptions,
20 pub checkpoint: Option<TofLeBailCheckpoint>,
22}
23
24impl TofLeBailAnalysis {
25 pub fn validate(&self) -> Result<(), TofProjectError> {
31 self.input.validate()?;
32 self.options.validate()?;
33 if let Some(checkpoint) = &self.checkpoint {
34 checkpoint.validate_for(&self.input, &self.options)?;
35 }
36 Ok(())
37 }
38}
39
40#[derive(Clone, Debug, PartialEq)]
42pub struct TofLeBailProjectState {
43 pub project: ProjectRecord,
45 pub analyses: Vec<TofLeBailAnalysis>,
47}
48
49impl TofLeBailProjectState {
50 pub fn validate(&self) -> Result<(), TofProjectError> {
57 self.project.validate()?;
58 let mut histogram_ids = BTreeSet::new();
59 for analysis in &self.analyses {
60 analysis.validate()?;
61 if !histogram_ids.insert(analysis.histogram_id.clone()) {
62 return Err(TofProjectError::DuplicateAnalysis {
63 histogram_id: analysis.histogram_id.clone(),
64 });
65 }
66 let histogram = self
67 .project
68 .tof_histograms
69 .iter()
70 .find(|item| item.histogram_id == analysis.histogram_id)
71 .ok_or_else(|| TofProjectError::UnknownHistogram {
72 histogram_id: analysis.histogram_id.clone(),
73 })?;
74 if analysis.input.pattern != histogram.pattern
75 || analysis.input.instrument != histogram.experiment.instrument
76 {
77 return Err(TofProjectError::HistogramStateMismatch {
78 histogram_id: analysis.histogram_id.clone(),
79 });
80 }
81 let phase_ids = analysis
82 .input
83 .phases
84 .iter()
85 .map(crate::TofLeBailPhase::phase_id)
86 .collect::<Vec<_>>();
87 if phase_ids != histogram.phase_ids.iter().collect::<Vec<_>>() {
88 return Err(TofProjectError::PhaseOrderMismatch {
89 histogram_id: analysis.histogram_id.clone(),
90 });
91 }
92 for phase in &analysis.input.phases {
93 let stored = self
94 .project
95 .phases
96 .iter()
97 .find(|item| &item.phase_id == phase.phase_id())
98 .ok_or_else(|| TofProjectError::PhaseOrderMismatch {
99 histogram_id: analysis.histogram_id.clone(),
100 })?;
101 if stored.name != phase.name() {
102 return Err(TofProjectError::PhaseStateMismatch {
103 phase_id: stored.phase_id.clone(),
104 });
105 }
106 }
107 }
108 Ok(())
109 }
110}
111
112#[derive(Debug)]
114pub enum TofProjectError {
115 Domain(DomainError),
117 Workflow(TofLeBailError),
119 DuplicateAnalysis {
121 histogram_id: RecordId,
123 },
124 UnknownHistogram {
126 histogram_id: RecordId,
128 },
129 HistogramStateMismatch {
131 histogram_id: RecordId,
133 },
134 PhaseOrderMismatch {
136 histogram_id: RecordId,
138 },
139 PhaseStateMismatch {
141 phase_id: RecordId,
143 },
144}
145
146impl Display for TofProjectError {
147 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
148 match self {
149 Self::Domain(error) => Display::fmt(error, formatter),
150 Self::Workflow(error) => Display::fmt(error, formatter),
151 Self::DuplicateAnalysis { histogram_id } => {
152 write!(
153 formatter,
154 "duplicate TOF analysis for histogram {histogram_id}"
155 )
156 }
157 Self::UnknownHistogram { histogram_id } => {
158 write!(formatter, "unknown TOF histogram {histogram_id}")
159 }
160 Self::HistogramStateMismatch { histogram_id } => write!(
161 formatter,
162 "TOF analysis state differs from histogram {histogram_id}"
163 ),
164 Self::PhaseOrderMismatch { histogram_id } => write!(
165 formatter,
166 "TOF analysis phase order differs from histogram {histogram_id}"
167 ),
168 Self::PhaseStateMismatch { phase_id } => {
169 write!(formatter, "TOF analysis phase state differs for {phase_id}")
170 }
171 }
172 }
173}
174
175impl Error for TofProjectError {
176 fn source(&self) -> Option<&(dyn Error + 'static)> {
177 match self {
178 Self::Domain(error) => Some(error),
179 Self::Workflow(error) => Some(error),
180 _ => None,
181 }
182 }
183}
184
185impl From<DomainError> for TofProjectError {
186 fn from(value: DomainError) -> Self {
187 Self::Domain(value)
188 }
189}
190
191impl From<TofLeBailError> for TofProjectError {
192 fn from(value: TofLeBailError) -> Self {
193 Self::Workflow(value)
194 }
195}