feat(update): allow clearing values with "" and null on update

Update handlers skipped empty strings and nulls, so a client could not
blank or null out a column. Every key present in the payload now
overwrites the stored value, including "" and null.

- add common.MergeUpdateValues and use it in resolvespec and restheadspec
- add Handler.SetDisallowNulls to skip null values (""still overwrites)
- websocketspec and mqttspec now write only the keys present in the
  payload via SetMap instead of updating the whole zeroed model, which
  clobbered absent fields
This commit is contained in:
Hein
2026-09-30 17:03:42 +02:00
parent 1214f69e0c
commit 20ba8ed112
6 changed files with 146 additions and 80 deletions
+23
View File
@@ -0,0 +1,23 @@
package common
// MergeUpdateValues merges the incoming request values into the existing
// record map (in place) and returns it.
//
// Every key present in incoming overwrites the existing value, including empty
// strings and explicit nulls, so clients can clear a column by sending "" or
// null. Keys absent from incoming are left untouched.
//
// When disallowNulls is true, nil values are skipped and the existing value is
// kept. Empty strings are still applied.
func MergeUpdateValues(existing, incoming map[string]interface{}, disallowNulls bool) map[string]interface{} {
if existing == nil {
existing = make(map[string]interface{}, len(incoming))
}
for key, newValue := range incoming {
if newValue == nil && disallowNulls {
continue
}
existing[key] = newValue
}
return existing
}
+29
View File
@@ -0,0 +1,29 @@
package common
import "testing"
func TestMergeUpdateValues(t *testing.T) {
newExisting := func() map[string]interface{} {
return map[string]interface{}{"name": "old", "note": "keep", "other": "x"}
}
incoming := map[string]interface{}{"name": "", "note": nil}
got := MergeUpdateValues(newExisting(), incoming, false)
if got["name"] != "" {
t.Errorf("name = %v, want empty string", got["name"])
}
if v, ok := got["note"]; !ok || v != nil {
t.Errorf("note = %v (present=%v), want nil", v, ok)
}
if got["other"] != "x" {
t.Errorf("absent key changed: %v", got["other"])
}
got = MergeUpdateValues(newExisting(), incoming, true)
if got["name"] != "" {
t.Errorf("disallowNulls: name = %v, want empty string", got["name"])
}
if got["note"] != "keep" {
t.Errorf("disallowNulls: note = %v, want keep", got["note"])
}
}