use std::borrow::Cow;
use serde::Serialize;
use statum::MachinePresentation;
use crate::MachineDoc;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ExportDoc {
machine: ExportMachine,
states: Vec<ExportState>,
transitions: Vec<ExportTransition>,
}
impl ExportDoc {
pub fn machine(&self) -> ExportMachine {
self.machine
}
pub fn states(&self) -> &[ExportState] {
&self.states
}
pub fn transitions(&self) -> &[ExportTransition] {
&self.transitions
}
pub fn state(&self, index: usize) -> Option<&ExportState> {
self.states.get(index)
}
pub fn transition(&self, index: usize) -> Option<&ExportTransition> {
self.transitions.get(index)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct ExportMachine {
pub module_path: &'static str,
pub rust_type_path: &'static str,
pub label: Option<&'static str>,
pub description: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct ExportState {
pub index: usize,
pub rust_name: &'static str,
pub label: Option<&'static str>,
pub description: Option<&'static str>,
pub has_data: bool,
pub is_root: bool,
}
impl ExportState {
pub fn node_id(&self) -> String {
format!("s{}", self.index)
}
pub fn display_label(&self) -> Cow<'static, str> {
match self.label {
Some(label) => Cow::Borrowed(label),
None if self.has_data => Cow::Owned(format!("{} (data)", self.rust_name)),
None => Cow::Borrowed(self.rust_name),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ExportTransition {
pub index: usize,
pub method_name: &'static str,
pub label: Option<&'static str>,
pub description: Option<&'static str>,
pub from: usize,
pub to: Vec<usize>,
}
impl ExportTransition {
pub fn transition_id(&self) -> String {
format!("t{}", self.index)
}
pub fn display_label(&self) -> &'static str {
self.label.unwrap_or(self.method_name)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExportDocError {
UnknownStatePresentation { machine: &'static str, entry: usize },
DuplicateStatePresentation { machine: &'static str, entry: usize },
UnknownTransitionPresentation { machine: &'static str, entry: usize },
DuplicateTransitionPresentation { machine: &'static str, entry: usize },
}
impl core::fmt::Display for ExportDocError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnknownStatePresentation { machine, entry } => write!(
formatter,
"presentation for machine `{machine}` contains state entry {} whose id is missing from the graph",
entry + 1
),
Self::DuplicateStatePresentation { machine, entry } => write!(
formatter,
"presentation for machine `{machine}` contains duplicate state id at entry {}",
entry + 1
),
Self::UnknownTransitionPresentation { machine, entry } => write!(
formatter,
"presentation for machine `{machine}` contains transition entry {} whose id is missing from the graph",
entry + 1
),
Self::DuplicateTransitionPresentation { machine, entry } => write!(
formatter,
"presentation for machine `{machine}` contains duplicate transition id at entry {}",
entry + 1
),
}
}
}
impl std::error::Error for ExportDocError {}
impl<S, T> From<&MachineDoc<S, T>> for ExportDoc
where
S: Eq,
{
fn from(doc: &MachineDoc<S, T>) -> Self {
Self {
machine: ExportMachine {
module_path: doc.machine().module_path,
rust_type_path: doc.machine().rust_type_path,
label: None,
description: None,
},
states: doc
.states()
.iter()
.enumerate()
.map(|(index, state)| ExportState {
index,
rust_name: state.descriptor.rust_name,
label: None,
description: None,
has_data: state.descriptor.has_data,
is_root: state.is_root,
})
.collect(),
transitions: doc
.edges()
.iter()
.enumerate()
.map(|(index, edge)| ExportTransition {
index,
method_name: edge.descriptor.method_name,
label: None,
description: None,
from: doc
.states()
.iter()
.position(|state| state.descriptor.id == edge.descriptor.from)
.expect("MachineDoc state ids should align with edges"),
to: edge
.descriptor
.to
.iter()
.map(|target| {
doc.states()
.iter()
.position(|state| state.descriptor.id == *target)
.expect("MachineDoc target ids should align with states")
})
.collect(),
})
.collect(),
}
}
}
pub trait ExportSource {
fn export_doc(&self) -> Cow<'_, ExportDoc>;
}
impl ExportSource for ExportDoc {
fn export_doc(&self) -> Cow<'_, ExportDoc> {
Cow::Borrowed(self)
}
}
impl<S, T> ExportSource for MachineDoc<S, T>
where
S: Eq,
{
fn export_doc(&self) -> Cow<'_, ExportDoc> {
Cow::Owned(self.export())
}
}
impl<S, T> MachineDoc<S, T>
where
S: Eq,
{
pub fn export(&self) -> ExportDoc {
ExportDoc::from(self)
}
}
impl<S, T> MachineDoc<S, T>
where
S: Copy + Eq + 'static,
T: Copy + Eq + 'static,
{
pub fn export_with_presentation<MachineMeta, StateMeta, TransitionMeta>(
&self,
presentation: &MachinePresentation<S, T, MachineMeta, StateMeta, TransitionMeta>,
) -> Result<ExportDoc, ExportDocError> {
let mut export = self.export();
if let Some(machine) = &presentation.machine {
export.machine.label = machine.label;
export.machine.description = machine.description;
}
let mut seen_states = vec![false; export.states.len()];
for (entry, presented_state) in presentation.states.iter().enumerate() {
let Some(index) = self
.states()
.iter()
.position(|state| state.descriptor.id == presented_state.id)
else {
return Err(ExportDocError::UnknownStatePresentation {
machine: self.machine().rust_type_path,
entry,
});
};
if std::mem::replace(&mut seen_states[index], true) {
return Err(ExportDocError::DuplicateStatePresentation {
machine: self.machine().rust_type_path,
entry,
});
}
let export_state = &mut export.states[index];
export_state.label = presented_state.label;
export_state.description = presented_state.description;
}
let mut seen_transitions = vec![false; export.transitions.len()];
for (entry, presented_transition) in presentation.transitions.iter().enumerate() {
let Some(index) = self
.edges()
.iter()
.position(|edge| edge.descriptor.id == presented_transition.id)
else {
return Err(ExportDocError::UnknownTransitionPresentation {
machine: self.machine().rust_type_path,
entry,
});
};
if std::mem::replace(&mut seen_transitions[index], true) {
return Err(ExportDocError::DuplicateTransitionPresentation {
machine: self.machine().rust_type_path,
entry,
});
}
let export_transition = &mut export.transitions[index];
export_transition.label = presented_transition.label;
export_transition.description = presented_transition.description;
}
Ok(export)
}
}