use crate::store::replay::{ReplayCursor, ReplayStore};
use crate::types::{RunId, StepId, TaskId};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use thiserror::Error;
pub mod inmemory;
pub mod sqlite;
pub use inmemory::InMemoryRunStore;
pub use sqlite::SqliteRunStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RunStatus {
Pending,
Running,
Done,
Failed,
Interrupted,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct DegradationEntry {
pub tool: String,
pub error: String,
pub fallback: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt: Option<u32>,
pub at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct StepEntry {
#[schemars(with = "String")]
pub step_id: StepId,
pub step_ref: Option<String>,
pub status: Option<String>,
pub at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct RunRecord {
#[schemars(with = "String")]
pub id: RunId,
#[schemars(with = "String")]
pub task_id: TaskId,
pub status: RunStatus,
pub step_entries: Vec<StepEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub degradations: Vec<DegradationEntry>,
pub operator_sid: Option<String>,
#[schemars(with = "Option<serde_json::Value>")]
pub result_ref: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_json: Option<String>,
pub created_at: u64,
pub updated_at: u64,
}
#[derive(Debug, Error)]
pub enum RunStoreError {
#[error("run not found: {0}")]
NotFound(RunId),
#[error("run already exists: {0}")]
Duplicate(RunId),
#[error("other: {0}")]
Other(String),
}
#[derive(Clone)]
pub struct RunContext {
pub run_id: RunId,
pub run_store: Arc<dyn RunStore>,
pub replay_store: Option<Arc<dyn ReplayStore>>,
pub replay_cursor: Option<Arc<Mutex<ReplayCursor>>>,
}
impl RunContext {
pub fn new(run_id: RunId, run_store: Arc<dyn RunStore>) -> Self {
Self {
run_id,
run_store,
replay_store: None,
replay_cursor: None,
}
}
pub fn with_replay_store(mut self, store: Arc<dyn ReplayStore>) -> Self {
self.replay_store = Some(store);
self
}
pub fn with_replay_cursor(mut self, cursor: Arc<Mutex<ReplayCursor>>) -> Self {
self.replay_cursor = Some(cursor);
self
}
}
impl std::fmt::Debug for RunContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RunContext")
.field("run_id", &self.run_id)
.field("run_store", &self.run_store.name())
.field(
"replay_store",
&self.replay_store.as_ref().map(|s| s.name()),
)
.field("replay_cursor", &self.replay_cursor.is_some())
.finish()
}
}
#[async_trait]
pub trait RunStore: Send + Sync {
fn name(&self) -> &str;
async fn create(&self, record: RunRecord) -> Result<(), RunStoreError>;
async fn get(&self, id: &RunId) -> Result<RunRecord, RunStoreError>;
async fn list_by_task(&self, task_id: &TaskId) -> Result<Vec<RunRecord>, RunStoreError>;
async fn append_step_entry(&self, id: &RunId, entry: StepEntry) -> Result<(), RunStoreError>;
async fn append_degradation(
&self,
id: &RunId,
entry: DegradationEntry,
) -> Result<(), RunStoreError>;
async fn update_status(&self, id: &RunId, status: RunStatus) -> Result<(), RunStoreError>;
async fn try_transition(
&self,
id: &RunId,
from: RunStatus,
to: RunStatus,
) -> Result<bool, RunStoreError>;
async fn set_result(
&self,
id: &RunId,
result_ref: serde_json::Value,
) -> Result<(), RunStoreError>;
async fn list_running(&self) -> Result<Vec<RunRecord>, RunStoreError>;
}
#[derive(Default)]
pub(crate) struct Inner {
pub(crate) order: Vec<RunId>,
pub(crate) records: HashMap<RunId, RunRecord>,
}
pub(crate) type SharedInner = Mutex<Inner>;