use super::super::frame::validate_commit_request_frame;
use super::super::{CommitIr, CommitOp, CommitPlan, CommitValidationError, PlannedOp};
use super::checks::{validate_ops, OpValidationCursor};
#[cfg(test)]
use super::view::InMemoryValidationView;
use super::view::{CommitValidationView, PublishValidationView};
#[cfg(test)]
use crate::commit::CommitValidationContext;
use crate::error::CoreError;
use crate::metadata::{MetadataState, MetadataView};
use loonfs_api::wire::control::HeadState;
use loonfs_api::{ChangeSeq, InodeId};
use loonfs_objectstore::ObjectStore;
struct CommitShape {
assigned_seq: ChangeSeq,
allocated_inode_ids: Vec<InodeId>,
resulting_next_inode_id: InodeId,
}
#[derive(Clone, Copy)]
pub(crate) struct PublishCommitValidationContext<'a, S: ObjectStore + ?Sized> {
pub(crate) head: &'a HeadState,
pub(crate) metadata_view: MetadataView<'a, 'a, S>,
pub(crate) accepted_rows: &'a MetadataState,
}
#[cfg(test)]
pub(crate) async fn build_commit_plan(
request: &CommitIr,
committed_at_ms: u64,
context: &CommitValidationContext<'_>,
) -> Result<CommitPlan, CommitValidationError> {
let shape = compute_commit_shape(request, &context.head)?;
let view = InMemoryValidationView::new(context.metadata_state, shape.assigned_seq);
build_commit_plan_with_view(request, committed_at_ms, &context.head, shape, view).await
}
pub(crate) async fn build_commit_plan_for_publish<S: ObjectStore + ?Sized>(
request: &CommitIr,
committed_at_ms: u64,
context: &PublishCommitValidationContext<'_, S>,
) -> Result<CommitPlan, CoreError> {
let shape = compute_commit_shape(request, context.head)?;
let committed_seq = shape.assigned_seq;
build_commit_plan_with_view(
request,
committed_at_ms,
context.head,
shape,
PublishValidationView::new(context.metadata_view, context.accepted_rows, committed_seq),
)
.await
}
async fn build_commit_plan_with_view<V: CommitValidationView>(
request: &CommitIr,
committed_at_ms: u64,
head: &HeadState,
shape: CommitShape,
mut metadata_state: V,
) -> Result<CommitPlan, V::Error> {
validate_commit_request_frame(request, head)?;
let validated_ops = validate_ops(
&request.ops,
&mut metadata_state,
&mut OpValidationCursor::new(),
shape.assigned_seq,
committed_at_ms,
&mut shape.allocated_inode_ids.iter().copied(),
)
.await?;
Ok(CommitPlan {
namespace_id: request.namespace_id.clone(),
commit_id: request.commit_id.clone(),
apply_after_seq: head.seq,
assigned_seq: shape.assigned_seq,
validated_ops,
resulting_next_inode_id: shape.resulting_next_inode_id,
})
}
pub(crate) fn allocates_inode(op: &CommitOp) -> bool {
matches!(
op,
CommitOp::CreateDirectory { .. } | CommitOp::CreateFile { .. }
)
}
fn compute_commit_shape(
request: &CommitIr,
head: &HeadState,
) -> Result<CommitShape, CommitValidationError> {
let assigned_seq = head
.seq
.0
.checked_add(1)
.map(ChangeSeq)
.ok_or(CommitValidationError::SeqOverflow)?;
let create_op_count = request
.ops
.iter()
.filter(|planned: &&PlannedOp| allocates_inode(&planned.op))
.count();
let allocated_inode_ids = (0..create_op_count)
.map(|offset| {
let offset =
u64::try_from(offset).map_err(|_| CommitValidationError::NextInodeOverflow)?;
head.next_inode_id
.0
.checked_add(offset)
.map(InodeId)
.ok_or(CommitValidationError::NextInodeOverflow)
})
.collect::<Result<Vec<_>, _>>()?;
let resulting_next_inode_id = head
.next_inode_id
.0
.checked_add(
u64::try_from(create_op_count).map_err(|_| CommitValidationError::NextInodeOverflow)?,
)
.map(InodeId)
.ok_or(CommitValidationError::NextInodeOverflow)?;
Ok(CommitShape {
assigned_seq,
allocated_inode_ids,
resulting_next_inode_id,
})
}