use std::sync::Arc;
use async_trait::async_trait;
use crate::case::{ClaimError, TaskStore};
use crate::core::{CaseId, StoreError, Task, TaskId, TaskState, TenantId, Timestamp};
use crate::journal::payload;
use super::KeyRing;
#[derive(Debug)]
pub struct SealedTasks {
inner: Arc<dyn TaskStore>,
keys: Arc<dyn KeyRing>,
tenant: TenantId,
}
impl SealedTasks {
#[must_use]
pub fn wrap(inner: Arc<dyn TaskStore>, keys: Arc<dyn KeyRing>, tenant: TenantId) -> Arc<Self> {
Arc::new(Self {
inner,
keys,
tenant,
})
}
fn scope_for(&self, task: &Task) -> String {
task.case.map_or_else(
|| super::scope(&self.tenant, &task.run.to_string()),
|c| super::scope(&self.tenant, &c.to_string()),
)
}
pub(super) fn aad(tenant: &TenantId, id: TaskId) -> String {
format!("task:{tenant}:{id}")
}
async fn opened(&self, mut task: Task) -> Task {
let Some(envelope) = payload::unwrap(&task.justification.proposed_action) else {
return task;
};
let aad = Self::aad(&self.tenant, task.id);
if let Ok(plain) =
super::envelope::open(self.keys.as_ref(), aad.as_bytes(), &envelope).await
&& let Ok(value) = serde_json::from_slice(&plain)
{
task.justification.proposed_action = value;
}
task
}
async fn opened_all(&self, tasks: Vec<Task>) -> Vec<Task> {
let mut out = Vec::with_capacity(tasks.len());
for task in tasks {
out.push(self.opened(task).await);
}
out
}
}
#[async_trait]
impl TaskStore for SealedTasks {
async fn open(&self, task: &Task) -> Result<Task, StoreError> {
let plain = crate::core::canon::to_bytes(&task.justification.proposed_action)
.map_err(|e| StoreError::Backend(format!("a proposal would not serialise: {e}")))?;
let envelope = super::envelope::seal(
self.keys.as_ref(),
&self.scope_for(task),
Self::aad(&self.tenant, task.id).as_bytes(),
&plain,
)
.await
.map_err(|e| StoreError::Backend(format!("sealing a proposal failed: {e}")))?;
let mut sealed = task.clone();
sealed.justification.proposed_action = payload::wrap(&envelope);
let written = self.inner.open(&sealed).await?;
Ok(self.opened(written).await)
}
async fn task(&self, id: TaskId) -> Result<Option<Task>, StoreError> {
let found = self.inner.task(id).await?;
Ok(match found {
Some(task) => Some(self.opened(task).await),
None => None,
})
}
async fn claim(&self, id: TaskId, actor: &str, roles: &[String]) -> Result<Task, ClaimError> {
let claimed = self.inner.claim(id, actor, roles).await?;
Ok(self.opened(claimed).await)
}
async fn take_over(
&self,
id: TaskId,
from: &str,
actor: &str,
roles: &[String],
) -> Result<Task, ClaimError> {
let taken = self.inner.take_over(id, from, actor, roles).await?;
Ok(self.opened(taken).await)
}
async fn release(&self, id: TaskId, actor: &str) -> Result<(), ClaimError> {
self.inner.release(id, actor).await
}
async fn set_state(&self, id: TaskId, state: TaskState) -> Result<(), StoreError> {
self.inner.set_state(id, state).await
}
async fn queue(&self, roles: &[String], limit: usize) -> Result<Vec<Task>, StoreError> {
let tasks = self.inner.queue(roles, limit).await?;
Ok(self.opened_all(tasks).await)
}
async fn for_case(&self, case: CaseId) -> Result<Vec<Task>, StoreError> {
let tasks = self.inner.for_case(case).await?;
Ok(self.opened_all(tasks).await)
}
async fn open_count(&self) -> Result<u64, StoreError> {
self.inner.open_count().await
}
async fn overdue(&self, now: Timestamp, limit: usize) -> Result<Vec<Task>, StoreError> {
let tasks = self.inner.overdue(now, limit).await?;
Ok(self.opened_all(tasks).await)
}
}