use crate::path::OwnedPath;
use crate::{
Meta, MetaMut, MoveItemError, Object, ObjectRefMut, PathSegment, ReflectiveError, 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.type_name(),
actual.type_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<'_> {}
pub(crate) trait PatchTarget: Sized {
fn type_info(&self) -> TypeInfo;
fn kind(&self) -> ValueKind;
fn len(&self) -> Option<usize>;
fn set(self, value: Object) -> Result<(), ReflectiveError>;
fn insert_key(&mut self, key: &str, value: Object) -> Result<(), ReflectiveError>;
fn insert_item(&mut self, index: usize, value: Object) -> Result<(), ReflectiveError>;
fn push_item(&mut self, value: Object) -> Result<(), ReflectiveError>;
fn remove_key(&mut self, key: &str) -> Result<Object, ReflectiveError>;
fn remove_item(&mut self, index: usize) -> Result<Object, ReflectiveError>;
fn move_item(&mut self, from: usize, to: usize) -> Result<(), MoveItemError>;
}
impl PatchTarget for ObjectRefMut<'_> {
fn type_info(&self) -> TypeInfo {
Self::type_info(self)
}
fn kind(&self) -> ValueKind {
Self::kind(self)
}
fn len(&self) -> Option<usize> {
Self::len(self)
}
fn set(self, value: Object) -> Result<(), ReflectiveError> {
Self::set(self, value)
}
fn insert_key(&mut self, key: &str, value: Object) -> Result<(), ReflectiveError> {
Self::insert_key(self, key, value).map(|_| ())
}
fn insert_item(&mut self, index: usize, value: Object) -> Result<(), ReflectiveError> {
Self::insert_item(self, index, value).map(|_| ())
}
fn push_item(&mut self, value: Object) -> Result<(), ReflectiveError> {
Self::push_item(self, value).map(|_| ())
}
fn remove_key(&mut self, key: &str) -> Result<Object, ReflectiveError> {
Self::remove_key(self, key)
}
fn remove_item(&mut self, index: usize) -> Result<Object, ReflectiveError> {
Self::remove_item(self, index)
}
fn move_item(&mut self, from: usize, to: usize) -> Result<(), MoveItemError> {
self.inner.move_item(from, to)
}
}
pub(crate) trait PatchResolver {
type Target<'b>: PatchTarget
where
Self: 'b;
fn resolve<'b>(&'b mut self, path: &[PathSegment<'_>]) -> Option<Self::Target<'b>>;
}
impl PatchResolver for dyn MetaMut + '_ {
type Target<'b>
= ObjectRefMut<'b>
where
Self: 'b;
fn resolve<'b>(&'b mut self, path: &[PathSegment<'_>]) -> Option<ObjectRefMut<'b>> {
ObjectRefMut::path_from(self, path)
}
}
fn resolve_target<'a, 'p, R>(
root: &'a mut R,
path: Vec<PathSegment<'p>>,
operation: PatchOperationKind,
) -> Result<(R::Target<'a>, Vec<PathSegment<'p>>), ApplyError<'p>>
where
R: PatchResolver + ?Sized + 'a,
{
let target = match root.resolve(&path) {
Some(target) => target,
None => {
return Err(ApplyError::PathNotFound { path, operation });
}
};
Ok((target, path))
}
fn resolve_container<'a, 'p, R>(
root: &'a mut R,
path: Vec<PathSegment<'p>>,
operation: PatchOperationKind,
expected: ValueKind,
) -> Result<(R::Target<'a>, Vec<PathSegment<'p>>), ApplyError<'p>>
where
R: PatchResolver + ?Sized + 'a,
{
let (target, path) = resolve_target(root, path, operation)?;
let actual = target.kind();
if actual != expected {
return Err(ApplyError::ShapeMismatch {
path,
expected,
actual,
});
}
Ok((target, path))
}
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 })
}
fn apply_operation<'p, R: PatchResolver + ?Sized>(
root: &mut R,
operation: PatchOperation<'p>,
) -> Result<(), ApplyError<'p>> {
match operation {
PatchOperation::Set { path, value } => {
let (child, path) = resolve_target(root, path, PatchOperationKind::Set)?;
let expected = value.type_info();
let actual = child.type_info();
if expected != actual {
return Err(ApplyError::TypeMismatch {
path,
expected,
actual,
});
}
unsupported(child.set(value), path, PatchOperationKind::Set)?;
}
PatchOperation::InsertKey { path, key, value } => {
let (mut map, path) =
resolve_container(root, path, PatchOperationKind::InsertKey, ValueKind::Map)?;
unsupported(
map.insert_key(&key, value),
path,
PatchOperationKind::InsertKey,
)?;
}
PatchOperation::RemoveKey { path, key } => {
let (mut map, path) =
resolve_container(root, path, PatchOperationKind::RemoveKey, ValueKind::Map)?;
unsupported(map.remove_key(&key), path, PatchOperationKind::RemoveKey)?;
}
PatchOperation::InsertItem { path, index, value } => {
let (mut sequence, path) = resolve_container(
root,
path,
PatchOperationKind::InsertItem,
ValueKind::Sequence,
)?;
unsupported(
sequence.insert_item(index, value),
path,
PatchOperationKind::InsertItem,
)?;
}
PatchOperation::PushItem { path, value } => {
let (mut sequence, path) = resolve_container(
root,
path,
PatchOperationKind::PushItem,
ValueKind::Sequence,
)?;
unsupported(
sequence.push_item(value),
path,
PatchOperationKind::PushItem,
)?;
}
PatchOperation::RemoveItem { path, index } => {
let (mut sequence, path) = resolve_container(
root,
path,
PatchOperationKind::RemoveItem,
ValueKind::Sequence,
)?;
unsupported(
sequence.remove_item(index),
path,
PatchOperationKind::RemoveItem,
)?;
}
PatchOperation::MoveItem { path, from, to } => {
let (mut sequence, path) = resolve_container(
root,
path,
PatchOperationKind::MoveItem,
ValueKind::Sequence,
)?;
let Some(len) = sequence.len() else {
return Err(ApplyError::Unsupported {
path,
operation: PatchOperationKind::MoveItem,
});
};
if let Some(index) = move_index_out_of_bounds(from, to, len) {
return Err(ApplyError::IndexOutOfBounds {
path,
operation: PatchOperationKind::MoveItem,
index,
len,
});
}
sequence.move_item(from, to).map_err(|error| match error {
MoveItemError::RestoreFailed => ApplyError::ItemLost { path, index: from },
_ => ApplyError::Unsupported {
path,
operation: PatchOperationKind::MoveItem,
},
})?;
}
}
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, R, I>(root: &mut R, operations: I) -> Result<(), ApplyError<'p>>
where
R: PatchResolver + ?Sized,
I: IntoIterator<Item = PatchOperation<'p>>,
{
for operation in operations {
apply_operation(root, operation)?;
}
Ok(())
}