1
0
mirror of https://github.com/TomWright/dasel.git synced 2022-05-22 02:32:45 +03:00

Fix bugs when dealing with marked deletes, and add a bunch of tests

This commit is contained in:
Tom Wright
2021-08-01 18:15:50 +01:00
parent 3fc7fe6a82
commit 2d83790d01
7 changed files with 329 additions and 53 deletions

View File

@@ -34,6 +34,16 @@ func TestRootCMD_Delete(t *testing.T) {
[]string{"delete", "-p", "json", "-s", ".age"},
"no value found for selector: .age",
))
t.Run("InvalidRootNode", expectErrFromInput(
``,
[]string{"delete", "-p", "json", "-s", ".age"},
"no value found for selector: .age",
))
t.Run("InvalidRootNodeMulti", expectErrFromInput(
``,
[]string{"delete", "-p", "json", "-m", "-s", ".age"},
"no value found for selector: .age",
))
}
func deleteTest(in string, parser string, selector string, output string, expErr error, additionalArgs ...string) func(t *testing.T) {
@@ -89,53 +99,41 @@ func deleteTestCheck(in string, parser string, selector string, checkFn func(out
}
}
func deleteTestFromFile(inputPath string, selector string, out string, expErr error) func(t *testing.T) {
return func(t *testing.T) {
cmd := command.NewRootCMD()
outputBuffer := bytes.NewBuffer([]byte{})
args := []string{
"select", "-f", inputPath, "-s", selector,
}
cmd.SetOut(outputBuffer)
cmd.SetArgs(args)
err := cmd.Execute()
if expErr == nil && err != nil {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
if expErr != nil && err == nil {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
if expErr != nil && err != nil && err.Error() != expErr.Error() {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
output, err := io.ReadAll(outputBuffer)
if err != nil {
t.Errorf("unexpected error reading output buffer: %s", err)
return
}
if out != string(output) {
t.Errorf("expected result %v, got %v", out, string(output))
}
}
}
func TestRootCmd_Delete_JSON(t *testing.T) {
t.Run("RootElement", deleteTest(`{
t.Run("RootNodeObject", deleteTest(`{
"email": "tom@wright.com",
"name": "Tom"
}`, "json", ".", newline(`{}`), nil))
t.Run("RootNodeObjectMulti", deleteTest(`{
"email": "tom@wright.com",
"name": "Tom"
}`, "json", ".", newline(`{}`), nil, "-m"))
t.Run("RootNodeArray", deleteTest(`["a", "b", "c"]`, "json", ".",
newline(`[]`), nil))
t.Run("RootNodeArrayMulti", deleteTest(`["a", "b", "c"]`, "json", ".",
newline(`[]`), nil, "-m"))
t.Run("RootNodeUnknown", deleteTest(`false`, "json", ".",
newline(`{}`), nil))
t.Run("RootNodeUnknownMulti", deleteTest(`false`, "json", ".",
newline(`{}`), nil, "-m"))
t.Run("Property", deleteTest(`{
"email": "tom@wright.com",
"name": "Tom"
}`, "json", ".email", newline(`{
"name": "Tom"
}`), nil))
t.Run("PropertyCompact", deleteTest(`{
"email": "tom@wright.com",
"name": "Tom"
}`, "json", ".email", newline(`{"name":"Tom"}`), nil, "-c"))
t.Run("Index", deleteTest(`{
"colours": ["blue", "green", "red"],
"name": "Tom"

View File

@@ -4,7 +4,7 @@ import "reflect"
// Delete uses the given selector to find and delete the final node from the current node.
func (n *Node) Delete(selector string) error {
if selector == "." {
if isFinalSelector(selector) {
n.setReflectValue(initialiseEmptyOfType(n.Value))
return nil
}
@@ -87,6 +87,7 @@ func initialiseEmptyOfType(value reflect.Value) reflect.Value {
return reflect.MakeSlice(value.Type(), 0, 0)
case reflect.Map:
return reflect.MakeMap(value.Type())
default:
return reflect.ValueOf(map[string]interface{}{})
}
panic("unhandled kind: " + value.Kind().String())
}

View File

@@ -119,11 +119,6 @@ func deleteFromParentProperty(n *Node) error {
return &UnsupportedTypeForSelector{Selector: n.Selector, Value: n.Previous.Value}
}
type deletePlaceholder struct {
}
var deletePlaceholderType = reflect.TypeOf(deletePlaceholder{})
// deleteFromParentIndex sends the value of the current node up to the previous node in the chain.
func deleteFromParentIndex(n *Node) error {
if !isValid(n.Previous.Value) {
@@ -136,7 +131,7 @@ func deleteFromParentIndex(n *Node) error {
if n.Selector.Index >= 0 && n.Selector.Index < value.Len() {
// Mark this index for deletion.
// We can't just rewrite the slice here in-case other selectors also target it.
value.Index(n.Selector.Index).Set(reflect.ValueOf(deletePlaceholder{}))
value.Index(n.Selector.Index).Set(getDeletePlaceholder(value.Index(n.Selector.Index)))
}
return nil
}
@@ -162,7 +157,7 @@ func cleanupSliceDeletions(input reflect.Value) (reflect.Value, bool) {
invalidCount++
continue
}
if unwrapValue(item).Type() == deletePlaceholderType {
if isDeletePlaceholder(item) {
invalidCount++
continue
}
@@ -175,3 +170,44 @@ func cleanupSliceDeletions(input reflect.Value) (reflect.Value, bool) {
return res, true
}
const deletePlaceholderKey = "dasel:delete:key"
const deletePlaceholder = "dasel:delete:me"
func getDeletePlaceholder(item reflect.Value) reflect.Value {
switch unwrapValue(item).Kind() {
case reflect.Map:
return reflect.ValueOf(map[string]interface{}{
deletePlaceholderKey: deletePlaceholder,
})
case reflect.Slice:
return reflect.ValueOf([]interface{}{deletePlaceholder})
default:
return reflect.ValueOf(deletePlaceholder)
}
}
func isDeletePlaceholder(item reflect.Value) bool {
switch i := unwrapValue(item); i.Kind() {
case reflect.Map:
if val, ok := i.Interface().(map[string]interface{})[deletePlaceholderKey]; ok {
if val == deletePlaceholder {
return true
}
}
case reflect.Slice:
for _, val := range i.Interface().([]interface{}) {
if val == deletePlaceholder {
return true
}
}
default:
if val, ok := i.Interface().(string); ok {
if val == deletePlaceholder {
return true
}
}
}
return false
}

View File

@@ -66,7 +66,7 @@ func (n *Node) PutMultiple(selector string, newValue interface{}) error {
}
func buildPutChain(n *Node) error {
if n.Selector.Remaining == "" {
if isFinalSelector(n.Selector.Remaining) {
// We've reached the end
return nil
}
@@ -94,7 +94,7 @@ func buildPutChain(n *Node) error {
}
func buildPutMultipleChain(n *Node) error {
if n.Selector.Remaining == "" {
if isFinalSelector(n.Selector.Remaining) {
// We've reached the end
return nil
}

View File

@@ -29,8 +29,12 @@ func lastNode(n *Node) *Node {
}
}
func isFinalSelector(selector string) bool {
return selector == "" || selector == "."
}
func buildFindChain(n *Node) error {
if n.Selector.Remaining == "" {
if isFinalSelector(n.Selector.Remaining) {
// We've reached the end
return nil
}

View File

@@ -30,7 +30,7 @@ func lastNodes(n *Node) []*Node {
}
func buildFindMultipleChain(n *Node) error {
if n.Selector.Remaining == "" {
if isFinalSelector(n.Selector.Remaining) {
// We've reached the end
return nil
}

View File

@@ -793,6 +793,35 @@ func putQueryTest(rootNode *dasel.Node, putSelector string, newValue interface{}
}
}
func deleteTest(rootNode *dasel.Node, deleteSelector string, exp interface{}) func(t *testing.T) {
return func(t *testing.T) {
err := rootNode.Delete(deleteSelector)
if err != nil {
t.Errorf("unexpected delete error: %v", err)
return
}
gotVal := rootNode.InterfaceValue()
if !reflect.DeepEqual(exp, gotVal) && exp != gotVal {
t.Errorf("expected [%T] %v, got [%T] %v", exp, exp, gotVal, gotVal)
}
}
}
func deleteMultipleTest(rootNode *dasel.Node, deleteSelector string, exp interface{}) func(t *testing.T) {
return func(t *testing.T) {
err := rootNode.DeleteMultiple(deleteSelector)
if err != nil {
t.Errorf("unexpected delete error: %v", err)
return
}
if !reflect.DeepEqual(exp, rootNode.InterfaceValue()) {
t.Errorf("expected %v, got %v", exp, rootNode.InterfaceValue())
}
}
}
func putQueryMultipleTest(rootNode *dasel.Node, putSelector string, newValue interface{}, querySelector string) func(t *testing.T) {
return func(t *testing.T) {
err := rootNode.PutMultiple(putSelector, newValue)
@@ -955,3 +984,211 @@ func TestNode_PutMultiple_Query(t *testing.T) {
t.Run("NilChainToListIndex", putQueryMultipleTest(dasel.New(nil), "my.favourite.people.[0]", "Tom", "my.favourite.people.[0]"))
t.Run("NilChainToListNextAvailableIndex", putQueryMultipleTest(dasel.New(nil), "my.favourite.people.[]", "Tom", "my.favourite.people.[0]"))
}
func TestNode_Delete_Query(t *testing.T) {
data := func() map[string]interface{} {
return map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
}
}
t.Run("InvalidSelector", func(t *testing.T) {
err := dasel.New(data()).Delete("people.[a].name")
expErr := fmt.Errorf("failed to parse selector: %w", &dasel.InvalidIndexErr{Index: "a"})
if err == nil {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
if err.Error() != expErr.Error() {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
})
t.Run("ExistingSingleString", deleteTest(dasel.New(data()), "id", map[string]interface{}{
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("ExistingStringValue", deleteTest(dasel.New(data()), "people.[0].name", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
},
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("ExistingObjectInArray", deleteTest(dasel.New(data()), "people.[0]", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("ExistingIntValue", deleteTest(dasel.New(data()), "people.(id=1).id", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("RootNodeObject", deleteTest(dasel.New(map[string]interface{}{
"name": "Tom",
}), ".", map[string]interface{}{}))
t.Run("RootNodeArray", deleteTest(dasel.New([]interface{}{
"name", "Tom",
}), ".", []interface{}{}))
t.Run("RootNodeUnknown", deleteTest(dasel.New(false), ".", map[string]interface{}{}))
}
func TestNode_DeleteMultiple_Query(t *testing.T) {
data := func() map[string]interface{} {
return map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
}
}
t.Run("InvalidSelector", func(t *testing.T) {
err := dasel.New(data()).DeleteMultiple("people.[a].name")
expErr := fmt.Errorf("failed to parse selector: %w", &dasel.InvalidIndexErr{Index: "a"})
if err == nil {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
if err.Error() != expErr.Error() {
t.Errorf("expected err %v, got %v", expErr, err)
return
}
})
t.Run("ExistingSingleString", deleteMultipleTest(dasel.New(data()), "id", map[string]interface{}{
"people": []map[string]interface{}{
{
"id": 1,
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
"names": []string{
"Tom",
"Jim",
},
}))
t.Run("ExistingStringValue", deleteMultipleTest(dasel.New(data()), "people.[0].name", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
},
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("WildcardProperty", deleteMultipleTest(dasel.New(data()), "people.[*].name", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"id": 1,
},
{
"id": 2,
},
},
}))
t.Run("ExistingIntValue", deleteMultipleTest(dasel.New(data()), "people.(id=1).id", map[string]interface{}{
"id": "123",
"names": []string{
"Tom",
"Jim",
},
"people": []map[string]interface{}{
{
"name": "Tom",
},
{
"id": 2,
"name": "Jim",
},
},
}))
t.Run("RootNodeObject", deleteMultipleTest(dasel.New(map[string]interface{}{
"name": "Tom",
}), ".", map[string]interface{}{}))
t.Run("RootNodeArray", deleteMultipleTest(dasel.New([]interface{}{
"name", "Tom",
}), ".", []interface{}{}))
t.Run("RootNodeUnknown", deleteMultipleTest(dasel.New(false), ".", map[string]interface{}{}))
}