236 lines
6.6 KiB
Rust
236 lines
6.6 KiB
Rust
//! Managed temporary storage use cases.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use domain::ports::{Clock, IdGenerator, ManagedTempStore};
|
|
use domain::{
|
|
ManagedTempAllocation, ManagedTempGcReport, ManagedTempHandle, ManagedTempKind,
|
|
ManagedTempLease, ProjectId,
|
|
};
|
|
|
|
use crate::error::AppError;
|
|
|
|
/// Default lease TTL for managed temp allocations: 24 hours.
|
|
pub const MANAGED_TEMP_DEFAULT_TTL_MS: u64 = 24 * 60 * 60 * 1_000;
|
|
/// Maximum purpose length retained in metadata.
|
|
pub const MANAGED_TEMP_PURPOSE_MAX_CHARS: usize = 120;
|
|
|
|
/// Input for [`AllocateManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct AllocateManagedTempInput {
|
|
/// Optional project that requested this allocation.
|
|
pub owner_project_id: Option<ProjectId>,
|
|
/// Physical allocation kind.
|
|
pub kind: ManagedTempKind,
|
|
/// Short human/debug purpose.
|
|
pub purpose: String,
|
|
/// Optional TTL override in milliseconds. `None` uses the default.
|
|
pub ttl_ms: Option<u64>,
|
|
}
|
|
|
|
/// Output for [`AllocateManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct AllocateManagedTempOutput {
|
|
/// Allocated lease.
|
|
pub lease: ManagedTempLease,
|
|
}
|
|
|
|
/// Allocates an IdeA-managed temporary file/directory.
|
|
pub struct AllocateManagedTemp {
|
|
store: Arc<dyn ManagedTempStore>,
|
|
ids: Arc<dyn IdGenerator>,
|
|
clock: Arc<dyn Clock>,
|
|
}
|
|
|
|
impl AllocateManagedTemp {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(
|
|
store: Arc<dyn ManagedTempStore>,
|
|
ids: Arc<dyn IdGenerator>,
|
|
clock: Arc<dyn Clock>,
|
|
) -> Self {
|
|
Self { store, ids, clock }
|
|
}
|
|
|
|
/// Executes allocation.
|
|
pub async fn execute(
|
|
&self,
|
|
input: AllocateManagedTempInput,
|
|
) -> Result<AllocateManagedTempOutput, AppError> {
|
|
let purpose = bounded_purpose(input.purpose)?;
|
|
let now = now_ms(&self.clock);
|
|
let ttl_ms = input.ttl_ms.unwrap_or(MANAGED_TEMP_DEFAULT_TTL_MS);
|
|
if ttl_ms == 0 {
|
|
return Err(AppError::Invalid(
|
|
"managed temp ttl must be positive".to_owned(),
|
|
));
|
|
}
|
|
let expires_at_ms = now.checked_add(ttl_ms).ok_or_else(|| {
|
|
AppError::Invalid("managed temp ttl exceeds timestamp range".to_owned())
|
|
})?;
|
|
let handle = ManagedTempHandle::new(self.ids.new_uuid().to_string())
|
|
.map_err(|err| AppError::Invalid(err.to_string()))?;
|
|
let lease = self
|
|
.store
|
|
.allocate(ManagedTempAllocation {
|
|
handle,
|
|
kind: input.kind,
|
|
owner_project_id: input.owner_project_id,
|
|
purpose,
|
|
created_at_ms: now,
|
|
expires_at_ms: Some(expires_at_ms),
|
|
})
|
|
.await?;
|
|
Ok(AllocateManagedTempOutput { lease })
|
|
}
|
|
}
|
|
|
|
/// Input for [`ResolveManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ResolveManagedTempInput {
|
|
/// Opaque handle to resolve.
|
|
pub handle: String,
|
|
}
|
|
|
|
/// Output for [`ResolveManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ResolveManagedTempOutput {
|
|
/// Resolved active lease.
|
|
pub lease: ManagedTempLease,
|
|
}
|
|
|
|
/// Resolves a managed temp handle.
|
|
pub struct ResolveManagedTemp {
|
|
store: Arc<dyn ManagedTempStore>,
|
|
clock: Arc<dyn Clock>,
|
|
}
|
|
|
|
impl ResolveManagedTemp {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ManagedTempStore>, clock: Arc<dyn Clock>) -> Self {
|
|
Self { store, clock }
|
|
}
|
|
|
|
/// Executes resolution.
|
|
pub async fn execute(
|
|
&self,
|
|
input: ResolveManagedTempInput,
|
|
) -> Result<ResolveManagedTempOutput, AppError> {
|
|
let handle = ManagedTempHandle::new(input.handle)
|
|
.map_err(|err| AppError::Invalid(err.to_string()))?;
|
|
let lease = self.store.resolve(&handle, now_ms(&self.clock)).await?;
|
|
Ok(ResolveManagedTempOutput { lease })
|
|
}
|
|
}
|
|
|
|
/// Input for [`ReleaseManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ReleaseManagedTempInput {
|
|
/// Opaque handle to release.
|
|
pub handle: String,
|
|
}
|
|
|
|
/// Output for [`ReleaseManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ReleaseManagedTempOutput {
|
|
/// Released lease.
|
|
pub lease: ManagedTempLease,
|
|
}
|
|
|
|
/// Releases a managed temp handle.
|
|
pub struct ReleaseManagedTemp {
|
|
store: Arc<dyn ManagedTempStore>,
|
|
clock: Arc<dyn Clock>,
|
|
}
|
|
|
|
impl ReleaseManagedTemp {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ManagedTempStore>, clock: Arc<dyn Clock>) -> Self {
|
|
Self { store, clock }
|
|
}
|
|
|
|
/// Executes release.
|
|
pub async fn execute(
|
|
&self,
|
|
input: ReleaseManagedTempInput,
|
|
) -> Result<ReleaseManagedTempOutput, AppError> {
|
|
let handle = ManagedTempHandle::new(input.handle)
|
|
.map_err(|err| AppError::Invalid(err.to_string()))?;
|
|
let lease = self.store.release(&handle, now_ms(&self.clock)).await?;
|
|
Ok(ReleaseManagedTempOutput { lease })
|
|
}
|
|
}
|
|
|
|
/// Output for [`GcManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct GcManagedTempOutput {
|
|
/// GC report.
|
|
pub report: ManagedTempGcReport,
|
|
}
|
|
|
|
/// Runs managed temp garbage collection.
|
|
pub struct GcManagedTemp {
|
|
store: Arc<dyn ManagedTempStore>,
|
|
clock: Arc<dyn Clock>,
|
|
}
|
|
|
|
impl GcManagedTemp {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ManagedTempStore>, clock: Arc<dyn Clock>) -> Self {
|
|
Self { store, clock }
|
|
}
|
|
|
|
/// Executes GC.
|
|
pub async fn execute(&self) -> Result<GcManagedTempOutput, AppError> {
|
|
let report = self.store.gc(now_ms(&self.clock)).await?;
|
|
Ok(GcManagedTempOutput { report })
|
|
}
|
|
}
|
|
|
|
/// Output for [`PurgeManagedTemp`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct PurgeManagedTempOutput {
|
|
/// Purge report.
|
|
pub report: ManagedTempGcReport,
|
|
}
|
|
|
|
/// Eagerly purges all managed temporary allocations.
|
|
pub struct PurgeManagedTemp {
|
|
store: Arc<dyn ManagedTempStore>,
|
|
}
|
|
|
|
impl PurgeManagedTemp {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ManagedTempStore>) -> Self {
|
|
Self { store }
|
|
}
|
|
|
|
/// Executes the purge.
|
|
pub async fn execute(&self) -> Result<PurgeManagedTempOutput, AppError> {
|
|
let report = self.store.purge_all().await?;
|
|
Ok(PurgeManagedTempOutput { report })
|
|
}
|
|
}
|
|
|
|
fn now_ms(clock: &Arc<dyn Clock>) -> u64 {
|
|
u64::try_from(clock.now_millis()).unwrap_or(0)
|
|
}
|
|
|
|
fn bounded_purpose(raw: String) -> Result<String, AppError> {
|
|
let trimmed = raw.trim();
|
|
if trimmed.is_empty() {
|
|
return Err(AppError::Invalid(
|
|
"managed temp purpose must not be empty".to_owned(),
|
|
));
|
|
}
|
|
Ok(trimmed
|
|
.chars()
|
|
.take(MANAGED_TEMP_PURPOSE_MAX_CHARS)
|
|
.collect())
|
|
}
|