@@ -1365,55 +1365,87 @@ impl Value {
13651365 }
13661366 }
13671367
1368- /// Recursively merge `new` into `self`.
1369- ///
1370- /// Objects are deep-merged: keys present on only one side are kept, keys present on both
1371- /// whose values are both objects are merged recursively, and keys whose values are both
1372- /// sets are unioned. This matches OPA's data-document merge semantics for objects (see
1373- /// `Engine::add_data`); set-union is a regorus extension, since OPA data documents are
1374- /// JSON and cannot contain sets. Any other differing pair — values that are not both
1375- /// objects or both sets, such as two unequal scalars or two arrays — is a genuine
1376- /// conflict and is an error. Equal values are tolerated as a no-op;
1377- /// the rule-evaluation path (`Interpreter::merge_rule_value`) shares this method and relies
1378- /// on that leniency, since a rule may legally produce the same value more than once.
1368+ /// Shallow-merge `new` into `self` with strict rule-output semantics.
1369+ ///
1370+ /// Objects merge one level deep: a key on both sides must hold the *same* value or it is a
1371+ /// conflict; sets union; equal values are a no-op. Non-recursive by design — data documents
1372+ /// use [`Value::deep_merge`] instead.
13791373 pub ( crate ) fn merge ( & mut self , mut new : Value ) -> Result < ( ) > {
13801374 if self == & new {
13811375 return Ok ( ( ) ) ;
13821376 }
13831377 match ( self , & mut new) {
13841378 ( v @ Value :: Undefined , _) => * v = new,
13851379 ( Value :: Set ( ref mut set) , Value :: Set ( new) ) => {
1386- // Union without deep-cloning the RHS set. If we uniquely own it, move its
1387- // elements out; if it is shared (e.g. reached via `existing.merge(v.clone())`
1388- // in the object arm below), clone only the element handles (`Rc` bumps),
1389- // never the whole `BTreeSet`.
1380+ // Union without deep-cloning the RHS set (see `deep_merge`).
1381+ let dst = Rc :: make_mut ( set) ;
1382+ match Rc :: try_unwrap ( core:: mem:: take ( new) ) {
1383+ Ok ( owned) => dst. extend ( owned) ,
1384+ Err ( shared) => dst. extend ( shared. iter ( ) . cloned ( ) ) ,
1385+ }
1386+ enforce_limit_anyhow ( ) ?;
1387+ }
1388+ ( Value :: Object ( map) , Value :: Object ( new) ) => {
1389+ for ( k, v) in new. iter ( ) {
1390+ match map. get ( k) {
1391+ // Same key, different value: the rule produced two outputs for one path.
1392+ Some ( pv) if * pv != * v => bail ! (
1393+ "value for key `{}` generated multiple times: `{}` and `{}`" ,
1394+ serde_json:: to_string_pretty( & k) . map_err( anyhow:: Error :: msg) ?,
1395+ serde_json:: to_string_pretty( & pv) . map_err( anyhow:: Error :: msg) ?,
1396+ serde_json:: to_string_pretty( & v) . map_err( anyhow:: Error :: msg) ?,
1397+ ) ,
1398+ _ => {
1399+ Rc :: make_mut ( map) . insert ( k. clone ( ) , v. clone ( ) ) ;
1400+ enforce_limit_anyhow ( ) ?;
1401+ }
1402+ } ;
1403+ }
1404+ }
1405+ _ => bail ! ( "error: could not merge value" ) ,
1406+ } ;
1407+ Ok ( ( ) )
1408+ }
1409+
1410+ /// Recursively deep-merge `new` into `self` — the data-document merge behind [`Engine::add_data`].
1411+ ///
1412+ /// Objects recurse per-key, sets union, equal values are a no-op, any other differing pair
1413+ /// conflicts. Set-union is a regorus extension (OPA data is JSON, which has no sets). Distinct
1414+ /// from the strict, non-recursive [`Value::merge`] used for rule outputs — use deep-merge ONLY
1415+ /// for data documents.
1416+ ///
1417+ /// [`Engine::add_data`]: crate::Engine::add_data
1418+ pub ( crate ) fn deep_merge ( & mut self , mut new : Value ) -> Result < ( ) > {
1419+ if self == & new {
1420+ return Ok ( ( ) ) ;
1421+ }
1422+ match ( self , & mut new) {
1423+ ( v @ Value :: Undefined , _) => * v = new,
1424+ ( Value :: Set ( ref mut set) , Value :: Set ( new) ) => {
1425+ // Union without deep-cloning the RHS set: move elements if uniquely owned,
1426+ // else clone only the element handles (`Rc` bumps), never the whole `BTreeSet`.
13901427 let dst = Rc :: make_mut ( set) ;
13911428 match Rc :: try_unwrap ( core:: mem:: take ( new) ) {
13921429 Ok ( owned) => dst. extend ( owned) ,
13931430 Err ( shared) => dst. extend ( shared. iter ( ) . cloned ( ) ) ,
13941431 }
1395- // Enforce allocator limit after merging set entries.
13961432 enforce_limit_anyhow ( ) ?;
13971433 }
13981434 ( Value :: Object ( map) , Value :: Object ( new) ) => {
13991435 let map = Rc :: make_mut ( map) ;
14001436 for ( k, v) in new. iter ( ) {
14011437 match map. get_mut ( k) {
14021438 Some ( existing) => {
1403- // Deep-merge: when both sides are containers, recurse so that
1404- // nested objects are merged rather than the whole subtree being
1405- // replaced (matches OPA's data document merge semantics).
1439+ // When both sides are containers, recurse so nested objects merge
1440+ // rather than the subtree being replaced (OPA data-merge semantics).
14061441 let both_mergeable = matches ! (
14071442 ( & * existing, v) ,
14081443 ( Value :: Object ( _) , Value :: Object ( _) )
14091444 | ( Value :: Set ( _) , Value :: Set ( _) )
14101445 ) ;
14111446 if both_mergeable {
1412- existing. merge ( v. clone ( ) ) ?;
1447+ existing. deep_merge ( v. clone ( ) ) ?;
14131448 } else if * existing != * v {
1414- // A genuine leaf conflict: the same path holds two different
1415- // values. (Equal values are tolerated as a no-op, which the
1416- // shared rule-evaluation path relies on.)
14171449 bail ! (
14181450 "value for key `{}` generated multiple times: `{}` and `{}`" ,
14191451 serde_json:: to_string_pretty( & k) . map_err( anyhow:: Error :: msg) ?,
@@ -1425,7 +1457,6 @@ impl Value {
14251457 }
14261458 None => {
14271459 map. insert ( k. clone ( ) , v. clone ( ) ) ;
1428- // Enforce allocator limit after merging object entries.
14291460 enforce_limit_anyhow ( ) ?;
14301461 }
14311462 } ;
@@ -1436,16 +1467,14 @@ impl Value {
14361467 Ok ( ( ) )
14371468 }
14381469
1439- /// Read-only check that merging `other` into `self` via [`Value::merge`] would
1440- /// not conflict, without mutating `self` or allocating.
1470+ /// Read-only check that [`deep_merge`](Value::deep_merge)-ing `other` into `self` would not
1471+ /// conflict, without mutating or allocating.
14411472 ///
1442- /// This mirrors `merge`'s conflict rule exactly — objects are deep-merged, sets
1443- /// union (never conflict), equal values are a tolerated no-op, and any other
1444- /// differing pair is a conflict — but only *reads*. [`Engine::add_data`] runs this
1445- /// before `merge` so a rejected add is all-or-nothing: a conflict deep in a nested
1446- /// document cannot leave earlier keys of the same document partially written.
1447- /// Keeping the check separate preserves `merge`'s in-place (uniquely-owned) fast
1448- /// path — no candidate copy of the data document is made just to validate it.
1473+ /// Lets [`Engine::add_data`] validate before merging in place. Since a conflict is the only
1474+ /// way the default-build merge can fail and it depends only on the inputs, a passing scan
1475+ /// guarantees the in-place `deep_merge` won't fail — avoiding the alternative of cloning the
1476+ /// whole document into a candidate just to validate. Only overlapping keys are walked, so
1477+ /// disjoint additions are near-free.
14491478 ///
14501479 /// [`Engine::add_data`]: crate::Engine::add_data
14511480 #[ cfg( not( feature = "allocator-memory-limits" ) ) ]
@@ -1455,12 +1484,11 @@ impl Value {
14551484 }
14561485 match ( self , other) {
14571486 ( Value :: Undefined , _) => Ok ( ( ) ) ,
1458- // Set union never conflicts (matches the `(Set, Set)` merge arm) .
1487+ // Set union never conflicts.
14591488 ( Value :: Set ( _) , Value :: Set ( _) ) => Ok ( ( ) ) ,
14601489 ( Value :: Object ( dst) , Value :: Object ( src) ) => {
14611490 for ( k, sv) in src. iter ( ) {
1462- // Only overlapping keys can conflict; keys unique to `src` are
1463- // pure insertions.
1491+ // Only overlapping keys can conflict.
14641492 if let Some ( dv) = dst. get ( k) {
14651493 let both_mergeable = matches ! (
14661494 ( dv, sv) ,
0 commit comments