use schemars::JsonSchema;
use serde::Deserialize;
use super::{
paging, storage, validate_id, ExecutionCatalog, ExecutionRecord, ExecutionScope, HistoryError,
HistoryResult, OutputPage, OutputSource, MAX_PAGE_BYTES, MAX_WATCHED_EXECUTIONS,
};
pub const DEFAULT_OBSERVATION_PAGE_BYTES: usize = 2 * 1024;
pub const MAX_OBSERVATION_ITEMS: usize = MAX_WATCHED_EXECUTIONS;
pub const MAX_OBSERVATION_OUTPUT_BYTES: usize = MAX_PAGE_BYTES;
#[derive(Clone, Debug, Deserialize, JsonSchema)]
pub struct ObservationRequest {
#[schemars(length(min = 1, max = 512))]
pub execution_id: String,
#[serde(default)]
pub source: OutputSource,
#[serde(default)]
pub cursor: Option<String>,
#[serde(default = "default_page_bytes")]
#[schemars(range(min = 4, max = 65536))]
pub page_bytes: usize,
}
const fn default_page_bytes() -> usize {
DEFAULT_OBSERVATION_PAGE_BYTES
}
#[derive(Debug)]
pub struct ObservationItem {
pub execution_id: String,
pub source: OutputSource,
pub record: Option<ExecutionRecord>,
pub page: HistoryResult<OutputPage>,
}
#[derive(Debug)]
pub struct CatalogObservation {
pub sequence: u64,
pub cursor: String,
pub items: Vec<ObservationItem>,
}
pub fn validate_observation_requests(items: &[ObservationRequest]) -> HistoryResult<()> {
if items.is_empty() || items.len() > MAX_OBSERVATION_ITEMS {
return Err(HistoryError::InvalidInput(format!(
"observe requires between 1 and {MAX_OBSERVATION_ITEMS} items"
)));
}
let mut total_page_bytes = 0;
for (index, item) in items.iter().enumerate() {
validate_id(&item.execution_id)?;
if !(4..=MAX_PAGE_BYTES).contains(&item.page_bytes) {
return Err(HistoryError::InvalidInput(format!(
"observe page_bytes must be between 4 and {MAX_PAGE_BYTES}"
)));
}
total_page_bytes += item.page_bytes;
if total_page_bytes > MAX_OBSERVATION_OUTPUT_BYTES {
return Err(HistoryError::InvalidInput(format!(
"observe total page_bytes must not exceed {MAX_OBSERVATION_OUTPUT_BYTES}"
)));
}
if items[..index].iter().any(|previous| {
previous.execution_id == item.execution_id && previous.source == item.source
}) {
return Err(HistoryError::InvalidInput(
"observe requires unique execution_id and source pairs".into(),
));
}
}
Ok(())
}
impl ExecutionCatalog {
pub fn observe(
&self,
scope: &ExecutionScope,
items: &[ObservationRequest],
now: u64,
) -> HistoryResult<CatalogObservation> {
validate_observation_requests(items)?;
storage::transaction(self, scope, now, |transaction| {
let items = items
.iter()
.map(|item| ObservationItem {
execution_id: item.execution_id.clone(),
source: item.source,
record: transaction.find(&item.execution_id).cloned(),
page: paging::read(
transaction,
&item.execution_id,
item.source,
item.cursor.as_deref(),
item.page_bytes,
),
})
.collect();
Ok(CatalogObservation {
sequence: transaction.index.sequence,
cursor: paging::event_cursor(transaction)?,
items,
})
})
}
}