1use std::borrow::Cow;
2
3use serde::Serialize;
4use statum::MachinePresentation;
5
6use crate::MachineDoc;
7
8#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
15pub struct ExportDoc {
16 machine: ExportMachine,
18 states: Vec<ExportState>,
20 transitions: Vec<ExportTransition>,
22}
23
24impl ExportDoc {
25 pub fn machine(&self) -> ExportMachine {
27 self.machine
28 }
29
30 pub fn states(&self) -> &[ExportState] {
32 &self.states
33 }
34
35 pub fn transitions(&self) -> &[ExportTransition] {
37 &self.transitions
38 }
39
40 pub fn state(&self, index: usize) -> Option<&ExportState> {
42 self.states.get(index)
43 }
44
45 pub fn transition(&self, index: usize) -> Option<&ExportTransition> {
47 self.transitions.get(index)
48 }
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
53pub struct ExportMachine {
54 pub module_path: &'static str,
56 pub rust_type_path: &'static str,
58 pub label: Option<&'static str>,
60 pub description: Option<&'static str>,
62}
63
64#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
66pub struct ExportState {
67 pub index: usize,
69 pub rust_name: &'static str,
71 pub label: Option<&'static str>,
73 pub description: Option<&'static str>,
75 pub has_data: bool,
77 pub is_root: bool,
79}
80
81impl ExportState {
82 pub fn node_id(&self) -> String {
84 format!("s{}", self.index)
85 }
86
87 pub fn display_label(&self) -> Cow<'static, str> {
89 match self.label {
90 Some(label) => Cow::Borrowed(label),
91 None if self.has_data => Cow::Owned(format!("{} (data)", self.rust_name)),
92 None => Cow::Borrowed(self.rust_name),
93 }
94 }
95}
96
97#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
99pub struct ExportTransition {
100 pub index: usize,
102 pub method_name: &'static str,
104 pub label: Option<&'static str>,
106 pub description: Option<&'static str>,
108 pub from: usize,
110 pub to: Vec<usize>,
112}
113
114impl ExportTransition {
115 pub fn transition_id(&self) -> String {
117 format!("t{}", self.index)
118 }
119
120 pub fn display_label(&self) -> &'static str {
122 self.label.unwrap_or(self.method_name)
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum ExportDocError {
130 UnknownStatePresentation { machine: &'static str, entry: usize },
133 DuplicateStatePresentation { machine: &'static str, entry: usize },
135 UnknownTransitionPresentation { machine: &'static str, entry: usize },
138 DuplicateTransitionPresentation { machine: &'static str, entry: usize },
140}
141
142impl core::fmt::Display for ExportDocError {
143 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 match self {
145 Self::UnknownStatePresentation { machine, entry } => write!(
146 formatter,
147 "presentation for machine `{machine}` contains state entry {} whose id is missing from the graph",
148 entry + 1
149 ),
150 Self::DuplicateStatePresentation { machine, entry } => write!(
151 formatter,
152 "presentation for machine `{machine}` contains duplicate state id at entry {}",
153 entry + 1
154 ),
155 Self::UnknownTransitionPresentation { machine, entry } => write!(
156 formatter,
157 "presentation for machine `{machine}` contains transition entry {} whose id is missing from the graph",
158 entry + 1
159 ),
160 Self::DuplicateTransitionPresentation { machine, entry } => write!(
161 formatter,
162 "presentation for machine `{machine}` contains duplicate transition id at entry {}",
163 entry + 1
164 ),
165 }
166 }
167}
168
169impl std::error::Error for ExportDocError {}
170
171impl<S, T> From<&MachineDoc<S, T>> for ExportDoc
172where
173 S: Eq,
174{
175 fn from(doc: &MachineDoc<S, T>) -> Self {
176 Self {
177 machine: ExportMachine {
178 module_path: doc.machine().module_path,
179 rust_type_path: doc.machine().rust_type_path,
180 label: None,
181 description: None,
182 },
183 states: doc
184 .states()
185 .iter()
186 .enumerate()
187 .map(|(index, state)| ExportState {
188 index,
189 rust_name: state.descriptor.rust_name,
190 label: None,
191 description: None,
192 has_data: state.descriptor.has_data,
193 is_root: state.is_root,
194 })
195 .collect(),
196 transitions: doc
197 .edges()
198 .iter()
199 .enumerate()
200 .map(|(index, edge)| ExportTransition {
201 index,
202 method_name: edge.descriptor.method_name,
203 label: None,
204 description: None,
205 from: doc
206 .states()
207 .iter()
208 .position(|state| state.descriptor.id == edge.descriptor.from)
209 .expect("MachineDoc state ids should align with edges"),
210 to: edge
211 .descriptor
212 .to
213 .iter()
214 .map(|target| {
215 doc.states()
216 .iter()
217 .position(|state| state.descriptor.id == *target)
218 .expect("MachineDoc target ids should align with states")
219 })
220 .collect(),
221 })
222 .collect(),
223 }
224 }
225}
226
227pub trait ExportSource {
228 fn export_doc(&self) -> Cow<'_, ExportDoc>;
229}
230
231impl ExportSource for ExportDoc {
232 fn export_doc(&self) -> Cow<'_, ExportDoc> {
233 Cow::Borrowed(self)
234 }
235}
236
237impl<S, T> ExportSource for MachineDoc<S, T>
238where
239 S: Eq,
240{
241 fn export_doc(&self) -> Cow<'_, ExportDoc> {
242 Cow::Owned(self.export())
243 }
244}
245
246impl<S, T> MachineDoc<S, T>
247where
248 S: Eq,
249{
250 pub fn export(&self) -> ExportDoc {
252 ExportDoc::from(self)
253 }
254}
255
256impl<S, T> MachineDoc<S, T>
257where
258 S: Copy + Eq + 'static,
259 T: Copy + Eq + 'static,
260{
261 pub fn export_with_presentation<MachineMeta, StateMeta, TransitionMeta>(
264 &self,
265 presentation: &MachinePresentation<S, T, MachineMeta, StateMeta, TransitionMeta>,
266 ) -> Result<ExportDoc, ExportDocError> {
267 let mut export = self.export();
268
269 if let Some(machine) = &presentation.machine {
270 export.machine.label = machine.label;
271 export.machine.description = machine.description;
272 }
273
274 let mut seen_states = vec![false; export.states.len()];
275 for (entry, presented_state) in presentation.states.iter().enumerate() {
276 let Some(index) = self
277 .states()
278 .iter()
279 .position(|state| state.descriptor.id == presented_state.id)
280 else {
281 return Err(ExportDocError::UnknownStatePresentation {
282 machine: self.machine().rust_type_path,
283 entry,
284 });
285 };
286
287 if std::mem::replace(&mut seen_states[index], true) {
288 return Err(ExportDocError::DuplicateStatePresentation {
289 machine: self.machine().rust_type_path,
290 entry,
291 });
292 }
293
294 let export_state = &mut export.states[index];
295 export_state.label = presented_state.label;
296 export_state.description = presented_state.description;
297 }
298
299 let mut seen_transitions = vec![false; export.transitions.len()];
300 for (entry, presented_transition) in presentation.transitions.iter().enumerate() {
301 let Some(index) = self
302 .edges()
303 .iter()
304 .position(|edge| edge.descriptor.id == presented_transition.id)
305 else {
306 return Err(ExportDocError::UnknownTransitionPresentation {
307 machine: self.machine().rust_type_path,
308 entry,
309 });
310 };
311
312 if std::mem::replace(&mut seen_transitions[index], true) {
313 return Err(ExportDocError::DuplicateTransitionPresentation {
314 machine: self.machine().rust_type_path,
315 entry,
316 });
317 }
318
319 let export_transition = &mut export.transitions[index];
320 export_transition.label = presented_transition.label;
321 export_transition.description = presented_transition.description;
322 }
323
324 Ok(export)
325 }
326}