Files
pulse/internal/websocket/merge_patch_reference_test.go
T
pulse-triage[bot] 9388cc2c73 Compare connected dashboard lifecycle with the exact parent delta oracle
Exercise advancing heartbeat and exact counters, metadata/catalog/health/alert changes, ownership, re-enrolment, removal, quiet updates and tenant isolation through production dispatch. The reference remains the supplied parent implementation.

Change-source: pulse-maintainer
2026-10-06 12:09:42 +01:00

198 lines
5.7 KiB
Go

package websocket
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"sort"
)
// Exact d14a3891 pre-change implementation, independent of the new raw-object path.
func referenceJSONMergePatch(previous, current json.RawMessage) (json.RawMessage, error) {
previousValue, err := referenceDecodeJSONMergeValue(previous)
if err != nil {
return nil, err
}
currentValue, err := referenceDecodeJSONMergeValue(current)
if err != nil {
return nil, err
}
patchValue, changed := referenceDiffJSONMergeValue(previousValue, currentValue)
if !changed {
return json.RawMessage(`{}`), nil
}
if patchObject, ok := patchValue.(map[string]interface{}); ok {
if currentObject, ok := currentValue.(map[string]interface{}); ok {
if id, ok := currentObject["id"]; ok {
patchObject["id"] = id
}
}
}
patch, err := json.Marshal(patchValue)
if err != nil {
return nil, err
}
return patch, nil
}
// A delta must preserve the authoritative encoded counters, not round them
// through float64. Otherwise adjacent int64/uint64 readings above 2^53 can
// compare equal, and a changed nested value/array can be sent with altered
// numbers. Keep number tokens exact while retaining the existing merge rules.
func referenceDecodeJSONMergeValue(encoded json.RawMessage) (interface{}, error) {
decoder := json.NewDecoder(bytes.NewReader(encoded))
decoder.UseNumber()
var value interface{}
if err := decoder.Decode(&value); err != nil {
return nil, err
}
// Decode accepts a stream by default. Preserve Unmarshal's one-value
// contract, including rejection of a valid prefix followed by more JSON.
var trailing interface{}
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
err = fmt.Errorf("multiple JSON merge values")
}
return nil, err
}
return value, nil
}
func referenceDiffJSONMergeValue(previous, current interface{}) (interface{}, bool) {
if reflect.DeepEqual(previous, current) {
return nil, false
}
previousObject, previousIsObject := previous.(map[string]interface{})
currentObject, currentIsObject := current.(map[string]interface{})
if !previousIsObject || !currentIsObject {
return current, true
}
patch := make(map[string]interface{})
for key := range previousObject {
if _, exists := currentObject[key]; !exists {
patch[key] = nil
}
}
for key, currentValue := range currentObject {
previousValue, exists := previousObject[key]
if !exists {
patch[key] = currentValue
continue
}
if nestedPatch, changed := referenceDiffJSONMergeValue(previousValue, currentValue); changed {
patch[key] = nestedPatch
}
}
return patch, len(patch) > 0
}
func referenceBuildKeyedArrayDelta(
previousEntries, currentEntries map[string]json.RawMessage,
previousOrder, currentOrder []string,
label string,
) (resourceDeltaPayload, error) {
payload := resourceDeltaPayload{}
for _, id := range currentOrder {
currentEntry := currentEntries[id]
previousEntry, exists := previousEntries[id]
if !exists {
payload.Upserts = append(payload.Upserts, currentEntry)
continue
}
if bytes.Equal(previousEntry, currentEntry) {
continue
}
patch, err := referenceJSONMergePatch(previousEntry, currentEntry)
if err != nil {
return resourceDeltaPayload{}, fmt.Errorf("build %s %q patch: %w", label, id, err)
}
payload.Upserts = append(payload.Upserts, patch)
}
for id := range previousEntries {
if _, exists := currentEntries[id]; !exists {
payload.Removed = append(payload.Removed, id)
}
}
sort.Strings(payload.Removed)
if !reflect.DeepEqual(previousOrder, currentOrder) {
payload.Order = append([]string(nil), currentOrder...)
}
return payload, nil
}
func referenceBuildClientStateDelta(previous, current *clientStateSnapshot) (map[string]interface{}, error) {
if previous == nil || current == nil {
return nil, fmt.Errorf("state delta requires previous and current snapshots")
}
delta := make(map[string]interface{})
for key, currentValue := range current.fields {
if previousValue, ok := previous.fields[key]; !ok || !bytes.Equal(previousValue, currentValue) {
delta[key] = currentValue
}
}
for key := range previous.fields {
if _, ok := current.fields[key]; !ok {
delta[key] = nil
}
}
resourceDelta, err := referenceBuildKeyedArrayDelta(
previous.resources,
current.resources,
previous.resourceOrder,
current.resourceOrder,
"resource",
)
if err != nil {
return nil, err
}
if !resourceDelta.isEmpty() {
delta[resourceDeltaField] = resourceDelta
}
for _, keyedField := range keyedDeltaFields {
previousSnapshot := previous.keyed[keyedField.field]
currentSnapshot := current.keyed[keyedField.field]
switch {
case previousSnapshot != nil && currentSnapshot != nil:
keyedDelta, err := referenceBuildKeyedArrayDelta(
previousSnapshot.entries,
currentSnapshot.entries,
previousSnapshot.order,
currentSnapshot.order,
keyedField.field,
)
if err != nil {
return nil, err
}
if !keyedDelta.isEmpty() {
delta[keyedField.deltaField] = keyedDelta
}
case currentSnapshot != nil:
// The previous snapshot carried the array as a plain field (or
// not at all): fall back to shipping the full array once so the
// client re-adopts a clean baseline.
if previousRaw, ok := previous.fields[keyedField.field]; !ok ||
!bytes.Equal(previousRaw, currentSnapshot.raw) {
delta[keyedField.field] = currentSnapshot.raw
}
case previousSnapshot != nil:
// The array left the keyed path. If the current snapshot still
// carries it as a plain field, the fields loop above already
// shipped it whole; if it vanished entirely, clear it like any
// removed field.
if _, ok := current.fields[keyedField.field]; !ok {
delta[keyedField.field] = nil
}
}
}
return delta, nil
}