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>,
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>,
}
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())
.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 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>;