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:
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
237
node_test.go
237
node_test.go
@@ -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{}{}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user