deepdiff-rs 0.5.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/Cargo.lock +3 -3
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/Cargo.toml +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/PKG-INFO +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/array.rs +7 -7
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/dispatch.rs +4 -4
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/object.rs +3 -3
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/scalar.rs +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/tests.rs +12 -11
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/distance.rs +10 -10
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/fxhash.rs +4 -4
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/hash.rs +4 -4
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/memo.rs +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/mod.rs +7 -7
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/pairing.rs +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/lcs.rs +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/report.rs +1 -1
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/README.md +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/Cargo.toml +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/examples/stack_frame_cost.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/datetime.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/datetime_tests.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/mod.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/options.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/diff/set.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/error.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/ignore_order/tests.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/lcs_tests.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/lib.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/path.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/report_tests.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/test_support.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/value.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/src/value_tests.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/tests/golden.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/tests/ignore_order_memory.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/tests/memory_footprint.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/tests/proptest_diff.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-core/tests/proptest_ignore_order.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/Cargo.toml +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/benchmarks/bench_bindings.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/convert.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/deepdiff.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/errors.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/fast_path.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/guard.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/src/lib.rs +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_bindings_memory.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_conversions.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_datetimes.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_depth_guard.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_differential_fuzz.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_golden_parity.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_sets.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_signed_zero.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_smoke.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_suite_hygiene.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/crates/onix-py/tests/test_tuples.py +0 -0
- {deepdiff_rs-0.5.1 → deepdiff_rs-0.5.3}/pyproject.toml +0 -0
|
@@ -127,7 +127,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
127
127
|
|
|
128
128
|
[[package]]
|
|
129
129
|
name = "onix-cli"
|
|
130
|
-
version = "0.5.
|
|
130
|
+
version = "0.5.3"
|
|
131
131
|
dependencies = [
|
|
132
132
|
"onix-core",
|
|
133
133
|
"serde_json",
|
|
@@ -135,7 +135,7 @@ dependencies = [
|
|
|
135
135
|
|
|
136
136
|
[[package]]
|
|
137
137
|
name = "onix-core"
|
|
138
|
-
version = "0.5.
|
|
138
|
+
version = "0.5.3"
|
|
139
139
|
dependencies = [
|
|
140
140
|
"proptest",
|
|
141
141
|
"serde",
|
|
@@ -145,7 +145,7 @@ dependencies = [
|
|
|
145
145
|
|
|
146
146
|
[[package]]
|
|
147
147
|
name = "onix-py"
|
|
148
|
-
version = "0.5.
|
|
148
|
+
version = "0.5.3"
|
|
149
149
|
dependencies = [
|
|
150
150
|
"onix-core",
|
|
151
151
|
"pyo3",
|
|
@@ -133,7 +133,7 @@ struct LcsPair<'a> {
|
|
|
133
133
|
/// function, and must record *nothing* — which is also what keeps
|
|
134
134
|
/// [`lcs_or_positional_array_diff`]'s finding-count comparison matching
|
|
135
135
|
/// `DeepDiff`'s. That branch is also where the pair picks up the
|
|
136
|
-
/// UTC-normalized rendering [`datetime_diff`] documents.
|
|
136
|
+
/// UTC-normalized rendering [`datetime_diff`](super::datetime_diff) documents.
|
|
137
137
|
///
|
|
138
138
|
/// Checks [`check_traversal_depth`] at `depth + 1` (this pair's own path
|
|
139
139
|
/// depth) before recording anything — the same bound
|
|
@@ -312,7 +312,7 @@ fn lcs_array_diff(
|
|
|
312
312
|
/// own internal dispatch one level deeper with the index appended to the
|
|
313
313
|
/// path (so a changed, type-changed, or further-nested-container
|
|
314
314
|
/// difference at that index surfaces with its own deep path, exactly like
|
|
315
|
-
/// [`object_diff`]'s shared-key recursion). Once the shorter list is
|
|
315
|
+
/// [`object_diff`](super::object_diff)'s shared-key recursion). Once the shorter list is
|
|
316
316
|
/// exhausted, the longer list's surplus tail becomes
|
|
317
317
|
/// `iterable_item_removed` findings (if `a` is longer) or
|
|
318
318
|
/// `iterable_item_added` findings (if `b` is longer), one per surplus index,
|
|
@@ -325,15 +325,15 @@ fn lcs_array_diff(
|
|
|
325
325
|
/// against [`lcs_array_diff`]'s result otherwise — see [`array_diff`]'s
|
|
326
326
|
/// doc.
|
|
327
327
|
///
|
|
328
|
-
/// Like [`object_diff`], this always recurses into same-index pairs rather
|
|
329
|
-
/// than re-checking [`values_equal`] first — see that function's doc for why
|
|
330
|
-
/// the single top-level equality check in [`diff_with_max_depth`] is enough.
|
|
328
|
+
/// Like [`object_diff`](super::object_diff), this always recurses into same-index pairs rather
|
|
329
|
+
/// than re-checking [`values_equal`](super::values_equal) first — see that function's doc for why
|
|
330
|
+
/// the single top-level equality check in [`diff_with_max_depth`](super::diff_with_max_depth) is enough.
|
|
331
331
|
///
|
|
332
332
|
/// Every surplus-tail clone is checked with [`check_value_depth`] first,
|
|
333
|
-
/// exactly like [`object_diff`]'s added/removed leaf clones: a surplus
|
|
333
|
+
/// exactly like [`object_diff`](super::object_diff)'s added/removed leaf clones: a surplus
|
|
334
334
|
/// element can itself be an arbitrarily deep value, so the same combined
|
|
335
335
|
/// path-depth-plus-value-depth budget applies here too (see
|
|
336
|
-
/// [`diff_with_max_depth`]'s doc for the full contract).
|
|
336
|
+
/// [`diff_with_max_depth`](super::diff_with_max_depth)'s doc for the full contract).
|
|
337
337
|
///
|
|
338
338
|
/// `path` is the single buffer shared across the whole traversal (see
|
|
339
339
|
/// [`diff_at`]'s doc): each iteration below runs its work through
|
|
@@ -18,7 +18,7 @@ use super::{
|
|
|
18
18
|
type_change_report,
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
/// The recursive core of [`diff_with_max_depth()`]: identical dispatch, but
|
|
21
|
+
/// The recursive core of [`diff_with_max_depth()`](super::diff_with_max_depth): identical dispatch, but
|
|
22
22
|
/// carrying the path and depth accumulated so far, so that nested findings
|
|
23
23
|
/// get their full deep path and the recursion-depth bound can be enforced.
|
|
24
24
|
///
|
|
@@ -153,7 +153,7 @@ pub(crate) fn deeper_than(value: &Value, limit: usize) -> bool {
|
|
|
153
153
|
/// closes: a value could be accepted at up to `max_depth` deep *in addition
|
|
154
154
|
/// to* however deep the traversal already was, for a combined worst case of
|
|
155
155
|
/// roughly `2 * max_depth` native frames at the `.clone()` call — see
|
|
156
|
-
/// [`diff_with_max_depth`]'s doc for the full contract this restores.
|
|
156
|
+
/// [`diff_with_max_depth`](super::diff_with_max_depth)'s doc for the full contract this restores.
|
|
157
157
|
pub(crate) fn check_value_depth(
|
|
158
158
|
path: &[PathSegment],
|
|
159
159
|
value: &Value,
|
|
@@ -217,10 +217,10 @@ pub(crate) fn check_map_depth(
|
|
|
217
217
|
/// `max_depth`, `Ok(())` otherwise.
|
|
218
218
|
///
|
|
219
219
|
/// This is [`diff_at`]'s own top check, factored out so
|
|
220
|
-
///
|
|
220
|
+
/// `insert_lcs_pair_finding` can enforce the *exact same* bound for an
|
|
221
221
|
/// LCS `'replace'`-opcode pairwise comparison as `diff_at` would have
|
|
222
222
|
/// enforced had the pair been reached by ordinary recursion instead (which
|
|
223
|
-
/// is what
|
|
223
|
+
/// is what `positional_array_diff`'s equivalent same-index pairs go
|
|
224
224
|
/// through) — see [`array_diff`]'s module-level "List diffing" doc section.
|
|
225
225
|
/// Deliberately distinct from [`check_value_depth`]: that function bounds a
|
|
226
226
|
/// *value's own nesting* combined with the remaining budget, and is a
|
|
@@ -22,8 +22,8 @@ use super::{DiffOptions, check_map_depth, check_value_depth, diff_at, scoped};
|
|
|
22
22
|
/// finding (via `array_diff`), all surface with their own deep path.
|
|
23
23
|
///
|
|
24
24
|
/// This always recurses, even for a key whose value is unchanged — it does
|
|
25
|
-
/// not re-check [`values_equal`] per key. That single top-level check in
|
|
26
|
-
/// [`diff_with_max_depth`] is enough for its documented guarantee (fully
|
|
25
|
+
/// not re-check [`values_equal`](super::values_equal) per key. That single top-level check in
|
|
26
|
+
/// [`diff_with_max_depth`](super::diff_with_max_depth) is enough for its documented guarantee (fully
|
|
27
27
|
/// equal *whole* inputs never hit the bound); re-running it per key would
|
|
28
28
|
/// only rescue one specific edge case (an equal subtree nested arbitrarily
|
|
29
29
|
/// deep under an unrelated shallow change) at the cost of an extra full
|
|
@@ -49,7 +49,7 @@ use super::{DiffOptions, check_map_depth, check_value_depth, diff_at, scoped};
|
|
|
49
49
|
/// [`Report::merge`] documents why the per-key `report.merge(...)` calls
|
|
50
50
|
/// below never collide on a *structural* path (each key here is visited
|
|
51
51
|
/// once, so no traversal ever revisits the same node) — `Report` is keyed
|
|
52
|
-
/// by that structural path, not by [`render_path`]'s rendered string, which
|
|
52
|
+
/// by that structural path, not by [`render_path`](crate::path::render_path)'s rendered string, which
|
|
53
53
|
/// is *not* injective on adversarial keys (see [`crate::path::quote_key`]'s
|
|
54
54
|
/// doc and `Report`'s module doc for how two structurally distinct paths
|
|
55
55
|
/// can render identically, and how that collision is handled at
|
|
@@ -173,7 +173,7 @@ pub(crate) fn numeric_diff(
|
|
|
173
173
|
scalar_diff(path, numbers_equal(old, new), a, b, depth, max_depth)
|
|
174
174
|
}
|
|
175
175
|
/// The single definition of numeric equality shared by [`numeric_diff`] and
|
|
176
|
-
/// [`values_equal`], so there is exactly one place these rules live.
|
|
176
|
+
/// [`values_equal`](super::values_equal), so there is exactly one place these rules live.
|
|
177
177
|
///
|
|
178
178
|
/// An int and a float are never equal (mirroring `DeepDiff` always reporting
|
|
179
179
|
/// that pairing as a `type_changes`, never a numeric comparison). Within the
|
|
@@ -749,9 +749,9 @@ fn map_deeper_than_recurses_with_incrementing_not_constant_depth() {
|
|
|
749
749
|
|
|
750
750
|
#[test]
|
|
751
751
|
fn deeper_than_handles_a_deeply_nested_value_without_crashing() {
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
//
|
|
752
|
+
// Guards `deeper_than` itself against being vulnerable to the very
|
|
753
|
+
// depth it is measuring — RECURSION_OVERFLOW_DEPTH (see its doc)
|
|
754
|
+
// reuses this file's own established native-recursion-overflow
|
|
755
755
|
// threshold. Built iteratively (a flat loop, not recursion); the
|
|
756
756
|
// whole test runs on `run_on_a_large_stack` so it is genuinely
|
|
757
757
|
// dropped afterwards instead of leaked.
|
|
@@ -761,7 +761,7 @@ fn deeper_than_handles_a_deeply_nested_value_without_crashing() {
|
|
|
761
761
|
});
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
// ---
|
|
764
|
+
// --- A shallow finding carrying a deep VALUE
|
|
765
765
|
// (dictionary_item_added/removed/type_changes/values_changed) must
|
|
766
766
|
// error cleanly via check_value_depth, never clone the deep value. ---
|
|
767
767
|
|
|
@@ -917,9 +917,9 @@ fn scalar_diff_rejects_a_value_whose_own_nesting_exceeds_max_depth_on_the_new_si
|
|
|
917
917
|
);
|
|
918
918
|
}
|
|
919
919
|
|
|
920
|
-
// --- Compounding-depth regression
|
|
921
|
-
//
|
|
922
|
-
//
|
|
920
|
+
// --- Compounding-depth regression: a value reached through a shallow
|
|
921
|
+
// finding must not be allowed its own full max_depth on top of the path
|
|
922
|
+
// depth already consumed to reach it. ---
|
|
923
923
|
|
|
924
924
|
#[test]
|
|
925
925
|
fn value_depth_full_budget_at_a_shallow_finding_is_accepted() {
|
|
@@ -1114,9 +1114,10 @@ fn sibling_after_a_deeply_nested_key_gets_its_own_shallow_path_not_the_deep_ones
|
|
|
1114
1114
|
}
|
|
1115
1115
|
|
|
1116
1116
|
#[test]
|
|
1117
|
-
fn
|
|
1118
|
-
//
|
|
1119
|
-
// at DEFAULT_MAX_DEPTH.
|
|
1117
|
+
fn shallow_finding_with_a_value_past_the_guard_errors_cleanly() {
|
|
1118
|
+
// Exercises the shallow-finding-with-a-too-deep-value shape:
|
|
1119
|
+
// diff({}, {"x": <deep array>}) at DEFAULT_MAX_DEPTH.
|
|
1120
|
+
// `check_value_depth`/`deeper_than` reject a
|
|
1120
1121
|
// too-deep value by walking at most `max_depth + 1` levels of it
|
|
1121
1122
|
// before short-circuiting (see `check_value_depth`'s doc), so
|
|
1122
1123
|
// PAST_DEFAULT_MAX_DEPTH exercises identical behavior to the
|
|
@@ -1201,7 +1202,7 @@ fn compounding_depth_regression_max_depth_20_000_traversal_plus_deep_added_value
|
|
|
1201
1202
|
|
|
1202
1203
|
#[test]
|
|
1203
1204
|
fn threshold_collapse_rejects_a_deep_side_on_a_constrained_stack_instead_of_crashing() {
|
|
1204
|
-
//
|
|
1205
|
+
// Guards the `threshold_to_diff_deeper` collapse:
|
|
1205
1206
|
// the collapse clones the whole `a`/`b` dict into a finding,
|
|
1206
1207
|
// so cloning before checking depth would hand an attacker-controlled,
|
|
1207
1208
|
// `RECURSION_OVERFLOW_DEPTH`-deep value straight to `serde_json::Value`'s
|
|
@@ -14,8 +14,8 @@ use super::IgnoreOrderMemo;
|
|
|
14
14
|
use super::fxhash::HashSet;
|
|
15
15
|
|
|
16
16
|
/// A total-ordering wrapper for the non-negative, always-finite distances
|
|
17
|
-
/// [`rough_distance`] computes, so they can key a [`BTreeMap`] (ascending
|
|
18
|
-
/// iteration, for [`compute_pairs`]'s greedy loop) and group candidates by
|
|
17
|
+
/// [`rough_distance`] computes, so they can key a [`BTreeMap`](std::collections::BTreeMap) (ascending
|
|
18
|
+
/// iteration, for [`compute_pairs`](super::pairing::compute_pairs)'s greedy loop) and group candidates by
|
|
19
19
|
/// **exact** float equality — matching `DeepDiff`'s own behavior of keying
|
|
20
20
|
/// a plain Python `dict` by the raw `float` distance value.
|
|
21
21
|
#[derive(Debug, Clone, Copy)]
|
|
@@ -155,7 +155,7 @@ fn family_distance(removed: &Value, added: &Value, cutoff: f64) -> Option<f64> {
|
|
|
155
155
|
/// `DeepDiff`'s `_get_numbers_distance` (distance.py), including the
|
|
156
156
|
/// "self-cancellation" quirk (`max_` appears in both the
|
|
157
157
|
/// formula and the rejection threshold, so same-sign numeric pairs are
|
|
158
|
-
/// almost never rejected by [`CUTOFF_DISTANCE_FOR_PAIRS`]) and the
|
|
158
|
+
/// almost never rejected by [`CUTOFF_DISTANCE_FOR_PAIRS`](super::pairing::CUTOFF_DISTANCE_FOR_PAIRS)) and the
|
|
159
159
|
/// opposite-sign zero-sum edge case (`divisor == 0.0` returns `cutoff`
|
|
160
160
|
/// itself, i.e. always-reject, rather than dividing by zero).
|
|
161
161
|
#[allow(
|
|
@@ -254,9 +254,9 @@ pub(crate) fn is_length_excluded_key(key: &str) -> bool {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/// A `Report`-free mirror of the recursive diff dispatch, counting exactly
|
|
257
|
-
/// what [`Report::distance_leaf_length`] would sum from the equivalent real
|
|
257
|
+
/// what [`Report::distance_leaf_length`](crate::report::Report::distance_leaf_length) would sum from the equivalent real
|
|
258
258
|
/// diff — used by [`rough_distance`]'s structural fallback so a candidate
|
|
259
|
-
/// pair's `diff_length` never pays for a [`Report`]'s `PathSegment`
|
|
259
|
+
/// pair's `diff_length` never pays for a [`Report`](crate::report::Report)'s `PathSegment`
|
|
260
260
|
/// allocations, `Value` clones, or `BTreeMap` inserts. This matters: a
|
|
261
261
|
/// naive "just call `diff_at` and count its `Report`" implementation would
|
|
262
262
|
/// pay exactly the per-candidate object-construction cost that is the
|
|
@@ -648,10 +648,10 @@ fn coerce_to_python_str(value: &Value) -> Option<String> {
|
|
|
648
648
|
/// dict-vs-dict pair through the real `array_diff`/`object_diff` engine:
|
|
649
649
|
/// when that trial's pairing accepts a dict-vs-dict pair nested inside it,
|
|
650
650
|
/// the *actual Report entry it builds* now already reflects the collapse,
|
|
651
|
-
/// so [`Report::distance_leaf_length`] never inherits an inflated,
|
|
651
|
+
/// so [`Report::distance_leaf_length`](crate::report::Report::distance_leaf_length) never inherits an inflated,
|
|
652
652
|
/// uncollapsed leaf count from it (found by differential fuzzing: a
|
|
653
653
|
/// nested-array-of-dicts candidate pair whose true distance is 0.1364 used
|
|
654
|
-
/// to be measured at 0.3182 — crossing [`CUTOFF_DISTANCE_FOR_PAIRS`] and
|
|
654
|
+
/// to be measured at 0.3182 — crossing [`CUTOFF_DISTANCE_FOR_PAIRS`](super::pairing::CUTOFF_DISTANCE_FOR_PAIRS) and
|
|
655
655
|
/// producing a completely different pairing decision).
|
|
656
656
|
pub(crate) const THRESHOLD_TO_DIFF_DEEPER: f64 = 0.33;
|
|
657
657
|
|
|
@@ -783,7 +783,7 @@ pub(crate) fn count_array_diff_leaves(
|
|
|
783
783
|
/// `diff_length` comes from [`count_diff_leaves`] — a `Report`-free mirror
|
|
784
784
|
/// of a trial recursive diff between `removed` and `added` (see that
|
|
785
785
|
/// function's own doc for why it deliberately does *not* build a
|
|
786
|
-
/// [`Report`], unlike `DeepDiff`'s own brand-new nested `DeepDiff` object
|
|
786
|
+
/// [`Report`](crate::report::Report), unlike `DeepDiff`'s own brand-new nested `DeepDiff` object
|
|
787
787
|
/// built purely to measure this).
|
|
788
788
|
///
|
|
789
789
|
/// `depth` is the depth of the *list* doing the pairing (so `removed`/
|
|
@@ -797,14 +797,14 @@ pub(crate) fn count_array_diff_leaves(
|
|
|
797
797
|
///
|
|
798
798
|
/// **The one place this can still fail is [`count_array_diff_leaves`]'s own
|
|
799
799
|
/// nested trial diff, and it is believed unreachable today, kept anyway as
|
|
800
|
-
/// defense-in-depth.** [`ignore_order_array_diff`]'s own up-front
|
|
800
|
+
/// defense-in-depth.** [`ignore_order_array_diff`](super::ignore_order_array_diff)'s own up-front
|
|
801
801
|
/// [`crate::diff::check_value_depth`] pass already validates `removed`/`added`
|
|
802
802
|
/// individually against this exact same reduced budget
|
|
803
803
|
/// (`max_depth` minus the items' own depth) before pairing ever runs; a
|
|
804
804
|
/// short inductive argument (every subtree's remaining-budget-at-its-relative-depth
|
|
805
805
|
/// transfers exactly from a depth-checked root to a trial restarted at
|
|
806
806
|
/// depth `0` with that same budget as its own `max_depth`) shows the trial
|
|
807
|
-
/// can never need more depth than that. Unlike
|
|
807
|
+
/// can never need more depth than that. Unlike `insert_lcs_pair_finding`'s
|
|
808
808
|
/// equivalent "structurally unreachable" guard (a *static type fact* — a
|
|
809
809
|
/// JSON scalar's nesting is always `0`, true forever), the invariant here
|
|
810
810
|
/// is a *cross-function arithmetic* one, spread across several functions —
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! `FxHash`: a small, fast, non-cryptographic hasher used for this module's
|
|
2
2
|
//! `HashMap`/`HashSet`s ([`HashedList::info`](super::hash::HashedList),
|
|
3
|
-
//!
|
|
3
|
+
//! `AddedCandidates::buckets`, the
|
|
4
4
|
//! pairing/`used` sets in [`compute_pairs`](super::pairing::compute_pairs),
|
|
5
5
|
//! and the [`IgnoreOrderMemo`](super::memo::IgnoreOrderMemo) cache) —
|
|
6
6
|
//! chosen for speed at the cost of hash-flooding resistance. Some of these
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
use std::hash::BuildHasherDefault;
|
|
13
13
|
|
|
14
14
|
/// A [`std::collections::HashMap`] keyed by this module's own types
|
|
15
|
-
/// ([`ItemKey`], [`Distance`]), using [`FxHasher`] instead of the standard
|
|
15
|
+
/// ([`ItemKey`](super::hash::ItemKey), [`Distance`](super::distance::Distance)), using [`FxHasher`] instead of the standard
|
|
16
16
|
/// library's default (`SipHash`) — see that type's doc for the trade-off.
|
|
17
17
|
pub(crate) type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<FxHasher>>;
|
|
18
18
|
/// The [`HashMap`] equivalent for [`std::collections::HashSet`].
|
|
@@ -44,11 +44,11 @@ pub(crate) type HashSet<T> = std::collections::HashSet<T, BuildHasherDefault<FxH
|
|
|
44
44
|
/// `FxHash` table there would be a crafted-collision `DoS` on the ordinary
|
|
45
45
|
/// path; a `BTreeMap` has no hash to attack (`O(log n)` worst case, always). See
|
|
46
46
|
/// that module's "Set-member digests" section. Every remaining `FxHash` table
|
|
47
|
-
/// in this module — `HashedList`,
|
|
47
|
+
/// in this module — `HashedList`, `AddedCandidates`,
|
|
48
48
|
/// the pairing/`used` sets, and the distance memo — is reached **only** under
|
|
49
49
|
/// `ignore_order=true`, the pairing hot path already bounded by the `O(N²)`
|
|
50
50
|
/// caveat below; those keep `FxHash`, and their float-carrying keys
|
|
51
|
-
/// ([`ItemKey`](super::hash::ItemKey), [`ScalarKey`](
|
|
51
|
+
/// ([`ItemKey`](super::hash::ItemKey), [`ScalarKey`](crate::lcs::ScalarKey),
|
|
52
52
|
/// and the distance memo's [`DistKey`](super::hash::DistKey)) mix their bits
|
|
53
53
|
/// first ([`mix_float_bits`](crate::lcs::mix_float_bits)) — `DistKey`'s
|
|
54
54
|
/// encoding routes every float through `number_key`, hence `ItemKey::Float`,
|
|
@@ -169,7 +169,7 @@ pub(crate) enum ItemKey {
|
|
|
169
169
|
/// value (never collides with `Int`).
|
|
170
170
|
Bool(bool),
|
|
171
171
|
/// A JSON value `serde_json` parsed as an integer (no decimal point or
|
|
172
|
-
/// exponent) — see [`crate::diff`]'s `python_type_name` for the same
|
|
172
|
+
/// exponent) — see [`mod@crate::diff`]'s `python_type_name` for the same
|
|
173
173
|
/// int/float split used throughout this crate. Always exact: a
|
|
174
174
|
/// `serde_json::Number`'s non-float representation is always an `i64`
|
|
175
175
|
/// or `u64`, both of which fit losslessly in `i128`.
|
|
@@ -400,7 +400,7 @@ pub(crate) enum MemberContent {
|
|
|
400
400
|
/// `RepId`s are equal.
|
|
401
401
|
///
|
|
402
402
|
/// Shared by [`crate::diff::set_diff`] and
|
|
403
|
-
///
|
|
403
|
+
/// `count_set_diff_leaves` so the two can never drift
|
|
404
404
|
/// on what "the same member" means.
|
|
405
405
|
pub(crate) fn set_difference<'a>(
|
|
406
406
|
a: &'a [Value],
|
|
@@ -660,7 +660,7 @@ fn number_key(n: &crate::value::Number) -> ItemKey {
|
|
|
660
660
|
///
|
|
661
661
|
/// Recurses natively — safe only because
|
|
662
662
|
/// every caller in this module first proves `value`'s nesting is within the
|
|
663
|
-
/// crate's shared depth budget via [`check_value_depth`] (see this module's
|
|
663
|
+
/// crate's shared depth budget via [`check_value_depth`](crate::diff::check_value_depth) (see this module's
|
|
664
664
|
/// "Depth safety" doc section).
|
|
665
665
|
pub(crate) fn item_key(value: &Value, memo: &IgnoreOrderMemo) -> ItemKey {
|
|
666
666
|
keyed(value, memo, false).0
|
|
@@ -790,7 +790,7 @@ pub(crate) struct HashedList<'a> {
|
|
|
790
790
|
/// — this is `SetOrdered(full_t{1,2}_hashtable.keys())`'s own iteration
|
|
791
791
|
/// order (a Python dict's insertion order). Each key sits behind an [`Rc`]
|
|
792
792
|
/// shared with [`Self::info`] and the distance-memo cache keys (see
|
|
793
|
-
///
|
|
793
|
+
/// `DistanceKey`).
|
|
794
794
|
pub(crate) distinct_order: Vec<Rc<ItemKey>>,
|
|
795
795
|
info: HashMap<Rc<ItemKey>, (usize, &'a Value)>,
|
|
796
796
|
}
|
|
@@ -139,7 +139,7 @@ use super::hash::{
|
|
|
139
139
|
/// [`super::hash::DistKey`] for why each side is a value's exact structural
|
|
140
140
|
/// identity (not its order/repetition-insensitive `ItemKey`). Memory per
|
|
141
141
|
/// entry is a refcount bump: each side's value is interned once per candidate
|
|
142
|
-
/// and the `A * R` entries one pairing records just share those [`Rc`]s. The
|
|
142
|
+
/// and the `A * R` entries one pairing records just share those [`Rc`](std::rc::Rc)s. The
|
|
143
143
|
/// *lookup* cost is not constant, though — every probe hashes both keys (a
|
|
144
144
|
/// full walk of each value) and, on a bucket match, compares them structurally
|
|
145
145
|
/// — so per-pair work is proportional to record size (see [`super::hash::DistKey`]).
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
//!
|
|
34
34
|
//! For one list level (`a` = `t1`/old, `b` = `t2`/new):
|
|
35
35
|
//!
|
|
36
|
-
//! 1. **Hash every item** ([`item_key`]) into a canonical equivalence key —
|
|
36
|
+
//! 1. **Hash every item** ([`item_key`](hash::item_key)) into a canonical equivalence key —
|
|
37
37
|
//! see that function's doc for the exact `DeepHash`-matching rules
|
|
38
38
|
//! (type-tagged numbers, order/count-insensitive nested lists, etc.).
|
|
39
39
|
//! Reduce each list to its *distinct* keys in first-occurrence order
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
//! added-hash loop consumed every paired partner) is a plain
|
|
71
71
|
//! `iterable_item_removed` at its own index.
|
|
72
72
|
//!
|
|
73
|
-
//! After the whole recursive traversal finishes, [`crate::diff`]'s existing
|
|
73
|
+
//! After the whole recursive traversal finishes, [`mod@crate::diff`]'s existing
|
|
74
74
|
//! whole-tree [`crate::report::Report::merge_mutual_add_removes`] pass
|
|
75
75
|
//! still runs exactly once, unchanged — it is what turns many of this
|
|
76
76
|
//! module's own raw adds/removes into `values_changed` on typical small,
|
|
@@ -79,21 +79,21 @@
|
|
|
79
79
|
//!
|
|
80
80
|
//! # Depth safety (this crate's own invariant, not `DeepDiff`'s)
|
|
81
81
|
//!
|
|
82
|
-
//! [`item_key`] and [`rough_length`] both recurse **natively** (no explicit
|
|
82
|
+
//! [`item_key`](hash::item_key) and [`rough_length`](distance::rough_length) both recurse **natively** (no explicit
|
|
83
83
|
//! work-stack) — unlike the rest of this crate's traversal, which is bounded
|
|
84
84
|
//! via [`crate::diff::check_traversal_depth`]/[`crate::diff::check_value_depth`].
|
|
85
85
|
//! [`ignore_order_array_diff`] closes this gap up front: every item of both
|
|
86
86
|
//! `a` and `b` is validated with [`crate::diff::check_value_depth`] (the same
|
|
87
87
|
//! combined path-depth-plus-value-depth budget documented on
|
|
88
88
|
//! [`crate::diff::diff_with_max_depth`]) *before* any hashing happens, so by
|
|
89
|
-
//! the time [`item_key`]/[`rough_length`] run, their native recursion is
|
|
89
|
+
//! the time [`item_key`](hash::item_key)/[`rough_length`](distance::rough_length) run, their native recursion is
|
|
90
90
|
//! already proven to be no deeper than a bound this crate already trusts a
|
|
91
91
|
//! plain `.clone()` to recurse through safely elsewhere. The structural
|
|
92
|
-
//! distance fallback's own trial comparison ([`count_diff_leaves`]) is a
|
|
92
|
+
//! distance fallback's own trial comparison ([`count_diff_leaves`](distance::count_diff_leaves)) is a
|
|
93
93
|
//! `Report`-free mirror of a diff for scalars and dicts (no further native
|
|
94
94
|
//! recursion risk beyond what's already covered above), except for a
|
|
95
95
|
//! genuinely nested array, which still runs a real, small trial
|
|
96
|
-
//! [`crate::diff::array_diff`] call — see [`rough_distance`]'s doc for its
|
|
96
|
+
//! [`crate::diff::array_diff`] call — see [`rough_distance`](distance::rough_distance)'s doc for its
|
|
97
97
|
//! own remaining-budget bound.
|
|
98
98
|
//!
|
|
99
99
|
//! # Two `DeepDiff` behaviors the pairing depends on
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
//! `float(0) == 0.0`, `int(True) == 1`, `bool(0) == False`) — the general
|
|
108
108
|
//! rule real `DeepDiff` applies
|
|
109
109
|
//! (`model.py::TreeResult._from_tree_type_changes`, `DELTA_VIEW` branch).
|
|
110
|
-
//! See
|
|
110
|
+
//! See `coerce_for_type_change` for the exact matrix.
|
|
111
111
|
//! [`type_change_leaf_length`] and `Report::distance_leaf_length` both
|
|
112
112
|
//! route through it, so this rule lives in exactly one place.
|
|
113
113
|
//! 2. **`new_path` composition across nesting levels.** A paired item whose
|
|
@@ -83,7 +83,7 @@ impl AddedCandidates {
|
|
|
83
83
|
/// Returns added-hash → removed-hash (only that one direction: `DeepDiff`'s
|
|
84
84
|
/// own `pairs` dict is built symmetrically so `get_other_pair` can look it
|
|
85
85
|
/// up from either side, but this port only ever looks up from the added
|
|
86
|
-
/// side — see [`ignore_order_array_diff`] — so the reverse direction is
|
|
86
|
+
/// side — see [`ignore_order_array_diff`](super::ignore_order_array_diff) — so the reverse direction is
|
|
87
87
|
/// never constructed).
|
|
88
88
|
pub(crate) fn compute_pairs(
|
|
89
89
|
hashes_added: &[Rc<ItemKey>],
|
|
@@ -44,7 +44,7 @@ use crate::value::Value;
|
|
|
44
44
|
|
|
45
45
|
/// A bucket key for grouping list elements that compare equal the way
|
|
46
46
|
/// Python's `==` (and therefore `difflib`'s matcher, and dict/set hashing)
|
|
47
|
-
/// does — **not** the way [`crate::diff`]'s own scalar comparison does.
|
|
47
|
+
/// does — **not** the way [`mod@crate::diff`]'s own scalar comparison does.
|
|
48
48
|
///
|
|
49
49
|
/// This is the "hashable" finding: Python treats `1 == 1.0 == True` (and
|
|
50
50
|
/// `0 == 0.0 == False`) as equal regardless of type, so `difflib` can match
|
|
@@ -588,7 +588,7 @@ impl Report {
|
|
|
588
588
|
}
|
|
589
589
|
|
|
590
590
|
/// `DeepDiff`'s `_get_item_length` (distance.py) applied to a whole
|
|
591
|
-
/// trial-diff report —
|
|
591
|
+
/// trial-diff report — `rough_distance`'s
|
|
592
592
|
/// structural-fallback numerator (`diff_length`).
|
|
593
593
|
///
|
|
594
594
|
/// Mirrors exactly which sub-value `_get_item_length` would see for
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|