From c00b99a2eb5848360e47de9a2861e9172a93627b Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Thu, 14 Jul 2022 21:15:31 +1000 Subject: [PATCH] dynamo-query: added delete attribute command --- .../dynamo-browse/controllers/attrpath.go | 32 +++++++++++++ .../dynamo-browse/controllers/tablewrite.go | 29 ++++++++++++ .../controllers/tablewrite_test.go | 45 +++++++++++++++++++ internal/dynamo-browse/ui/model.go | 6 +++ 4 files changed, 112 insertions(+) diff --git a/internal/dynamo-browse/controllers/attrpath.go b/internal/dynamo-browse/controllers/attrpath.go index 3a653ea..09e191f 100644 --- a/internal/dynamo-browse/controllers/attrpath.go +++ b/internal/dynamo-browse/controllers/attrpath.go @@ -31,6 +31,38 @@ func (ap attrPath) follow(item models.Item) (types.AttributeValue, error) { return step, nil } +func (ap attrPath) deleteAt(item models.Item) error { + if len(ap) == 1 { + delete(item, ap[0]) + return nil + } + + var step types.AttributeValue + for i, seg := range ap[:len(ap)-1] { + if i == 0 { + step = item[seg] + continue + } + + switch s := step.(type) { + case *types.AttributeValueMemberM: + step = s.Value[seg] + default: + return errors.Errorf("seg %v expected to be a map", i) + } + } + + lastSeg := ap[len(ap)-1] + switch s := step.(type) { + case *types.AttributeValueMemberM: + delete(s.Value, lastSeg) + default: + return errors.Errorf("last seg expected to be a map, but was %T", lastSeg) + } + + return nil +} + func (ap attrPath) setAt(item models.Item, newValue types.AttributeValue) error { if len(ap) == 1 { item[ap[0]] = newValue diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go index f6c634a..a6b2f2f 100644 --- a/internal/dynamo-browse/controllers/tablewrite.go +++ b/internal/dynamo-browse/controllers/tablewrite.go @@ -138,6 +138,35 @@ func (twc *TableWriteController) SetNumberValue(idx int, key string) tea.Cmd { } } +func (twc *TableWriteController) DeleteAttribute(idx int, key string) tea.Cmd { + return func() tea.Msg { + // Verify that the expression is valid + apPath := newAttrPath(key) + + if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error { + _, err := apPath.follow(set.Items()[idx]) + return err + }); err != nil { + return events.Error(err) + } + + if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error { + err := apPath.deleteAt(set.Items()[idx]) + if err != nil { + return err + } + + set.SetDirty(idx, true) + set.RefreshColumns() + return nil + }); err != nil { + return events.Error(err) + } + + return ResultSetUpdated{} + } +} + func (twc *TableWriteController) PutItem(idx int) tea.Cmd { return func() tea.Msg { resultSet := twc.state.ResultSet() diff --git a/internal/dynamo-browse/controllers/tablewrite_test.go b/internal/dynamo-browse/controllers/tablewrite_test.go index 094c706..98c593e 100644 --- a/internal/dynamo-browse/controllers/tablewrite_test.go +++ b/internal/dynamo-browse/controllers/tablewrite_test.go @@ -135,6 +135,51 @@ func TestTableWriteController_SetNumberValue(t *testing.T) { }) } +func TestTableWriteController_DeleteAttribute(t *testing.T) { + client, cleanupFn := testdynamo.SetupTestTable(t, testData) + defer cleanupFn() + + provider := dynamo.NewProvider(client) + service := tables.NewService(provider) + + t.Run("should delete top level attribute", func(t *testing.T) { + state := controllers.NewState() + readController := controllers.NewTableReadController(state, service, "alpha-table") + writeController := controllers.NewTableWriteController(state, service, readController) + + invokeCommand(t, readController.Init()) + before, _ := state.ResultSet().Items()[0].AttributeValueAsString("age") + assert.Equal(t, "23", before) + assert.False(t, state.ResultSet().IsDirty(0)) + + invokeCommand(t, writeController.DeleteAttribute(0, "age")) + + _, hasAge := state.ResultSet().Items()[0]["age"] + assert.False(t, hasAge) + }) + + t.Run("should delete attribute of map", func(t *testing.T) { + state := controllers.NewState() + readController := controllers.NewTableReadController(state, service, "alpha-table") + writeController := controllers.NewTableWriteController(state, service, readController) + + invokeCommand(t, readController.Init()) + + beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM) + beforeStreet := beforeAddress.Value["no"].(*types.AttributeValueMemberN).Value + + assert.Equal(t, "123", beforeStreet) + assert.False(t, state.ResultSet().IsDirty(0)) + + invokeCommand(t, writeController.DeleteAttribute(0, "address.no")) + + afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM) + _, hasStreet := afterAddress.Value["no"] + + assert.False(t, hasStreet) + }) +} + func TestTableWriteController_PutItem(t *testing.T) { t.Run("should put the selected item if dirty", func(t *testing.T) { client, cleanupFn := testdynamo.SetupTestTable(t, testData) diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index 891117f..34b62c1 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -68,6 +68,12 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon } return wc.SetNumberValue(dtv.SelectedItemIndex(), args[0]) }, + "del-attr": func(args []string) tea.Cmd { + if len(args) == 0 { + return events.SetError(errors.New("expected field")) + } + return wc.DeleteAttribute(dtv.SelectedItemIndex(), args[0]) + }, "put": func(args []string) tea.Cmd { return wc.PutItem(dtv.SelectedItemIndex())