-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and create worker target struct
Change-type: minor
- Loading branch information
Showing
9 changed files
with
274 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ pub mod error; | |
pub mod extract; | ||
pub mod system; | ||
pub mod task; | ||
pub mod worker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mod domain; | ||
mod intent; | ||
mod target; | ||
|
||
pub use domain::*; | ||
pub use intent::*; | ||
pub use target::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
use json_patch::{diff, Patch, PatchOperation, RemoveOperation, ReplaceOperation}; | ||
use jsonptr::Pointer; | ||
use serde::Serialize; | ||
use serde_json::Value; | ||
use std::{ | ||
cmp::Ordering, | ||
collections::{BTreeSet, LinkedList}, | ||
fmt::{self, Display}, | ||
ops::Deref, | ||
}; | ||
|
||
pub struct Target(Value); | ||
|
||
#[derive(Debug)] | ||
pub enum TargetError { | ||
SerializationFailed(serde_json::error::Error), | ||
} | ||
|
||
impl std::error::Error for TargetError {} | ||
|
||
impl Display for TargetError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
TargetError::SerializationFailed(err) => err.fmt(f), | ||
} | ||
} | ||
} | ||
|
||
#[derive(PartialEq, Eq, Debug, Clone)] | ||
pub(crate) struct Operation(PatchOperation); | ||
|
||
impl PartialOrd for Operation { | ||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||
Some(self.cmp(other)) | ||
} | ||
} | ||
|
||
impl Ord for Operation { | ||
fn cmp(&self, other: &Self) -> Ordering { | ||
let thispath = self.0.path(); | ||
let otherpath = other.0.path(); | ||
|
||
// Order operations by path length | ||
thispath | ||
.count() | ||
.cmp(&otherpath.count()) | ||
.then(thispath.cmp(otherpath)) | ||
} | ||
} | ||
|
||
impl From<PatchOperation> for Operation { | ||
fn from(op: PatchOperation) -> Operation { | ||
Operation(op) | ||
} | ||
} | ||
|
||
pub(crate) struct Distance(BTreeSet<Operation>); | ||
|
||
impl Distance { | ||
fn new() -> Self { | ||
Distance(BTreeSet::new()) | ||
} | ||
|
||
fn insert(&mut self, o: Operation) { | ||
self.0.insert(o); | ||
} | ||
|
||
fn iter(&self) -> impl Iterator<Item = &Operation> { | ||
self.0.iter() | ||
} | ||
|
||
fn add_removes_for_children(&mut self, path: &Pointer, value: &Value) { | ||
let mut queue = LinkedList::new(); | ||
queue.push_back((path.to_buf(), value)); | ||
|
||
while let Some((path, value)) = queue.pop_front() { | ||
if value.is_object() { | ||
let obj = value.as_object().unwrap(); | ||
for (k, v) in obj.iter() { | ||
let path = path.concat(Pointer::parse(&format!("/{}", k)).unwrap()); | ||
// Insert a replace operation for each child | ||
self.insert(Operation::from(PatchOperation::Remove(RemoveOperation { | ||
path: path.clone(), | ||
}))); | ||
|
||
// Append the value to the queue | ||
queue.push_back((path, v)); | ||
} | ||
} | ||
|
||
if value.is_array() { | ||
let obj = value.as_array().unwrap(); | ||
for (k, v) in obj.iter().enumerate() { | ||
let path = path.concat(Pointer::parse(&format!("/{}", k)).unwrap()); | ||
|
||
// Insert a replace operation for each child | ||
self.insert(Operation::from(PatchOperation::Remove(RemoveOperation { | ||
path: path.clone(), | ||
}))); | ||
|
||
// Append the value to the queue | ||
queue.push_back((path, v)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Target { | ||
pub fn new(target: impl Into<Value>) -> Self { | ||
Self(target.into()) | ||
} | ||
|
||
pub fn from<S: Serialize>(state: S) -> Result<Self, TargetError> { | ||
let state = serde_json::to_value(state).map_err(TargetError::SerializationFailed)?; | ||
Ok(Target::new(state)) | ||
} | ||
|
||
pub(crate) fn distance(&self, state: &Value) -> Distance { | ||
let mut distance = Distance::new(); | ||
|
||
// calculate differences between the system root and | ||
// the target | ||
let Patch(changes) = diff(state, self); | ||
for op in changes { | ||
// For every operation on the list of changes | ||
let path = op.path(); | ||
|
||
let mut parent = path; | ||
|
||
// get all paths up to the root | ||
while let Some(newparent) = parent.parent() { | ||
// get the target at the parent to use as value | ||
// no matter the operation, the parent of the target shoul | ||
// always exist. If it doesn't there is a bug (probbly in jsonptr) | ||
let value = newparent.resolve(&self.0).unwrap_or_else(|e| { | ||
panic!( | ||
"[BUG] Path `{}` should be resolvable on the target, but got error: {}", | ||
newparent, e | ||
) | ||
}); | ||
|
||
// Insert a replace operation for each one | ||
distance.insert(Operation::from(PatchOperation::Replace(ReplaceOperation { | ||
path: newparent.to_buf(), | ||
value: value.clone(), | ||
}))); | ||
|
||
parent = newparent; | ||
} | ||
|
||
// for every delete operation '/a/b/c', add child | ||
// nodes of the deleted path with 'remove' operation | ||
if let PatchOperation::Remove(_) = op { | ||
// By definition of the remove operation the path should be | ||
// resolvable on the left side of the diff | ||
let value = path.resolve(state).unwrap_or_else(|e| { | ||
panic!( | ||
"[BUG] Path `{}` should be resolvable on the state, but got error: {}", | ||
path, e | ||
) | ||
}); | ||
|
||
// | ||
distance.add_removes_for_children(path, value); | ||
} | ||
|
||
// Finally insert the actual operation | ||
distance.insert(Operation::from(op)); | ||
} | ||
|
||
distance | ||
} | ||
} | ||
|
||
impl Deref for Target { | ||
type Target = Value; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use serde_json::json; | ||
|
||
fn distance_eq(src: Value, tgt: Value, result: Vec<Value>) { | ||
let target = Target::new(tgt); | ||
let distance = target.distance(&src); | ||
|
||
// Serialize results to make comparison easier | ||
let ops: Vec<Value> = distance | ||
.iter() | ||
.map(|Operation(o)| serde_json::to_value(o).unwrap()) | ||
.collect(); | ||
|
||
assert_eq!(ops, result) | ||
} | ||
|
||
#[test] | ||
fn calculates_possible_changes_to_target() { | ||
distance_eq( | ||
json!({"a": 1, "b": "one", "c": {"k": "v"}}), | ||
json!({"a": 2, "b": "one", "c": {}}), | ||
vec![ | ||
json!({"op": "replace", "path": "", "value": {"a": 2, "b": "one", "c": {}}}), | ||
json!({"op": "replace", "path": "/a", "value": 2}), | ||
json!({"op": "replace", "path": "/c", "value": {}}), | ||
json!({"op": "remove", "path": "/c/k"}), | ||
], | ||
); | ||
distance_eq( | ||
json!({"a": 1, "b": "one", "c": {"k": "v"}}), | ||
json!({"a": 2}), | ||
vec![ | ||
json!({"op": "replace", "path": "", "value": {"a": 2}}), | ||
json!({"op": "replace", "path": "/a", "value": 2}), | ||
json!({"op": "remove", "path": "/b"}), | ||
json!({"op": "remove", "path": "/c"}), | ||
json!({"op": "remove", "path": "/c/k"}), | ||
], | ||
); | ||
distance_eq( | ||
json!({"a": 1, "b": "one", "c": {"k": "v"}}), | ||
json!({"a": 2, "b": "two", "c": {"k": "v"}}), | ||
vec![ | ||
json!({"op": "replace", "path": "", "value": {"a": 2, "b": "two", "c": {"k": "v"}}}), | ||
json!({"op": "replace", "path": "/a", "value": 2}), | ||
json!({"op": "replace", "path": "/b", "value": "two"}), | ||
], | ||
); | ||
distance_eq( | ||
json!({"a": {"b": {"c": {"d": "e"}}}}), | ||
json!({"a": {"b": {}}}), | ||
vec![ | ||
json!({"op": "replace", "path": "", "value": {"a": {"b": {}}}}), | ||
json!({"op": "replace", "path": "/a", "value": {"b": {}}}), | ||
json!({"op": "replace", "path": "/a/b", "value": {}}), | ||
json!({"op": "remove", "path": "/a/b/c"}), | ||
json!({"op": "remove", "path": "/a/b/c/d"}), | ||
], | ||
); | ||
} | ||
} |