use std::collections::HashSet;
use serde_json::Value;
use crate::ag_ui::{Interrupt, Metadata, ResumeEntry, ResumeStatus};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResumeError {
UnknownInterrupt(String),
DuplicateAnswer(String),
Uncovered(Vec<String>),
}
impl std::fmt::Display for ResumeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnknownInterrupt(id) => write!(f, "no open interrupt has id '{id}'"),
Self::DuplicateAnswer(id) => write!(f, "interrupt '{id}' is answered twice"),
Self::Uncovered(ids) => write!(
f,
"a resume must answer every open interrupt; unanswered: {}",
ids.join(", ")
),
}
}
}
impl std::error::Error for ResumeError {}
#[derive(Clone, Debug)]
pub struct ResumeBuilder {
open: Vec<String>,
entries: Vec<ResumeEntry>,
}
impl ResumeBuilder {
pub fn new(interrupts: &[Interrupt]) -> Self {
Self {
open: interrupts.iter().map(|i| i.id.clone()).collect(),
entries: Vec::new(),
}
}
fn check(&self, id: &str) -> Result<(), ResumeError> {
if !self.open.iter().any(|open| open == id) {
return Err(ResumeError::UnknownInterrupt(id.to_owned()));
}
if self.entries.iter().any(|e| e.interrupt_id == id) {
return Err(ResumeError::DuplicateAnswer(id.to_owned()));
}
Ok(())
}
pub fn resolve(&mut self, id: &str, payload: Value) -> Result<&mut Self, ResumeError> {
self.answer(id, ResumeStatus::Resolved, Some(payload), None)
}
pub fn cancel(&mut self, id: &str) -> Result<&mut Self, ResumeError> {
self.answer(id, ResumeStatus::Cancelled, None, None)
}
pub fn answer(
&mut self,
id: &str,
status: ResumeStatus,
payload: Option<Value>,
metadata: Option<Metadata>,
) -> Result<&mut Self, ResumeError> {
self.check(id)?;
self.entries.push(ResumeEntry {
interrupt_id: id.to_owned(),
status,
payload,
metadata,
});
Ok(self)
}
pub fn cancel_remaining(&mut self) -> &mut Self {
let answered: HashSet<String> = self
.entries
.iter()
.map(|e| e.interrupt_id.clone())
.collect();
for id in &self.open {
if !answered.contains(id) {
self.entries.push(ResumeEntry {
interrupt_id: id.clone(),
status: ResumeStatus::Cancelled,
payload: None,
metadata: None,
});
}
}
self
}
pub fn build(self) -> Result<Vec<ResumeEntry>, ResumeError> {
let mut entries = self.entries;
let uncovered: Vec<String> = self
.open
.iter()
.filter(|id| !entries.iter().any(|e| &e.interrupt_id == *id))
.cloned()
.collect();
if !uncovered.is_empty() {
return Err(ResumeError::Uncovered(uncovered));
}
let order = |id: &str| self.open.iter().position(|open| open == id);
entries.sort_by_key(|e| order(&e.interrupt_id));
Ok(entries)
}
}
pub fn check_resume_coverage(
interrupts: &[Interrupt],
resume: &[ResumeEntry],
) -> Result<(), ResumeError> {
let mut builder = ResumeBuilder::new(interrupts);
for entry in resume {
builder.answer(
&entry.interrupt_id,
entry.status,
entry.payload.clone(),
entry.metadata.clone(),
)?;
}
builder.build().map(|_| ())
}