From 5a69e6c95433f5945c9508da84a94f8ac2590986 Mon Sep 17 00:00:00 2001
From: Leon Mika <lmika@lmika.org>
Date: Fri, 25 Mar 2022 08:17:52 +1100
Subject: [PATCH] sqs-browse: remove assumption regarding table keys

Table keys are now retrieved from describe
---
 cmd/dynamo-browse/main.go                     |  8 +++---
 cmd/sqs-browse/main.go                        |  7 +++---
 cmd/sqs-drain/main.go                         | 19 +++++++-------
 internal/common/ui/commandctrl/commandctrl.go |  5 ++--
 .../common/ui/commandctrl/commandctrl_test.go |  3 ++-
 internal/common/ui/commandctrl/context.go     |  5 ++--
 internal/common/ui/dispatcher/context.go      |  1 +
 internal/common/ui/dispatcher/dispatcher.go   |  7 ++----
 internal/common/ui/dispatcher/iface.go        |  2 +-
 internal/common/ui/events/errors.go           |  2 +-
 internal/common/ui/uimodels/context.go        |  3 ++-
 internal/common/ui/uimodels/operations.go     |  1 -
 internal/common/ui/uimodels/promptvalue.go    |  3 ++-
 internal/dynamo-browse/controllers/events.go  |  2 +-
 internal/dynamo-browse/controllers/state.go   |  1 +
 .../dynamo-browse/controllers/tableread.go    |  1 +
 .../dynamo-browse/controllers/tablewrite.go   |  9 ++++---
 .../controllers/tablewrite_test.go            | 24 ++++++++++++------
 internal/dynamo-browse/models/attrutils.go    |  3 ++-
 internal/dynamo-browse/models/models.go       |  2 +-
 internal/dynamo-browse/models/modexpr/ast.go  |  2 +-
 .../dynamo-browse/models/modexpr/expr_test.go |  7 +++---
 internal/dynamo-browse/models/modexpr/mods.go |  4 +--
 .../dynamo-browse/models/modexpr/values.go    |  3 ++-
 internal/dynamo-browse/models/sorted_test.go  | 15 ++++++++---
 .../providers/dynamo/provider.go              |  7 +++---
 .../providers/dynamo/provider_test.go         |  3 ++-
 .../dynamo-browse/services/tables/iface.go    |  1 +
 .../dynamo-browse/services/tables/service.go  |  9 ++++---
 .../services/tables/service_test.go           |  3 ++-
 internal/dynamo-browse/ui/model.go            |  7 +++---
 internal/dynamo-browse/ui/tblmodel.go         |  5 ++--
 internal/sqs-browse/controllers/forward.go    |  1 +
 internal/sqs-browse/models/message.go         |  6 ++---
 internal/sqs-browse/providers/sqs/provider.go |  7 +++---
 .../providers/stormstore/memstore.go          |  1 +
 .../sqs-browse/services/messages/iface.go     |  3 ++-
 .../sqs-browse/services/messages/service.go   |  1 +
 .../sqs-browse/services/pollmessage/iface.go  |  3 ++-
 .../services/pollmessage/service.go           |  3 ++-
 internal/sqs-browse/ui/events.go              |  2 +-
 internal/sqs-browse/ui/model.go               | 25 ++++++++++---------
 internal/sqs-browse/ui/tblmodel.go            |  5 ++--
 test/cmd/load-test-table/main.go              | 10 ++++++--
 test/testdynamo/client.go                     |  3 ++-
 test/testdynamo/helpers.go                    |  3 ++-
 test/testuictx/testuictx.go                   |  1 +
 47 files changed, 150 insertions(+), 98 deletions(-)

diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go
index f08ea81..e19cca8 100644
--- a/cmd/dynamo-browse/main.go
+++ b/cmd/dynamo-browse/main.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"flag"
 	"fmt"
+	"os"
+
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
 	tea "github.com/charmbracelet/bubbletea"
@@ -15,7 +17,6 @@ import (
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
 	"github.com/lmika/awstools/internal/dynamo-browse/ui"
 	"github.com/lmika/gopkgs/cli"
-	"os"
 )
 
 func main() {
@@ -49,8 +50,8 @@ func main() {
 
 	commandController := commandctrl.NewCommandController(map[string]uimodels.Operation{
 		"scan": tableReadController.Scan(),
-		"rw": tableWriteController.ToggleReadWrite(),
-		"dup": tableWriteController.Duplicate(),
+		"rw":   tableWriteController.ToggleReadWrite(),
+		"dup":  tableWriteController.Duplicate(),
 	})
 
 	uiModel := ui.NewModel(uiDispatcher, commandController, tableReadController, tableWriteController)
@@ -77,4 +78,3 @@ type msgLoopback struct {
 func (m *msgLoopback) Send(msg tea.Msg) {
 	m.program.Send(msg)
 }
-
diff --git a/cmd/sqs-browse/main.go b/cmd/sqs-browse/main.go
index ce866a3..edb761f 100644
--- a/cmd/sqs-browse/main.go
+++ b/cmd/sqs-browse/main.go
@@ -4,6 +4,9 @@ import (
 	"context"
 	"flag"
 	"fmt"
+	"log"
+	"os"
+
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/sqs"
 	tea "github.com/charmbracelet/bubbletea"
@@ -17,8 +20,6 @@ import (
 	"github.com/lmika/awstools/internal/sqs-browse/ui"
 	"github.com/lmika/events"
 	"github.com/lmika/gopkgs/cli"
-	"log"
-	"os"
 )
 
 func main() {
@@ -39,7 +40,7 @@ func main() {
 	if err != nil {
 		cli.Fatalf("cannot create workspace file: %v", err)
 	}
-	workspaceFile.Close()		// We just need the filename
+	workspaceFile.Close() // We just need the filename
 
 	msgStore, err := stormstore.NewStore(workspaceFile.Name())
 	if err != nil {
diff --git a/cmd/sqs-drain/main.go b/cmd/sqs-drain/main.go
index cc65159..a2efb9d 100644
--- a/cmd/sqs-drain/main.go
+++ b/cmd/sqs-drain/main.go
@@ -3,15 +3,16 @@ package main
 import (
 	"context"
 	"flag"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
+
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/sqs"
 	"github.com/aws/aws-sdk-go-v2/service/sqs/types"
 	"github.com/pkg/errors"
-	"log"
-	"os"
-	"path/filepath"
-	"time"
 
 	"github.com/lmika/gopkgs/cli"
 )
@@ -44,9 +45,9 @@ func main() {
 	msgCount := 0
 	for {
 		out, err := client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
-			QueueUrl: aws.String(*flagQueue),
+			QueueUrl:            aws.String(*flagQueue),
 			MaxNumberOfMessages: 10,
-			WaitTimeSeconds: 1,
+			WaitTimeSeconds:     1,
 		})
 		if err != nil {
 			log.Fatalf("error receiving messages: %v", err)
@@ -59,7 +60,7 @@ func main() {
 		for _, msg := range out.Messages {
 			if err := handleMessage(ctx, outDir, msg); err == nil {
 				messagesToDelete = append(messagesToDelete, types.DeleteMessageBatchRequestEntry{
-					Id: msg.MessageId,
+					Id:            msg.MessageId,
 					ReceiptHandle: msg.ReceiptHandle,
 				})
 				msgCount += 1
@@ -74,7 +75,7 @@ func main() {
 
 		if _, err := client.DeleteMessageBatch(ctx, &sqs.DeleteMessageBatchInput{
 			QueueUrl: aws.String(*flagQueue),
-			Entries: messagesToDelete,
+			Entries:  messagesToDelete,
 		}); err != nil {
 			log.Printf("error deleting messages from queue: %v", err)
 			break
@@ -85,7 +86,7 @@ func main() {
 }
 
 func handleMessage(ctx context.Context, outDir string, msg types.Message) error {
-	outFile := filepath.Join(outDir, aws.ToString(msg.MessageId) + ".json")
+	outFile := filepath.Join(outDir, aws.ToString(msg.MessageId)+".json")
 	msgBody := aws.ToString(msg.Body)
 
 	log.Printf("%v -> %v", aws.ToString(msg.MessageId), outFile)
diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go
index ef26530..0d827a9 100644
--- a/internal/common/ui/commandctrl/commandctrl.go
+++ b/internal/common/ui/commandctrl/commandctrl.go
@@ -2,11 +2,12 @@ package commandctrl
 
 import (
 	"context"
+	"strings"
+
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/shellwords"
 	"github.com/pkg/errors"
-	"strings"
 )
 
 type CommandController struct {
@@ -45,4 +46,4 @@ func (c *CommandController) Execute() uimodels.Operation {
 
 		return command.Execute(WithCommandArgs(ctx, tokens[1:]))
 	})
-}
\ No newline at end of file
+}
diff --git a/internal/common/ui/commandctrl/commandctrl_test.go b/internal/common/ui/commandctrl/commandctrl_test.go
index 0391107..93c4c26 100644
--- a/internal/common/ui/commandctrl/commandctrl_test.go
+++ b/internal/common/ui/commandctrl/commandctrl_test.go
@@ -2,11 +2,12 @@ package commandctrl_test
 
 import (
 	"context"
+	"testing"
+
 	"github.com/lmika/awstools/internal/common/ui/commandctrl"
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/test/testuictx"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestCommandController_Prompt(t *testing.T) {
diff --git a/internal/common/ui/commandctrl/context.go b/internal/common/ui/commandctrl/context.go
index 6728126..5417c97 100644
--- a/internal/common/ui/commandctrl/context.go
+++ b/internal/common/ui/commandctrl/context.go
@@ -2,7 +2,8 @@ package commandctrl
 
 import "context"
 
-type commandArgContextKeyType struct {}
+type commandArgContextKeyType struct{}
+
 var commandArgContextKey = commandArgContextKeyType{}
 
 func WithCommandArgs(ctx context.Context, args []string) context.Context {
@@ -12,4 +13,4 @@ func WithCommandArgs(ctx context.Context, args []string) context.Context {
 func CommandArgs(ctx context.Context) []string {
 	args, _ := ctx.Value(commandArgContextKey).([]string)
 	return args
-}
\ No newline at end of file
+}
diff --git a/internal/common/ui/dispatcher/context.go b/internal/common/ui/dispatcher/context.go
index 19b1a50..46e2fc2 100644
--- a/internal/common/ui/dispatcher/context.go
+++ b/internal/common/ui/dispatcher/context.go
@@ -2,6 +2,7 @@ package dispatcher
 
 import (
 	"fmt"
+
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
diff --git a/internal/common/ui/dispatcher/dispatcher.go b/internal/common/ui/dispatcher/dispatcher.go
index 0693143..946c356 100644
--- a/internal/common/ui/dispatcher/dispatcher.go
+++ b/internal/common/ui/dispatcher/dispatcher.go
@@ -2,10 +2,11 @@ package dispatcher
 
 import (
 	"context"
+	"sync"
+
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/pkg/errors"
-	"sync"
 )
 
 type Dispatcher struct {
@@ -14,7 +15,6 @@ type Dispatcher struct {
 	publisher MessagePublisher
 }
 
-
 func NewDispatcher(publisher MessagePublisher) *Dispatcher {
 	return &Dispatcher{
 		mutex:     new(sync.Mutex),
@@ -44,6 +44,3 @@ func (d *Dispatcher) Start(ctx context.Context, operation uimodels.Operation) {
 		d.runningOp = nil
 	}()
 }
-
-
-
diff --git a/internal/common/ui/dispatcher/iface.go b/internal/common/ui/dispatcher/iface.go
index 064624d..2af3399 100644
--- a/internal/common/ui/dispatcher/iface.go
+++ b/internal/common/ui/dispatcher/iface.go
@@ -4,4 +4,4 @@ import tea "github.com/charmbracelet/bubbletea"
 
 type MessagePublisher interface {
 	Send(msg tea.Msg)
-}
\ No newline at end of file
+}
diff --git a/internal/common/ui/events/errors.go b/internal/common/ui/events/errors.go
index 35fe5be..0c031b6 100644
--- a/internal/common/ui/events/errors.go
+++ b/internal/common/ui/events/errors.go
@@ -14,4 +14,4 @@ type Message string
 type PromptForInput struct {
 	Prompt string
 	OnDone uimodels.Operation
-}
\ No newline at end of file
+}
diff --git a/internal/common/ui/uimodels/context.go b/internal/common/ui/uimodels/context.go
index dae4ab4..9918aee 100644
--- a/internal/common/ui/uimodels/context.go
+++ b/internal/common/ui/uimodels/context.go
@@ -2,7 +2,8 @@ package uimodels
 
 import "context"
 
-type uiContextKeyType struct {}
+type uiContextKeyType struct{}
+
 var uiContextKey = uiContextKeyType{}
 
 func Ctx(ctx context.Context) UIContext {
diff --git a/internal/common/ui/uimodels/operations.go b/internal/common/ui/uimodels/operations.go
index c1f504a..4eabbee 100644
--- a/internal/common/ui/uimodels/operations.go
+++ b/internal/common/ui/uimodels/operations.go
@@ -11,4 +11,3 @@ type OperationFn func(ctx context.Context) error
 func (f OperationFn) Execute(ctx context.Context) error {
 	return f(ctx)
 }
-
diff --git a/internal/common/ui/uimodels/promptvalue.go b/internal/common/ui/uimodels/promptvalue.go
index d829687..7ac33de 100644
--- a/internal/common/ui/uimodels/promptvalue.go
+++ b/internal/common/ui/uimodels/promptvalue.go
@@ -2,7 +2,8 @@ package uimodels
 
 import "context"
 
-type promptValueKeyType struct {}
+type promptValueKeyType struct{}
+
 var promptValueKey = promptValueKeyType{}
 
 func PromptValue(ctx context.Context) string {
diff --git a/internal/dynamo-browse/controllers/events.go b/internal/dynamo-browse/controllers/events.go
index f911744..1289b94 100644
--- a/internal/dynamo-browse/controllers/events.go
+++ b/internal/dynamo-browse/controllers/events.go
@@ -8,4 +8,4 @@ type NewResultSet struct {
 
 type SetReadWrite struct {
 	NewValue bool
-}
\ No newline at end of file
+}
diff --git a/internal/dynamo-browse/controllers/state.go b/internal/dynamo-browse/controllers/state.go
index db2174b..a711e6d 100644
--- a/internal/dynamo-browse/controllers/state.go
+++ b/internal/dynamo-browse/controllers/state.go
@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 )
 
diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go
index 77af19f..94ade82 100644
--- a/internal/dynamo-browse/controllers/tableread.go
+++ b/internal/dynamo-browse/controllers/tableread.go
@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go
index 435a574..57b252c 100644
--- a/internal/dynamo-browse/controllers/tablewrite.go
+++ b/internal/dynamo-browse/controllers/tablewrite.go
@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/dynamo-browse/models/modexpr"
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
@@ -9,16 +10,16 @@ import (
 )
 
 type TableWriteController struct {
-	tableService *tables.Service
+	tableService         *tables.Service
 	tableReadControllers *TableReadController
-	tableName string
+	tableName            string
 }
 
 func NewTableWriteController(tableService *tables.Service, tableReadControllers *TableReadController, tableName string) *TableWriteController {
 	return &TableWriteController{
-		tableService: tableService,
+		tableService:         tableService,
 		tableReadControllers: tableReadControllers,
-		tableName: tableName,
+		tableName:            tableName,
 	}
 }
 
diff --git a/internal/dynamo-browse/controllers/tablewrite_test.go b/internal/dynamo-browse/controllers/tablewrite_test.go
index b9021db..635878b 100644
--- a/internal/dynamo-browse/controllers/tablewrite_test.go
+++ b/internal/dynamo-browse/controllers/tablewrite_test.go
@@ -2,6 +2,8 @@ package controllers_test
 
 import (
 	"context"
+	"testing"
+
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/dynamo-browse/controllers"
@@ -10,7 +12,6 @@ import (
 	"github.com/lmika/awstools/test/testdynamo"
 	"github.com/lmika/awstools/test/testuictx"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestTableWriteController_ToggleReadWrite(t *testing.T) {
@@ -47,7 +48,10 @@ func TestTableWriteController_Delete(t *testing.T) {
 		twc, ctrls, closeFn := setupController(t)
 		t.Cleanup(closeFn)
 
-		resultSet, err := ctrls.tableService.Scan(context.Background(), ctrls.tableName)
+		ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
+		assert.NoError(t, err)
+
+		resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
 		assert.NoError(t, err)
 		assert.Len(t, resultSet.Items, 3)
 
@@ -71,7 +75,7 @@ func TestTableWriteController_Delete(t *testing.T) {
 		err = promptRequest.OnDone.Execute(uimodels.WithPromptValue(ctx, "y"))
 		assert.NoError(t, err)
 
-		afterResultSet, err := ctrls.tableService.Scan(context.Background(), ctrls.tableName)
+		afterResultSet, err := ctrls.tableService.Scan(context.Background(), ti)
 		assert.NoError(t, err)
 		assert.Len(t, afterResultSet.Items, 2)
 		assert.Contains(t, afterResultSet.Items, resultSet.Items[0])
@@ -83,7 +87,10 @@ func TestTableWriteController_Delete(t *testing.T) {
 		twc, ctrls, closeFn := setupController(t)
 		t.Cleanup(closeFn)
 
-		resultSet, err := ctrls.tableService.Scan(context.Background(), ctrls.tableName)
+		ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
+		assert.NoError(t, err)
+
+		resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
 		assert.NoError(t, err)
 		assert.Len(t, resultSet.Items, 3)
 
@@ -107,7 +114,7 @@ func TestTableWriteController_Delete(t *testing.T) {
 		err = promptRequest.OnDone.Execute(uimodels.WithPromptValue(ctx, "n"))
 		assert.Error(t, err)
 
-		afterResultSet, err := ctrls.tableService.Scan(context.Background(), ctrls.tableName)
+		afterResultSet, err := ctrls.tableService.Scan(context.Background(), ti)
 		assert.NoError(t, err)
 		assert.Len(t, afterResultSet.Items, 3)
 		assert.Contains(t, afterResultSet.Items, resultSet.Items[0])
@@ -119,7 +126,10 @@ func TestTableWriteController_Delete(t *testing.T) {
 		tableWriteController, ctrls, closeFn := setupController(t)
 		t.Cleanup(closeFn)
 
-		resultSet, err := ctrls.tableService.Scan(context.Background(), ctrls.tableName)
+		ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
+		assert.NoError(t, err)
+
+		resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
 		assert.NoError(t, err)
 		assert.Len(t, resultSet.Items, 3)
 
@@ -174,4 +184,4 @@ var testData = testdynamo.TestData{
 		"beta":  2468,
 		"gamma": "foobar",
 	},
-}
\ No newline at end of file
+}
diff --git a/internal/dynamo-browse/models/attrutils.go b/internal/dynamo-browse/models/attrutils.go
index 4757814..cf1855b 100644
--- a/internal/dynamo-browse/models/attrutils.go
+++ b/internal/dynamo-browse/models/attrutils.go
@@ -1,8 +1,9 @@
 package models
 
 import (
-	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"math/big"
+
+	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 )
 
 func compareScalarAttributes(x, y types.AttributeValue) (int, bool) {
diff --git a/internal/dynamo-browse/models/models.go b/internal/dynamo-browse/models/models.go
index 0f0f0d1..332c2f8 100644
--- a/internal/dynamo-browse/models/models.go
+++ b/internal/dynamo-browse/models/models.go
@@ -29,4 +29,4 @@ func (i Item) KeyValue(info *TableInfo) map[string]types.AttributeValue {
 		itemKey[info.Keys.SortKey] = i[info.Keys.SortKey]
 	}
 	return itemKey
-}
\ No newline at end of file
+}
diff --git a/internal/dynamo-browse/models/modexpr/ast.go b/internal/dynamo-browse/models/modexpr/ast.go
index dc2aa2c..e694acb 100644
--- a/internal/dynamo-browse/models/modexpr/ast.go
+++ b/internal/dynamo-browse/models/modexpr/ast.go
@@ -32,4 +32,4 @@ func Parse(expr string) (*ModExpr, error) {
 	}
 
 	return &ModExpr{ast: &ast}, nil
-}
\ No newline at end of file
+}
diff --git a/internal/dynamo-browse/models/modexpr/expr_test.go b/internal/dynamo-browse/models/modexpr/expr_test.go
index 7978665..3b369ea 100644
--- a/internal/dynamo-browse/models/modexpr/expr_test.go
+++ b/internal/dynamo-browse/models/modexpr/expr_test.go
@@ -1,11 +1,12 @@
 package modexpr_test
 
 import (
+	"testing"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/lmika/awstools/internal/dynamo-browse/models/modexpr"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestModExpr_Patch(t *testing.T) {
@@ -26,7 +27,7 @@ func TestModExpr_Patch(t *testing.T) {
 		assert.NoError(t, err)
 
 		oldItem := models.Item{
-			"old": &types.AttributeValueMemberS{Value: "before"},
+			"old":  &types.AttributeValueMemberS{Value: "before"},
 			"beta": &types.AttributeValueMemberS{Value: "before beta"},
 		}
 		newItem, err := modExpr.Patch(oldItem)
@@ -42,7 +43,7 @@ func TestModExpr_Patch(t *testing.T) {
 		assert.NoError(t, err)
 
 		oldItem := models.Item{
-			"old": &types.AttributeValueMemberS{Value: "before"},
+			"old":  &types.AttributeValueMemberS{Value: "before"},
 			"beta": &types.AttributeValueMemberS{Value: "before beta"},
 		}
 		newItem, err := modExpr.Patch(oldItem)
diff --git a/internal/dynamo-browse/models/modexpr/mods.go b/internal/dynamo-browse/models/modexpr/mods.go
index 7a18bff..6839d73 100644
--- a/internal/dynamo-browse/models/modexpr/mods.go
+++ b/internal/dynamo-browse/models/modexpr/mods.go
@@ -10,8 +10,8 @@ type patchMod interface {
 }
 
 type setAttributeMod struct {
-	key  string
-	to   types.AttributeValue
+	key string
+	to  types.AttributeValue
 }
 
 func (sa setAttributeMod) Apply(item models.Item) {
diff --git a/internal/dynamo-browse/models/modexpr/values.go b/internal/dynamo-browse/models/modexpr/values.go
index 7e2222a..15b404b 100644
--- a/internal/dynamo-browse/models/modexpr/values.go
+++ b/internal/dynamo-browse/models/modexpr/values.go
@@ -1,9 +1,10 @@
 package modexpr
 
 import (
+	"strconv"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/pkg/errors"
-	"strconv"
 )
 
 func (a *astLiteralValue) dynamoValue() (types.AttributeValue, error) {
diff --git a/internal/dynamo-browse/models/sorted_test.go b/internal/dynamo-browse/models/sorted_test.go
index 674cb2f..1925711 100644
--- a/internal/dynamo-browse/models/sorted_test.go
+++ b/internal/dynamo-browse/models/sorted_test.go
@@ -1,18 +1,21 @@
 package models_test
 
 import (
+	"testing"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestSort(t *testing.T) {
 	t.Run("pk and sk are both strings", func(t *testing.T) {
+		tableInfo := &models.TableInfo{Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"}}
+
 		items := make([]models.Item, len(testStringData))
 		copy(items, testStringData)
 
-		models.Sort(items, "pk", "sk")
+		models.Sort(items, tableInfo)
 
 		assert.Equal(t, items[0], testStringData[1])
 		assert.Equal(t, items[1], testStringData[2])
@@ -20,10 +23,12 @@ func TestSort(t *testing.T) {
 	})
 
 	t.Run("pk and sk are both numbers", func(t *testing.T) {
+		tableInfo := &models.TableInfo{Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"}}
+
 		items := make([]models.Item, len(testNumberData))
 		copy(items, testNumberData)
 
-		models.Sort(items, "pk", "sk")
+		models.Sort(items, tableInfo)
 
 		assert.Equal(t, items[0], testNumberData[2])
 		assert.Equal(t, items[1], testNumberData[1])
@@ -31,10 +36,12 @@ func TestSort(t *testing.T) {
 	})
 
 	t.Run("pk and sk are both bools", func(t *testing.T) {
+		tableInfo := &models.TableInfo{Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"}}
+
 		items := make([]models.Item, len(testBoolData))
 		copy(items, testBoolData)
 
-		models.Sort(items, "pk", "sk")
+		models.Sort(items, tableInfo)
 
 		assert.Equal(t, items[0], testBoolData[2])
 		assert.Equal(t, items[1], testBoolData[1])
diff --git a/internal/dynamo-browse/providers/dynamo/provider.go b/internal/dynamo-browse/providers/dynamo/provider.go
index 25db663..7636845 100644
--- a/internal/dynamo-browse/providers/dynamo/provider.go
+++ b/internal/dynamo-browse/providers/dynamo/provider.go
@@ -2,6 +2,7 @@ package dynamo
 
 import (
 	"context"
+
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
@@ -42,7 +43,7 @@ func (p *Provider) DescribeTable(ctx context.Context, tableName string) (*models
 func (p *Provider) PutItem(ctx context.Context, name string, item models.Item) error {
 	_, err := p.client.PutItem(ctx, &dynamodb.PutItemInput{
 		TableName: aws.String(name),
-		Item: item,
+		Item:      item,
 	})
 	if err != nil {
 		return errors.Wrapf(err, "cannot execute put on table %v", name)
@@ -73,7 +74,7 @@ func (p *Provider) ScanItems(ctx context.Context, tableName string) ([]models.It
 func (p *Provider) DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error {
 	_, err := p.client.DeleteItem(ctx, &dynamodb.DeleteItemInput{
 		TableName: aws.String(tableName),
-		Key: key,
+		Key:       key,
 	})
 	return errors.Wrap(err, "could not delete item")
-}
\ No newline at end of file
+}
diff --git a/internal/dynamo-browse/providers/dynamo/provider_test.go b/internal/dynamo-browse/providers/dynamo/provider_test.go
index 819453a..a408bc2 100644
--- a/internal/dynamo-browse/providers/dynamo/provider_test.go
+++ b/internal/dynamo-browse/providers/dynamo/provider_test.go
@@ -2,11 +2,12 @@ package dynamo_test
 
 import (
 	"context"
+	"testing"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
 	"github.com/lmika/awstools/test/testdynamo"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestProvider_ScanItems(t *testing.T) {
diff --git a/internal/dynamo-browse/services/tables/iface.go b/internal/dynamo-browse/services/tables/iface.go
index ff5afda..aedae2e 100644
--- a/internal/dynamo-browse/services/tables/iface.go
+++ b/internal/dynamo-browse/services/tables/iface.go
@@ -2,6 +2,7 @@ package tables
 
 import (
 	"context"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 )
diff --git a/internal/dynamo-browse/services/tables/service.go b/internal/dynamo-browse/services/tables/service.go
index 5e95a26..22aab20 100644
--- a/internal/dynamo-browse/services/tables/service.go
+++ b/internal/dynamo-browse/services/tables/service.go
@@ -2,9 +2,10 @@ package tables
 
 import (
 	"context"
+	"sort"
+
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/pkg/errors"
-	"sort"
 )
 
 type Service struct {
@@ -63,9 +64,9 @@ func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*model
 	models.Sort(results, tableInfo)
 
 	return &models.ResultSet{
-		TableInfo:   tableInfo,
-		Columns: columns,
-		Items:   results,
+		TableInfo: tableInfo,
+		Columns:   columns,
+		Items:     results,
 	}, nil
 }
 
diff --git a/internal/dynamo-browse/services/tables/service_test.go b/internal/dynamo-browse/services/tables/service_test.go
index e31a2b8..c5c8b37 100644
--- a/internal/dynamo-browse/services/tables/service_test.go
+++ b/internal/dynamo-browse/services/tables/service_test.go
@@ -2,11 +2,12 @@ package tables_test
 
 import (
 	"context"
+	"testing"
+
 	"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
 	"github.com/lmika/awstools/test/testdynamo"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestService_Describe(t *testing.T) {
diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go
index 045309e..00b994c 100644
--- a/internal/dynamo-browse/ui/model.go
+++ b/internal/dynamo-browse/ui/model.go
@@ -3,6 +3,9 @@ package ui
 import (
 	"context"
 	"fmt"
+	"strings"
+	"text/tabwriter"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	table "github.com/calyptia/go-bubble-table"
 	"github.com/charmbracelet/bubbles/textinput"
@@ -14,8 +17,6 @@ import (
 	"github.com/lmika/awstools/internal/common/ui/events"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/dynamo-browse/controllers"
-	"strings"
-	"text/tabwriter"
 )
 
 var (
@@ -156,7 +157,7 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	// Tea events
 	case tea.WindowSizeMsg:
 		fixedViewsHeight := lipgloss.Height(m.headerView()) + lipgloss.Height(m.splitterView()) + lipgloss.Height(m.footerView())
-		viewportHeight := msg.Height / 2		// TODO: make this dynamic
+		viewportHeight := msg.Height / 2 // TODO: make this dynamic
 		if viewportHeight > 15 {
 			viewportHeight = 15
 		}
diff --git a/internal/dynamo-browse/ui/tblmodel.go b/internal/dynamo-browse/ui/tblmodel.go
index 8609ce0..6cb8d41 100644
--- a/internal/dynamo-browse/ui/tblmodel.go
+++ b/internal/dynamo-browse/ui/tblmodel.go
@@ -2,11 +2,12 @@ package ui
 
 import (
 	"fmt"
+	"io"
+	"strings"
+
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	table "github.com/calyptia/go-bubble-table"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
-	"io"
-	"strings"
 )
 
 type itemTableRow struct {
diff --git a/internal/sqs-browse/controllers/forward.go b/internal/sqs-browse/controllers/forward.go
index 8e538e7..ffd9b62 100644
--- a/internal/sqs-browse/controllers/forward.go
+++ b/internal/sqs-browse/controllers/forward.go
@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 	"github.com/lmika/awstools/internal/sqs-browse/services/messages"
diff --git a/internal/sqs-browse/models/message.go b/internal/sqs-browse/models/message.go
index f8996a6..7a22466 100644
--- a/internal/sqs-browse/models/message.go
+++ b/internal/sqs-browse/models/message.go
@@ -3,9 +3,9 @@ package models
 import "time"
 
 type Message struct {
-	ID       uint64		`storm:"id,increment"`
-	ExtID    string		`storm:"unique"`
-	Queue    string		`storm:"index"`
+	ID       uint64 `storm:"id,increment"`
+	ExtID    string `storm:"unique"`
+	Queue    string `storm:"index"`
 	Received time.Time
 	Data     string
 }
diff --git a/internal/sqs-browse/providers/sqs/provider.go b/internal/sqs-browse/providers/sqs/provider.go
index 77c166b..4307b45 100644
--- a/internal/sqs-browse/providers/sqs/provider.go
+++ b/internal/sqs-browse/providers/sqs/provider.go
@@ -2,13 +2,14 @@ package sqs
 
 import (
 	"context"
+	"log"
+	"time"
+
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/service/sqs"
 	"github.com/aws/aws-sdk-go-v2/service/sqs/types"
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 	"github.com/pkg/errors"
-	"log"
-	"time"
 )
 
 type Provider struct {
@@ -23,7 +24,7 @@ func (p *Provider) SendMessage(ctx context.Context, msg models.Message, queue st
 	// TEMP :: queue URL
 
 	out, err := p.client.SendMessage(ctx, &sqs.SendMessageInput{
-		QueueUrl: aws.String(queue),
+		QueueUrl:    aws.String(queue),
 		MessageBody: aws.String(msg.Data),
 	})
 	if err != nil {
diff --git a/internal/sqs-browse/providers/stormstore/memstore.go b/internal/sqs-browse/providers/stormstore/memstore.go
index 813565f..5825003 100644
--- a/internal/sqs-browse/providers/stormstore/memstore.go
+++ b/internal/sqs-browse/providers/stormstore/memstore.go
@@ -2,6 +2,7 @@ package stormstore
 
 import (
 	"context"
+
 	"github.com/asdine/storm"
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 	"github.com/pkg/errors"
diff --git a/internal/sqs-browse/services/messages/iface.go b/internal/sqs-browse/services/messages/iface.go
index bd8beaa..431d6c5 100644
--- a/internal/sqs-browse/services/messages/iface.go
+++ b/internal/sqs-browse/services/messages/iface.go
@@ -2,9 +2,10 @@ package messages
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 )
 
 type MessageSender interface {
 	SendMessage(ctx context.Context, msg models.Message, queue string) (string, error)
-}
\ No newline at end of file
+}
diff --git a/internal/sqs-browse/services/messages/service.go b/internal/sqs-browse/services/messages/service.go
index 433239f..5cc63c5 100644
--- a/internal/sqs-browse/services/messages/service.go
+++ b/internal/sqs-browse/services/messages/service.go
@@ -2,6 +2,7 @@ package messages
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 	"github.com/pkg/errors"
 )
diff --git a/internal/sqs-browse/services/pollmessage/iface.go b/internal/sqs-browse/services/pollmessage/iface.go
index 7087b28..9a8f7e8 100644
--- a/internal/sqs-browse/services/pollmessage/iface.go
+++ b/internal/sqs-browse/services/pollmessage/iface.go
@@ -2,6 +2,7 @@ package pollmessage
 
 import (
 	"context"
+
 	"github.com/lmika/awstools/internal/sqs-browse/models"
 )
 
@@ -11,4 +12,4 @@ type MessageStore interface {
 
 type MessagePoller interface {
 	PollForNewMessages(ctx context.Context, queue string) ([]*models.Message, error)
-}
\ No newline at end of file
+}
diff --git a/internal/sqs-browse/services/pollmessage/service.go b/internal/sqs-browse/services/pollmessage/service.go
index 99a3669..69d2bef 100644
--- a/internal/sqs-browse/services/pollmessage/service.go
+++ b/internal/sqs-browse/services/pollmessage/service.go
@@ -2,9 +2,10 @@ package pollmessage
 
 import (
 	"context"
+	"log"
+
 	"github.com/lmika/events"
 	"github.com/pkg/errors"
-	"log"
 )
 
 type Service struct {
diff --git a/internal/sqs-browse/ui/events.go b/internal/sqs-browse/ui/events.go
index b7844fb..b80dbdc 100644
--- a/internal/sqs-browse/ui/events.go
+++ b/internal/sqs-browse/ui/events.go
@@ -2,4 +2,4 @@ package ui
 
 import "github.com/lmika/awstools/internal/sqs-browse/models"
 
-type NewMessagesEvent []*models.Message
\ No newline at end of file
+type NewMessagesEvent []*models.Message
diff --git a/internal/sqs-browse/ui/model.go b/internal/sqs-browse/ui/model.go
index 0549222..d5f45df 100644
--- a/internal/sqs-browse/ui/model.go
+++ b/internal/sqs-browse/ui/model.go
@@ -4,6 +4,9 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"log"
+	"strings"
+
 	table "github.com/calyptia/go-bubble-table"
 	"github.com/charmbracelet/bubbles/textinput"
 	"github.com/charmbracelet/bubbles/viewport"
@@ -14,19 +17,17 @@ import (
 	"github.com/lmika/awstools/internal/common/ui/uimodels"
 	"github.com/lmika/awstools/internal/sqs-browse/controllers"
 	"github.com/lmika/awstools/internal/sqs-browse/models"
-	"log"
-	"strings"
 )
 
 var (
 	activeHeaderStyle = lipgloss.NewStyle().
-		Bold(true).
-		Foreground(lipgloss.Color("#ffffff")).
-		Background(lipgloss.Color("#eac610"))
+				Bold(true).
+				Foreground(lipgloss.Color("#ffffff")).
+				Background(lipgloss.Color("#eac610"))
 
 	inactiveHeaderStyle = lipgloss.NewStyle().
-		Foreground(lipgloss.Color("#000000")).
-		Background(lipgloss.Color("#d1d1d1"))
+				Foreground(lipgloss.Color("#000000")).
+				Background(lipgloss.Color("#d1d1d1"))
 )
 
 type uiModel struct {
@@ -52,12 +53,12 @@ func NewModel(dispatcher *dispatcher.Dispatcher, msgSendingHandlers *controllers
 	textInput := textinput.New()
 
 	model := uiModel{
-		table:      tbl,
-		tableRows:  rows,
-		message:    "",
-		textInput:  textInput,
+		table:              tbl,
+		tableRows:          rows,
+		message:            "",
+		textInput:          textInput,
 		msgSendingHandlers: msgSendingHandlers,
-		dispatcher: dispatcher,
+		dispatcher:         dispatcher,
 	}
 
 	return model
diff --git a/internal/sqs-browse/ui/tblmodel.go b/internal/sqs-browse/ui/tblmodel.go
index 1dc2e1a..b4fc15c 100644
--- a/internal/sqs-browse/ui/tblmodel.go
+++ b/internal/sqs-browse/ui/tblmodel.go
@@ -2,10 +2,11 @@ package ui
 
 import (
 	"fmt"
-	"github.com/lmika/awstools/internal/sqs-browse/models"
-	table "github.com/calyptia/go-bubble-table"
 	"io"
 	"strings"
+
+	table "github.com/calyptia/go-bubble-table"
+	"github.com/lmika/awstools/internal/sqs-browse/models"
 )
 
 type messageTableRow models.Message
diff --git a/test/cmd/load-test-table/main.go b/test/cmd/load-test-table/main.go
index 9493caa..dfda661 100644
--- a/test/cmd/load-test-table/main.go
+++ b/test/cmd/load-test-table/main.go
@@ -2,6 +2,8 @@ package main
 
 import (
 	"context"
+	"log"
+
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
@@ -12,7 +14,6 @@ import (
 	"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
 	"github.com/lmika/gopkgs/cli"
-	"log"
 )
 
 func main() {
@@ -52,12 +53,17 @@ func main() {
 		log.Fatalf("warn: cannot create table: %v", tableName)
 	}
 
+	tableInfo := &models.TableInfo{
+		Name: tableName,
+		Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"},
+	}
+
 	dynamoProvider := dynamo.NewProvider(dynamoClient)
 	tableService := tables.NewService(dynamoProvider)
 
 	for i := 0; i < totalItems; i++ {
 		key := uuid.New().String()
-		if err := tableService.Put(ctx, tableName, models.Item{
+		if err := tableService.Put(ctx, tableInfo, models.Item{
 			"pk":      &types.AttributeValueMemberS{Value: key},
 			"sk":      &types.AttributeValueMemberS{Value: key},
 			"name":    &types.AttributeValueMemberS{Value: gofakeit.Name()},
diff --git a/test/testdynamo/client.go b/test/testdynamo/client.go
index 11cb7e1..bb7be11 100644
--- a/test/testdynamo/client.go
+++ b/test/testdynamo/client.go
@@ -2,6 +2,8 @@ package testdynamo
 
 import (
 	"context"
+	"testing"
+
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/credentials"
@@ -9,7 +11,6 @@ import (
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 type TestData []map[string]interface{}
diff --git a/test/testdynamo/helpers.go b/test/testdynamo/helpers.go
index c44743a..a52c052 100644
--- a/test/testdynamo/helpers.go
+++ b/test/testdynamo/helpers.go
@@ -1,10 +1,11 @@
 package testdynamo
 
 import (
+	"testing"
+
 	"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestRecordAsItem(t *testing.T, item map[string]interface{}) models.Item {
diff --git a/test/testuictx/testuictx.go b/test/testuictx/testuictx.go
index 2ef9c22..e5f1b5a 100644
--- a/test/testuictx/testuictx.go
+++ b/test/testuictx/testuictx.go
@@ -2,6 +2,7 @@ package testuictx
 
 import (
 	"context"
+
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/lmika/awstools/internal/common/ui/dispatcher"
 	"github.com/lmika/awstools/internal/common/ui/uimodels"