use crate::path::OwnedPath;
use crate::reflect::with_patch_sequence_mut;
use crate::{
Meta, MetaMut, MoveItemError, Object, ObjectRefMut, PathSegment, ReflectMut, TypeInfo, ValueKind,
};
#[allow(unused_imports)]
use crate::{MutationBatch, TypedMapAccessMut};
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug)]
pub enum PatchOperation<'a> {
Set {
path: Vec<PathSegment<'a>>,
value: Object,
},
InsertKey {
path: Vec<PathSegment<'a>>,
key: String,
value: Object,
},
RemoveKey {
path: Vec<PathSegment<'a>>,
key: String,
},
InsertItem {
path: Vec<PathSegment<'a>>,
index: usize,
value: Object,
},
PushItem {
path: Vec<PathSegment<'a>>,
value: Object,
},
RemoveItem {
path: Vec<PathSegment<'a>>,
index: usize,
},
MoveItem {
path: Vec<PathSegment<'a>>,
from: usize,
to: usize,
},
}
#[derive(Debug)]
pub enum OwnedPatchOperation {
Set {
path: OwnedPath,
value: Object,
},
InsertKey {
path: OwnedPath,
key: String,
value: Object,
},
RemoveKey {
path: OwnedPath,
key: String,
},
InsertItem {
path: OwnedPath,
index: usize,
value: Object,
},
PushItem {
path: OwnedPath,
value: Object,
},
RemoveItem {
path: OwnedPath,
index: usize,
},
MoveItem {
path: OwnedPath,
from: usize,
to: usize,
},
}
impl OwnedPatchOperation {
pub fn set<'a>(path: impl IntoIterator<Item = PathSegment<'a>>, value: Object) -> Self {
Self::Set {
path: path.into_iter().collect(),
value,
}
}
pub fn insert_key<'a>(
path: impl IntoIterator<Item = PathSegment<'a>>,
key: impl Into<String>,
value: Object,
) -> Self {
Self::InsertKey {
path: path.into_iter().collect(),
key: key.into(),
value,
}
}
pub fn remove_key<'a>(
path: impl IntoIterator<Item = PathSegment<'a>>,
key: impl Into<String>,
) -> Self {
Self::RemoveKey {
path: path.into_iter().collect(),
key: key.into(),
}
}
pub fn insert_item<'a>(
path: impl IntoIterator<Item = PathSegment<'a>>,
index: usize,
value: Object,
) -> Self {
Self::InsertItem {
path: path.into_iter().collect(),
index,
value,
}
}
pub fn push_item<'a>(path: impl IntoIterator<Item = PathSegment<'a>>, value: Object) -> Self {
Self::PushItem {
path: path.into_iter().collect(),
value,
}
}
pub fn remove_item<'a>(path: impl IntoIterator<Item = PathSegment<'a>>, index: usize) -> Self {
Self::RemoveItem {
path: path.into_iter().collect(),
index,
}
}
pub fn move_item<'a>(
path: impl IntoIterator<Item = PathSegment<'a>>,
from: usize,
to: usize,
) -> Self {
Self::MoveItem {
path: path.into_iter().collect(),
from,
to,
}
}
}
impl<'a> From<PatchOperation<'a>> for OwnedPatchOperation {
fn from(operation: PatchOperation<'a>) -> Self {
match operation {
PatchOperation::Set { path, value } => Self::Set {
path: OwnedPath::from(path.as_slice()),
value,
},
PatchOperation::InsertKey { path, key, value } => Self::InsertKey {
path: OwnedPath::from(path.as_slice()),
key,
value,
},
PatchOperation::RemoveKey { path, key } => Self::RemoveKey {
path: OwnedPath::from(path.as_slice()),
key,
},
PatchOperation::InsertItem { path, index, value } => Self::InsertItem {
path: OwnedPath::from(path.as_slice()),
index,
value,
},
PatchOperation::PushItem { path, value } => Self::PushItem {
path: OwnedPath::from(path.as_slice()),
value,
},
PatchOperation::RemoveItem { path, index } => Self::RemoveItem {
path: OwnedPath::from(path.as_slice()),
index,
},
PatchOperation::MoveItem { path, from, to } => Self::MoveItem {
path: OwnedPath::from(path.as_slice()),
from,
to,
},
}
}
}
impl<'a> PatchOperation<'a> {
pub fn set(path: impl IntoIterator<Item = PathSegment<'a>>, value: impl Meta + 'static) -> Self {
Self::Set {
path: path.into_iter().collect(),
value: Object::new(value),
}
}
pub fn insert_key(
path: impl IntoIterator<Item = PathSegment<'a>>,
key: impl Into<String>,
value: impl Meta + 'static,
) -> Self {
Self::InsertKey {
path: path.into_iter().collect(),
key: key.into(),
value: Object::new(value),
}
}
pub fn remove_key(
path: impl IntoIterator<Item = PathSegment<'a>>,
key: impl Into<String>,
) -> Self {
Self::RemoveKey {
path: path.into_iter().collect(),
key: key.into(),
}
}
pub fn insert_item(
path: impl IntoIterator<Item = PathSegment<'a>>,
index: usize,
value: impl Meta + 'static,
) -> Self {
Self::InsertItem {
path: path.into_iter().collect(),
index,
value: Object::new(value),
}
}
pub fn push_item(
path: impl IntoIterator<Item = PathSegment<'a>>,
value: impl Meta + 'static,
) -> Self {
Self::PushItem {
path: path.into_iter().collect(),
value: Object::new(value),
}
}
pub fn remove_item(path: impl IntoIterator<Item = PathSegment<'a>>, index: usize) -> Self {
Self::RemoveItem {
path: path.into_iter().collect(),
index,
}
}
pub fn move_item(
path: impl IntoIterator<Item = PathSegment<'a>>,
from: usize,
to: usize,
) -> Self {
Self::MoveItem {
path: path.into_iter().collect(),
from,
to,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatchOperationKind {
Set,
InsertKey,
RemoveKey,
InsertItem,
PushItem,
RemoveItem,
MoveItem,
}
impl fmt::Display for PatchOperationKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Set => "set",
Self::InsertKey => "insert key",
Self::RemoveKey => "remove key",
Self::InsertItem => "insert item",
Self::PushItem => "push item",
Self::RemoveItem => "remove item",
Self::MoveItem => "move item",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApplyError<'a> {
PathNotFound {
path: Vec<PathSegment<'a>>,
operation: PatchOperationKind,
},
ShapeMismatch {
path: Vec<PathSegment<'a>>,
expected: ValueKind,
actual: ValueKind,
},
TypeMismatch {
path: Vec<PathSegment<'a>>,
expected: TypeInfo,
actual: TypeInfo,
},
IndexOutOfBounds {
path: Vec<PathSegment<'a>>,
operation: PatchOperationKind,
index: usize,
len: usize,
},
Unsupported {
path: Vec<PathSegment<'a>>,
operation: PatchOperationKind,
},
ItemLost {
path: Vec<PathSegment<'a>>,
index: usize,
},
}
impl fmt::Display for ApplyError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PathNotFound { operation, .. } => {
write!(
f,
"Reflective patch operation {operation} targets a path that does not exist"
)
}
Self::ShapeMismatch {
expected, actual, ..
} => {
write!(
f,
"Reflective patch expected {expected:?}, found {actual:?}"
)
}
Self::TypeMismatch {
expected, actual, ..
} => write!(
f,
"Reflective patch expected {}, found {}",
expected.name(),
actual.name()
),
Self::IndexOutOfBounds {
operation,
index,
len,
..
} => write!(
f,
"Reflective patch operation {operation} received index {index} for a sequence of length {len}"
),
Self::Unsupported { operation, .. } => {
write!(f, "Reflective patch operation {operation} is unsupported")
}
Self::ItemLost { index, .. } => write!(
f,
"Reflective patch could not restore the item moved from index {index}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ApplyError<'_> {}
#[inline]
fn resolve_target<'a, 'p>(
root: &'a mut dyn MetaMut,
path: Vec<PathSegment<'p>>,
operation: PatchOperationKind,
) -> Result<(&'a mut dyn MetaMut, Vec<PathSegment<'p>>), ApplyError<'p>> {
match ObjectRefMut::path_from(root, &path) {
Some(target) => Ok((target.inner, path)),
None => Err(ApplyError::PathNotFound { path, operation }),
}
}
#[inline(never)]
pub(crate) fn kind_of(target: &dyn MetaMut) -> ValueKind {
target.reflect().kind()
}
fn build_container_error<'p>(
actual: ValueKind,
path: Vec<PathSegment<'p>>,
operation: PatchOperationKind,
expected: ValueKind,
) -> ApplyError<'p> {
if actual == expected {
ApplyError::Unsupported { path, operation }
} else {
ApplyError::ShapeMismatch {
path,
expected,
actual,
}
}
}
fn unsupported<'p, T, E>(
result: Result<T, E>,
path: Vec<PathSegment<'p>>,
operation: PatchOperationKind,
) -> Result<T, ApplyError<'p>> {
result.map_err(|_| ApplyError::Unsupported { path, operation })
}
#[inline(always)]
fn apply_operation<'p>(
root: &mut dyn MetaMut,
operation: PatchOperation<'p>,
) -> Result<(), ApplyError<'p>> {
match operation {
PatchOperation::Set { path, value } => {
let operation = PatchOperationKind::Set;
let (target, path) = resolve_target(root, path, operation)?;
let expected = value.type_info();
let actual = target.type_info();
if expected != actual {
return Err(ApplyError::TypeMismatch {
path,
expected,
actual,
});
}
unsupported(target.set_dyn(value), path, operation)?;
}
PatchOperation::InsertKey { path, key, value } => {
let operation = PatchOperationKind::InsertKey;
let (target, path) = resolve_target(root, path, operation)?;
let ReflectMut::Map(map) = target.reflect_mut() else {
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Map,
));
};
unsupported(map.insert_key(&key, value), path, operation)?;
}
PatchOperation::RemoveKey { path, key } => {
let operation = PatchOperationKind::RemoveKey;
let (target, path) = resolve_target(root, path, operation)?;
let ReflectMut::Map(map) = target.reflect_mut() else {
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Map,
));
};
unsupported(map.remove_key(&key).ok_or(()), path, operation)?;
}
PatchOperation::InsertItem { path, index, value } => {
let operation = PatchOperationKind::InsertItem;
let (target, path) = resolve_target(root, path, operation)?;
with_patch_sequence_mut!(
target.reflect_mut(),
|sequence| {
unsupported(sequence.insert_item(index, value), path, operation)?;
},
{
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Sequence,
));
}
)
}
PatchOperation::PushItem { path, value } => {
let operation = PatchOperationKind::PushItem;
let (target, path) = resolve_target(root, path, operation)?;
with_patch_sequence_mut!(
target.reflect_mut(),
|sequence| {
unsupported(sequence.push_item(value), path, operation)?;
},
{
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Sequence,
));
}
)
}
PatchOperation::RemoveItem { path, index } => {
let operation = PatchOperationKind::RemoveItem;
let (target, path) = resolve_target(root, path, operation)?;
with_patch_sequence_mut!(
target.reflect_mut(),
|sequence| {
unsupported(sequence.remove_item(index).ok_or(()), path, operation)?;
},
{
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Sequence,
));
}
)
}
PatchOperation::MoveItem { path, from, to } => {
let operation = PatchOperationKind::MoveItem;
let (target, path) = resolve_target(root, path, operation)?;
with_patch_sequence_mut!(
target.reflect_mut(),
|sequence| {
let len = sequence.len();
if let Some(index) = move_index_out_of_bounds(from, to, len) {
return Err(ApplyError::IndexOutOfBounds {
path,
operation,
index,
len,
});
}
sequence.move_item(from, to).map_err(|error| match error {
MoveItemError::RestoreFailed => ApplyError::ItemLost { path, index: from },
_ => ApplyError::Unsupported { path, operation },
})?;
},
{
return Err(build_container_error(
kind_of(target),
path,
operation,
ValueKind::Sequence,
));
}
)
}
}
Ok(())
}
pub(crate) fn move_index_out_of_bounds(from: usize, to: usize, len: usize) -> Option<usize> {
if from >= len {
Some(from)
} else if to > len {
Some(to)
} else {
None
}
}
pub(crate) fn apply_patch<'p, I>(
root: &mut dyn MetaMut,
operations: I,
) -> Result<(), ApplyError<'p>>
where
I: IntoIterator<Item = PatchOperation<'p>>,
{
for operation in operations {
apply_operation(root, operation)?;
}
Ok(())
}