use crate::patch::{OwnedPatchOperation, kind_of, move_index_out_of_bounds};
use crate::path::OwnedPath;
use crate::reflect::with_sequence_mut;
use crate::{
MetaMut, MoveItemError, Object, ObjectRef, PatchOperation, PatchOperationKind, PathSegment,
ReflectMut, TypeInfo, ValueKind, select_variant,
};
#[allow(unused_imports)]
use crate::{Meta, ObjectRefMut};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::iter::FromIterator;
use core::{fmt, mem};
pub struct MutationRollback {
inverses: Vec<OwnedPatchOperation>,
}
impl fmt::Debug for MutationRollback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MutationRollback")
.field("inverse_count", &self.inverses.len())
.finish()
}
}
impl MutationRollback {
pub fn previous_value(&self, index: usize) -> Option<ObjectRef<'_>> {
match self.inverses.get(index)? {
OwnedPatchOperation::Set { value, .. }
| OwnedPatchOperation::InsertKey { value, .. }
| OwnedPatchOperation::InsertItem { value, .. }
| OwnedPatchOperation::PushItem { value, .. } => Some(value.as_object_ref()),
OwnedPatchOperation::RemoveKey { .. }
| OwnedPatchOperation::RemoveItem { .. }
| OwnedPatchOperation::MoveItem { .. } => None,
}
}
pub fn undo(mut self, target: &mut dyn MetaMut) -> Result<(), RollbackError> {
while let Some(inverse) = self.inverses.pop() {
let inverse_index = self.inverses.len();
apply_staged_operation(target, inverse).map_err(|error| RollbackError {
inverse_index,
error,
})?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct RollbackError {
pub inverse_index: usize,
pub error: CommitOperationError,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommitOperationError {
PathNotFound {
path: OwnedPath,
operation: PatchOperationKind,
},
ShapeMismatch {
path: OwnedPath,
expected: ValueKind,
actual: ValueKind,
},
TypeMismatch {
path: OwnedPath,
expected: TypeInfo,
actual: TypeInfo,
},
IndexOutOfBounds {
path: OwnedPath,
operation: PatchOperationKind,
index: usize,
len: usize,
},
ItemLost {
path: OwnedPath,
index: usize,
},
Unsupported {
path: OwnedPath,
operation: PatchOperationKind,
},
}
impl fmt::Display for CommitOperationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PathNotFound { operation, .. } => write!(
f,
"Staged mutation operation {operation} targets a path that does not exist"
),
Self::ShapeMismatch {
expected, actual, ..
} => {
write!(f, "Staged mutation expected {expected:?}, found {actual:?}")
}
Self::TypeMismatch {
expected, actual, ..
} => write!(
f,
"Staged mutation expected {}, found {}",
expected.name(),
actual.name()
),
Self::IndexOutOfBounds {
operation,
index,
len,
..
} => write!(
f,
"Staged mutation operation {operation} received index {index} for a sequence of length {len}"
),
Self::ItemLost { index, .. } => write!(
f,
"Staged mutation could not restore the item moved from index {index}"
),
Self::Unsupported { operation, .. } => {
write!(f, "Staged mutation operation {operation} is unsupported")
}
}
}
}
impl CommitOperationError {
pub fn path(&self) -> &OwnedPath {
match self {
Self::PathNotFound { path, .. }
| Self::ShapeMismatch { path, .. }
| Self::TypeMismatch { path, .. }
| Self::IndexOutOfBounds { path, .. }
| Self::ItemLost { path, .. }
| Self::Unsupported { path, .. } => path,
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CommitOperationError {}
#[derive(Debug)]
pub enum CommitError {
OperationFailed {
index: usize,
error: CommitOperationError,
},
RollbackFailed {
failed_operation: Box<CommitError>,
operation_index: usize,
error: CommitOperationError,
},
}
impl fmt::Display for CommitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OperationFailed { index, error, .. } => {
write!(f, "Staged mutation operation {index} failed: {error}")
}
Self::RollbackFailed {
operation_index,
error,
..
} => write!(
f,
"Staged mutation rollback for operation {operation_index} failed: {error}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CommitError {}
#[derive(Debug, Default)]
pub struct MutationBatch {
operations: Vec<OwnedPatchOperation>,
}
impl<'a> FromIterator<PatchOperation<'a>> for MutationBatch {
fn from_iter<I: IntoIterator<Item = PatchOperation<'a>>>(operations: I) -> Self {
let mut batch = Self::new();
batch.extend(operations);
batch
}
}
impl<'a, const N: usize> From<[PatchOperation<'a>; N]> for MutationBatch {
fn from(operations: [PatchOperation<'a>; N]) -> Self {
operations.into_iter().collect()
}
}
impl MutationBatch {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.operations.is_empty()
}
pub fn len(&self) -> usize {
self.operations.len()
}
pub fn push(&mut self, operation: impl Into<OwnedPatchOperation>) -> &mut Self {
self.operations.push(operation.into());
self
}
pub fn extend<'a, I>(&mut self, operations: I) -> &mut Self
where
I: IntoIterator<Item = PatchOperation<'a>>,
{
self
.operations
.extend(operations.into_iter().map(OwnedPatchOperation::from));
self
}
#[allow(clippy::result_large_err)]
pub fn commit(self, target: &mut dyn MetaMut) -> Result<MutationRollback, CommitError> {
let mut inverses = self.operations;
for index in 0..inverses.len() {
let placeholder = OwnedPatchOperation::MoveItem {
path: OwnedPath::new(),
from: 0,
to: 0,
};
let operation = mem::replace(&mut inverses[index], placeholder);
match apply_staged_operation(target, operation) {
Ok(inverse) => inverses[index] = inverse,
Err(error) => {
inverses.truncate(index);
let failed_operation = CommitError::OperationFailed { index, error };
let rollback = MutationRollback { inverses };
return Err(match rollback.undo(target) {
Ok(()) => failed_operation,
Err(RollbackError {
inverse_index,
error,
}) => CommitError::RollbackFailed {
failed_operation: Box::new(failed_operation),
operation_index: inverse_index,
error,
},
});
}
}
}
Ok(MutationRollback { inverses })
}
}
fn resolve<'a>(
target: &'a mut dyn MetaMut,
path: &OwnedPath,
operation: PatchOperationKind,
) -> Result<&'a mut dyn MetaMut, CommitOperationError> {
resolve_path(target, path).ok_or_else(|| CommitOperationError::PathNotFound {
path: path.clone(),
operation,
})
}
fn resolve_path<'a>(target: &'a mut dyn MetaMut, path: &OwnedPath) -> Option<&'a mut dyn MetaMut> {
let mut current = target;
for segment in path {
current = match segment {
PathSegment::Field(name) => current.field_mut_dyn(name)?.inner,
PathSegment::Variant(name) => select_variant(current, name)?,
PathSegment::Item(index) => current.item_mut_dyn(index)?.inner,
PathSegment::Key(key) => current.key_mut_dyn(key)?.inner,
};
}
Some(current)
}
fn build_container_error(
actual: ValueKind,
path: &OwnedPath,
operation: PatchOperationKind,
expected: ValueKind,
) -> CommitOperationError {
if actual == expected {
unsupported_error(path, operation)
} else {
CommitOperationError::ShapeMismatch {
path: path.clone(),
expected,
actual,
}
}
}
fn unsupported_error(path: &OwnedPath, operation: PatchOperationKind) -> CommitOperationError {
CommitOperationError::Unsupported {
path: path.clone(),
operation,
}
}
fn replacement_error(
path: &OwnedPath,
operation: PatchOperationKind,
value: &Object,
actual: TypeInfo,
) -> CommitOperationError {
let expected = value.type_info();
if expected != actual {
CommitOperationError::TypeMismatch {
path: path.clone(),
expected,
actual,
}
} else {
unsupported_error(path, operation)
}
}
fn apply_staged_operation(
target: &mut dyn MetaMut,
operation: OwnedPatchOperation,
) -> Result<OwnedPatchOperation, CommitOperationError> {
match operation {
OwnedPatchOperation::Set { path, value } => {
let operation_kind = PatchOperationKind::Set;
let child = resolve(target, &path, operation_kind)?;
match child.replace_dyn(value) {
Ok(previous) => Ok(OwnedPatchOperation::Set {
path,
value: previous,
}),
Err(value) => Err(replacement_error(
&path,
operation_kind,
&value,
child.type_info(),
)),
}
}
OwnedPatchOperation::InsertKey { path, key, value } => {
let operation_kind = PatchOperationKind::InsertKey;
let target = resolve(target, &path, operation_kind)?;
let ReflectMut::Map(map) = target.reflect_mut() else {
return Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Map,
));
};
let Some(child) = map.key_mut(&key) else {
map
.insert_key(&key, value)
.map_err(|_| unsupported_error(&path, operation_kind))?;
return Ok(OwnedPatchOperation::RemoveKey { path, key });
};
match child.inner.replace_dyn(value) {
Ok(previous) => Ok(OwnedPatchOperation::InsertKey {
path,
key,
value: previous,
}),
Err(value) => Err(replacement_error(
&path,
operation_kind,
&value,
child.type_info(),
)),
}
}
OwnedPatchOperation::RemoveKey { path, key } => {
let operation_kind = PatchOperationKind::RemoveKey;
let target = resolve(target, &path, operation_kind)?;
let ReflectMut::Map(map) = target.reflect_mut() else {
return Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Map,
));
};
match map.remove_key(&key) {
Some(previous) => Ok(OwnedPatchOperation::InsertKey {
path,
key,
value: previous,
}),
None => Err(CommitOperationError::PathNotFound {
path,
operation: operation_kind,
}),
}
}
OwnedPatchOperation::InsertItem { path, index, value } => {
let operation_kind = PatchOperationKind::InsertItem;
let target = resolve(target, &path, operation_kind)?;
with_sequence_mut!(
target.reflect_mut(),
|sequence| {
sequence
.insert_item(index, value)
.map_err(|_| unsupported_error(&path, operation_kind))?;
Ok(OwnedPatchOperation::RemoveItem { path, index })
},
{
Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Sequence,
))
}
)
}
OwnedPatchOperation::PushItem { path, value } => {
let operation_kind = PatchOperationKind::PushItem;
let target = resolve(target, &path, operation_kind)?;
with_sequence_mut!(
target.reflect_mut(),
|sequence| {
let index = sequence.len();
sequence
.push_item(value)
.map_err(|_| unsupported_error(&path, operation_kind))?;
Ok(OwnedPatchOperation::RemoveItem { path, index })
},
{
Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Sequence,
))
}
)
}
OwnedPatchOperation::RemoveItem { path, index } => {
let operation_kind = PatchOperationKind::RemoveItem;
let target = resolve(target, &path, operation_kind)?;
with_sequence_mut!(
target.reflect_mut(),
|sequence| {
match sequence.remove_item(index) {
Some(previous) => Ok(OwnedPatchOperation::InsertItem {
path,
index,
value: previous,
}),
None => Err(unsupported_error(&path, operation_kind)),
}
},
{
Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Sequence,
))
}
)
}
OwnedPatchOperation::MoveItem { path, from, to } => {
let operation_kind = PatchOperationKind::MoveItem;
let target = resolve(target, &path, operation_kind)?;
with_sequence_mut!(
target.reflect_mut(),
|sequence| {
let len = sequence.len();
if let Some(index) = move_index_out_of_bounds(from, to, len) {
return Err(CommitOperationError::IndexOutOfBounds {
path,
operation: operation_kind,
index,
len,
});
}
sequence.move_item(from, to).map_err(|error| match error {
MoveItemError::RestoreFailed => CommitOperationError::ItemLost {
path: path.clone(),
index: from,
},
_ => unsupported_error(&path, operation_kind),
})?;
let (from, to) = if from < to {
(to - 1, from)
} else {
(to, from + 1)
};
Ok(OwnedPatchOperation::MoveItem { path, from, to })
},
{
Err(build_container_error(
kind_of(target),
&path,
operation_kind,
ValueKind::Sequence,
))
}
)
}
}
}