mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Save task options (#4620)
* Support setting nested UI values * Accept partial for configureUI * Send partial UI * Save scan, generate and auto-tag options on change * Send partials in saveUI * Save library task options on change
This commit is contained in:
64
pkg/utils/map.go
Normal file
64
pkg/utils/map.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NestedMap is a map that supports nested keys.
|
||||
// It is expected that the nested maps are of type map[string]interface{}
|
||||
type NestedMap map[string]interface{}
|
||||
|
||||
func (m NestedMap) Get(key string) (interface{}, bool) {
|
||||
fields := strings.Split(key, ".")
|
||||
|
||||
current := m
|
||||
|
||||
for _, f := range fields[:len(fields)-1] {
|
||||
v, found := current[f]
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
current, _ = v.(map[string]interface{})
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
ret, found := current[fields[len(fields)-1]]
|
||||
return ret, found
|
||||
}
|
||||
|
||||
func (m NestedMap) Set(key string, value interface{}) {
|
||||
fields := strings.Split(key, ".")
|
||||
|
||||
current := m
|
||||
|
||||
for _, f := range fields[:len(fields)-1] {
|
||||
v, ok := current[f].(map[string]interface{})
|
||||
if !ok {
|
||||
v = make(map[string]interface{})
|
||||
current[f] = v
|
||||
}
|
||||
|
||||
current = v
|
||||
}
|
||||
|
||||
current[fields[len(fields)-1]] = value
|
||||
}
|
||||
|
||||
// MergeMaps merges src into dest. If a key exists in both maps, the value from src is used.
|
||||
func MergeMaps(dest map[string]interface{}, src map[string]interface{}) {
|
||||
for k, v := range src {
|
||||
if _, ok := dest[k]; ok {
|
||||
if srcMap, ok := v.(map[string]interface{}); ok {
|
||||
if destMap, ok := dest[k].(map[string]interface{}); ok {
|
||||
MergeMaps(destMap, srcMap)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dest[k] = v
|
||||
}
|
||||
}
|
||||
218
pkg/utils/map_test.go
Normal file
218
pkg/utils/map_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNestedMapGet(t *testing.T) {
|
||||
m := NestedMap{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"baz": "qux",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
want interface{}
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
name: "Get a value from a nested map",
|
||||
key: "foo.bar.baz",
|
||||
want: "qux",
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Get a value from a nested map with a missing key",
|
||||
key: "foo.bar.quux",
|
||||
want: nil,
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "Get a value from a nested map with a missing key",
|
||||
key: "foo.quux.baz",
|
||||
want: nil,
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "Get a value from a nested map with a missing key",
|
||||
key: "quux.bar.baz",
|
||||
want: nil,
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "Get a value from a nested map with a missing key",
|
||||
key: "foo.bar",
|
||||
want: map[string]interface{}{"baz": "qux"},
|
||||
found: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, found := m.Get(tt.key)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NestedMap.Get() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if found != tt.found {
|
||||
t.Errorf("NestedMap.Get() found = %v, want %v", found, tt.found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedMapSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
existing NestedMap
|
||||
want NestedMap
|
||||
}{
|
||||
{
|
||||
name: "Set a value in a nested map",
|
||||
key: "foo.bar.baz",
|
||||
existing: NestedMap{},
|
||||
want: NestedMap{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"baz": "qux",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Overwrite existing value",
|
||||
key: "foo.bar",
|
||||
existing: NestedMap{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "old",
|
||||
},
|
||||
},
|
||||
want: NestedMap{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "qux",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set a value overwriting a primitive with a nested map",
|
||||
key: "foo.bar",
|
||||
existing: NestedMap{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: NestedMap{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "qux",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.existing.Set(tt.key, "qux")
|
||||
if !reflect.DeepEqual(tt.existing, tt.want) {
|
||||
t.Errorf("NestedMap.Set() got = %v, want %v", tt.existing, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeMaps(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dest map[string]interface{}
|
||||
src map[string]interface{}
|
||||
result map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "Merge two maps",
|
||||
dest: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
src: map[string]interface{}{
|
||||
"baz": "qux",
|
||||
},
|
||||
result: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Merge two maps with overlapping keys",
|
||||
dest: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
},
|
||||
src: map[string]interface{}{
|
||||
"baz": "quux",
|
||||
},
|
||||
result: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": "quux",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Merge two maps with overlapping keys and nested maps",
|
||||
dest: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
src: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"qux": "quux",
|
||||
},
|
||||
},
|
||||
result: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
"qux": "quux",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Merge two maps with overlapping keys and nested maps",
|
||||
dest: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
src: map[string]interface{}{
|
||||
"foo": "qux",
|
||||
},
|
||||
result: map[string]interface{}{
|
||||
"foo": "qux",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Merge two maps with overlapping keys and nested maps",
|
||||
dest: map[string]interface{}{
|
||||
"foo": "qux",
|
||||
},
|
||||
src: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
result: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
MergeMaps(tt.dest, tt.src)
|
||||
if !reflect.DeepEqual(tt.dest, tt.result) {
|
||||
t.Errorf("NestedMap.Set() got = %v, want %v", tt.dest, tt.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user