const SUPERVISION_KEY: &str = "supervision";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SupervisionPhase {
Held,
Released,
}
impl SupervisionPhase {
fn as_str(self) -> &'static str {
match self {
Self::Held => "held",
Self::Released => "released",
}
}
fn parse(value: &str) -> Option<Self> {
match value.trim() {
"held" => Some(Self::Held),
"released" => Some(Self::Released),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SupervisionCheckpoint {
task: String,
from: String,
to: String,
visit: u64,
}
fn open_descendant_count(task: &rhei_core::ast::Task, machine: &rhei_validator::StateMachine) -> u64 {
let mut count = 0;
for child in &task.children {
let state = normalized_state_name(child.state.as_str(), machine);
if !machine.states.get(&state).map(|def| def.terminal).unwrap_or(false) {
count += 1;
}
count += open_descendant_count(child, machine);
}
count
}
fn execute_on_of(
machine: &rhei_validator::StateMachine,
state_name: &str,
) -> Option<rhei_validator::ExecuteOn> {
machine.states.get(state_name).and_then(|def| def.execute_on())
}
fn task_is_supervising(
task: &rhei_core::ast::Task,
machine: &rhei_validator::StateMachine,
) -> bool {
execute_on_of(machine, &normalized_state_name(task.state.as_str(), machine)).is_some()
}
fn supervision_map<'a>(metadata: Option<&'a Metadata>, task_id: &TaskId) -> Option<&'a YamlMapping> {
task_metadata_map(metadata, task_id)?.get(yaml_key(SUPERVISION_KEY))?.as_mapping()
}
fn recorded_supervision_phase(
metadata: Option<&Metadata>,
task_id: &TaskId,
) -> Option<SupervisionPhase> {
supervision_map(metadata, task_id)
.and_then(|map| map.get(yaml_key("phase")))
.and_then(YamlValue::as_str)
.and_then(SupervisionPhase::parse)
}
fn supervision_phase(metadata: Option<&Metadata>, task_id: &TaskId) -> SupervisionPhase {
recorded_supervision_phase(metadata, task_id).unwrap_or(SupervisionPhase::Held)
}
fn supervision_checkpoints(
metadata: Option<&Metadata>,
task_id: &TaskId,
) -> Vec<SupervisionCheckpoint> {
let Some(entries) = supervision_map(metadata, task_id)
.and_then(|map| map.get(yaml_key("checkpoints")))
.and_then(YamlValue::as_sequence)
else {
return Vec::new();
};
entries
.iter()
.filter_map(|entry| {
let map = entry.as_mapping()?;
let text = |key: &str| {
map.get(yaml_key(key)).and_then(|value| match value {
YamlValue::String(value) => Some(value.clone()),
YamlValue::Number(value) => Some(value.to_string()),
_ => None,
})
};
Some(SupervisionCheckpoint {
task: text("task")?,
from: text("from").unwrap_or_default(),
to: text("to").unwrap_or_default(),
visit: map
.get(yaml_key("visit"))
.and_then(yaml_value_to_u64)
.unwrap_or(1),
})
})
.collect()
}
fn checkpoint_yaml(checkpoint: &SupervisionCheckpoint) -> YamlValue {
let mut map = YamlMapping::new();
map.insert(yaml_key("task"), yaml_key(&checkpoint.task));
map.insert(yaml_key("from"), yaml_key(&checkpoint.from));
map.insert(yaml_key("to"), yaml_key(&checkpoint.to));
map.insert(yaml_key("visit"), yaml_u64(checkpoint.visit));
YamlValue::Mapping(map)
}
fn record_supervision_hold(
existing: Option<&Metadata>,
task_id: &TaskId,
checkpoint: Option<&SupervisionCheckpoint>,
) -> Metadata {
let carried: Vec<YamlValue> = match checkpoint {
Some(_) => supervision_map(existing, task_id)
.and_then(|map| map.get(yaml_key("checkpoints")))
.and_then(YamlValue::as_sequence)
.cloned()
.unwrap_or_default(),
None => Vec::new(),
};
let mut root = existing.cloned().unwrap_or_default();
let metadata_section = ensure_mapping(&mut root, yaml_key("metadata"));
let tasks = ensure_mapping(metadata_section, yaml_key("tasks"));
let task_entry = ensure_mapping(tasks, task_id_yaml_key(task_id));
let supervision = ensure_mapping(task_entry, yaml_key(SUPERVISION_KEY));
supervision.insert(yaml_key("phase"), yaml_key(SupervisionPhase::Held.as_str()));
let mut list = carried;
if let Some(checkpoint) = checkpoint {
list.push(checkpoint_yaml(checkpoint));
}
if list.is_empty() {
supervision.remove(yaml_key("checkpoints"));
} else {
supervision.insert(yaml_key("checkpoints"), YamlValue::Sequence(list));
}
root
}
fn record_supervision_release(existing: Option<&Metadata>, task_id: &TaskId) -> Metadata {
let mut root = existing.cloned().unwrap_or_default();
let metadata_section = ensure_mapping(&mut root, yaml_key("metadata"));
let tasks = ensure_mapping(metadata_section, yaml_key("tasks"));
let task_entry = ensure_mapping(tasks, task_id_yaml_key(task_id));
let supervision = ensure_mapping(task_entry, yaml_key(SUPERVISION_KEY));
supervision.insert(yaml_key("phase"), yaml_key(SupervisionPhase::Released.as_str()));
supervision.remove(yaml_key("checkpoints"));
root
}
fn clear_supervision_for_task(existing: Option<&Metadata>, task_id: &TaskId) -> Option<Metadata> {
let mut root = existing.cloned()?;
let YamlValue::Mapping(metadata_section) = root.get_mut(yaml_key("metadata"))? else {
return Some(root);
};
let YamlValue::Mapping(tasks) = metadata_section.get_mut(yaml_key("tasks"))? else {
return Some(root);
};
let YamlValue::Mapping(task_entry) = tasks.get_mut(task_id_yaml_key(task_id))? else {
return Some(root);
};
task_entry.remove(yaml_key(SUPERVISION_KEY));
Some(root)
}
fn clear_runtime_task_metadata(existing: Option<&Metadata>) -> Option<Metadata> {
let without_visits = clear_runtime_state_visits(existing)?;
let cleared = clear_runtime_supervision(Some(&without_visits))?;
Some(drop_empty_task_metadata(cleared))
}
fn drop_empty_task_metadata(mut root: Metadata) -> Metadata {
let Some(YamlValue::Mapping(metadata_section)) = root.get_mut(yaml_key("metadata")) else {
return root;
};
if let Some(YamlValue::Mapping(tasks)) = metadata_section.get_mut(yaml_key("tasks")) {
tasks.retain(|_, value| !matches!(value, YamlValue::Mapping(map) if map.is_empty()));
if tasks.is_empty() {
metadata_section.remove(yaml_key("tasks"));
}
}
if metadata_section.is_empty() {
root.remove(yaml_key("metadata"));
}
root
}
fn clear_runtime_supervision(existing: Option<&Metadata>) -> Option<Metadata> {
let mut root = existing.cloned()?;
let Some(YamlValue::Mapping(metadata_section)) = root.get_mut(yaml_key("metadata")) else {
return Some(root);
};
let Some(YamlValue::Mapping(tasks)) = metadata_section.get_mut(yaml_key("tasks")) else {
return Some(root);
};
for value in tasks.values_mut() {
if let YamlValue::Mapping(task_map) = value {
task_map.remove(yaml_key(SUPERVISION_KEY));
}
}
Some(root)
}