Skip to main content

mahler_core/json/
operation.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4use super::patch::{AddOperation, PatchOperation, RemoveOperation, ReplaceOperation};
5use super::path::Path;
6use super::value::Value;
7
8use crate::serde::{ser::SerializeMap, Serialize, Serializer};
9
10/// An operation on the system state
11#[derive(Debug, PartialEq, Eq, Clone)]
12pub enum Operation {
13    Create { path: Path, value: Value },
14    Update { path: Path, value: Value },
15    Delete { path: Path },
16}
17
18impl Serialize for Operation {
19    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
20    where
21        S: Serializer,
22    {
23        use Operation::*;
24        match self {
25            Create { path, value } => {
26                let mut map = serializer.serialize_map(Some(3))?;
27                map.serialize_entry("op", "create")?;
28                map.serialize_entry("path", path)?;
29                map.serialize_entry("value", value)?;
30                map.end()
31            }
32            Update { path, value } => {
33                let mut map = serializer.serialize_map(Some(3))?;
34                map.serialize_entry("op", "update")?;
35                map.serialize_entry("path", path)?;
36                map.serialize_entry("value", value)?;
37                map.end()
38            }
39            Delete { path } => {
40                let mut map = serializer.serialize_map(Some(2))?;
41                map.serialize_entry("op", "delete")?;
42                map.serialize_entry("path", path)?;
43                map.end()
44            }
45        }
46    }
47}
48
49impl Operation {
50    /// Return the operation path
51    pub fn path(&self) -> &Path {
52        use Operation::*;
53        match *self {
54            Create { ref path, .. } => path,
55            Update { ref path, .. } => path,
56            Delete { ref path, .. } => path,
57        }
58    }
59}
60
61impl fmt::Display for Operation {
62    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
63        use Operation::*;
64
65        write!(fmt, "{{")?;
66        match *self {
67            Create {
68                ref path,
69                ref value,
70            } => {
71                write!(fmt, "\"op\": \"create\",")?;
72                write!(fmt, "\"path\": {path},")?;
73                write!(fmt, "\"value\": {value}")?;
74            }
75            Update {
76                ref path,
77                ref value,
78            } => {
79                write!(fmt, "\"op\": \"update\",")?;
80                write!(fmt, "\"path\": {path},")?;
81                write!(fmt, "\"value\": {value}")?;
82            }
83            Delete { ref path } => {
84                write!(fmt, "\"op\": \"delete\",")?;
85                write!(fmt, "\"path\": {path}")?;
86            }
87        }
88        write!(fmt, "}}")
89    }
90}
91
92impl From<PatchOperation> for Operation {
93    fn from(op: PatchOperation) -> Self {
94        match op {
95            PatchOperation::Add(AddOperation { path, value }) => Operation::Create {
96                path: Path::new(&path),
97                value,
98            },
99            PatchOperation::Replace(ReplaceOperation { path, value }) => Operation::Update {
100                path: Path::new(&path),
101                value,
102            },
103            PatchOperation::Remove(RemoveOperation { path }) => Operation::Delete {
104                path: Path::new(&path),
105            },
106            // move/copy/test are not supported, but it is not necessary as they are not
107            // never returned by json_patch::diff
108            _ => unreachable!("unsuppported operation {op}"),
109        }
110    }
111}
112
113impl PartialOrd for Operation {
114    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115        Some(self.cmp(other))
116    }
117}
118
119impl Ord for Operation {
120    fn cmp(&self, other: &Self) -> Ordering {
121        let thispath = self.path().as_ref();
122        let otherpath = other.path().as_ref();
123
124        // Order operations by path length
125        thispath
126            .count()
127            .cmp(&otherpath.count())
128            .then(thispath.cmp(otherpath))
129    }
130}
131
132/// Allows to match a specific operation, any operation or none
133#[derive(PartialEq, PartialOrd, Eq, Ord, Debug, Clone)]
134pub enum OperationMatcher {
135    /// Do not match any operation
136    None,
137    /// Match on any operation create/update/delete
138    Any,
139    /// Match a [remove](https://datatracker.ietf.org/doc/html/rfc6902#section-4.2) operation
140    /// (equivalent to [`Operation::Delete`])
141    Delete,
142    /// Match an [add](https://datatracker.ietf.org/doc/html/rfc6902#section-4.1) operation
143    /// (equivalent to [`Operation::Create`])
144    Create,
145    /// Match a [replace](https://datatracker.ietf.org/doc/html/rfc6902#section-4.3) operation
146    /// (equivalent to [`Operation::Update`])
147    Update,
148}
149
150impl OperationMatcher {
151    /// Returns true if the matcher allows the given operation
152    pub fn matches(&self, operation: &Operation) -> bool {
153        use OperationMatcher::*;
154        match *self {
155            None => false,
156            Any => true,
157            Create => matches!(operation, Operation::Create { .. }),
158            Update => matches!(operation, Operation::Update { .. }),
159            Delete => matches!(operation, Operation::Delete { .. }),
160        }
161    }
162
163    pub(crate) fn as_str(&self) -> &'static str {
164        use OperationMatcher::*;
165        match *self {
166            None => "none",
167            Any => "any",
168            Update => "update",
169            Create => "create",
170            Delete => "delete",
171        }
172    }
173}
174
175impl fmt::Display for OperationMatcher {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        self.as_str().fmt(f)
178    }
179}