commit 2c9d49c266c5387ea1d1eef0b8270135b0ca341e
parent b8b412d0cee2fe92f4493fde95964f87f7966860
Author: Sean Enck <sean@ttypty.com>
Date: Fri, 16 Aug 2024 22:18:31 -0400
sequence rewrite allows sharing of singular external entity type for queries and transactions
Diffstat:
8 files changed, 68 insertions(+), 87 deletions(-)
diff --git a/internal/app/move.go b/internal/app/move.go
@@ -98,5 +98,5 @@ func (r moveRequest) do(dryRun bool) error {
if dryRun {
return nil
}
- return tx.Move(srcExists.Transaction(), r.dst)
+ return tx.Move(srcExists, r.dst)
}
diff --git a/internal/app/remove.go b/internal/app/remove.go
@@ -4,8 +4,6 @@ package app
import (
"errors"
"fmt"
-
- "github.com/seanenck/lockbox/internal/backend"
)
// Remove will remove an entry
@@ -34,14 +32,7 @@ func Remove(cmd CommandOptions) error {
fmt.Fprintln(w, "")
}
if cmd.Confirm(fmt.Sprintf("delete entr%s", postfixRemove)) {
- removals := func() []backend.TransactionEntity {
- var tx []backend.TransactionEntity
- for _, e := range existings {
- tx = append(tx, e.Transaction())
- }
- return tx
- }()
- if err := t.RemoveAll(removals); err != nil {
+ if err := t.RemoveAll(existings); err != nil {
return fmt.Errorf("unable to remove: %w", err)
}
}
diff --git a/internal/backend/actions.go b/internal/backend/actions.go
@@ -182,11 +182,14 @@ func findAndDo(isAdd bool, entityName string, offset []string, opEntity *gokeepa
}
// Move will move a src object to a dst location
-func (t *Transaction) Move(src TransactionEntity, dst string) error {
- if strings.TrimSpace(src.path) == "" {
+func (t *Transaction) Move(src *Entity, dst string) error {
+ if src == nil {
+ return errors.New("source entity is not set")
+ }
+ if strings.TrimSpace(src.Path) == "" {
return errors.New("empty path not allowed")
}
- if strings.TrimSpace(src.value) == "" {
+ if strings.TrimSpace(src.Value) == "" {
return errors.New("empty secret not allowed")
}
mod := config.EnvModTime.Get()
@@ -202,22 +205,22 @@ func (t *Transaction) Move(src TransactionEntity, dst string) error {
if err != nil {
return err
}
- sOffset, sTitle, err := splitComponents(src.path)
+ sOffset, sTitle, err := splitComponents(src.Path)
if err != nil {
return err
}
action := MoveAction
- if dst == src.path {
+ if dst == src.Path {
action = InsertAction
}
- hook, err := NewHook(src.path, action)
+ hook, err := NewHook(src.Path, action)
if err != nil {
return err
}
if err := hook.Run(HookPre); err != nil {
return err
}
- multi := len(strings.Split(strings.TrimSpace(src.value), "\n")) > 1
+ multi := len(strings.Split(strings.TrimSpace(src.Value), "\n")) > 1
err = t.change(func(c Context) error {
c.removeEntity(sOffset, sTitle)
if action == MoveAction {
@@ -229,7 +232,7 @@ func (t *Transaction) Move(src TransactionEntity, dst string) error {
if multi {
field = notesKey
}
- v := src.value
+ v := src.Value
ok, err := isTOTP(dTitle)
if err != nil {
return err
@@ -254,19 +257,19 @@ func (t *Transaction) Move(src TransactionEntity, dst string) error {
// Insert is a move to the same location
func (t *Transaction) Insert(path, val string) error {
- return t.Move(TransactionEntity{path: path, value: val}, path)
+ return t.Move(&Entity{Path: path, Value: val}, path)
}
// Remove will remove a single entity
-func (t *Transaction) Remove(entity *TransactionEntity) error {
+func (t *Transaction) Remove(entity *Entity) error {
if entity == nil {
return errors.New("entity is empty/invalid")
}
- return t.RemoveAll([]TransactionEntity{*entity})
+ return t.RemoveAll([]Entity{*entity})
}
// RemoveAll handles removing elements
-func (t *Transaction) RemoveAll(entities []TransactionEntity) error {
+func (t *Transaction) RemoveAll(entities []Entity) error {
if len(entities) == 0 {
return errors.New("no entities given")
}
@@ -278,11 +281,11 @@ func (t *Transaction) RemoveAll(entities []TransactionEntity) error {
removals := []removal{}
hasHooks := false
for _, entity := range entities {
- offset, title, err := splitComponents(entity.path)
+ offset, title, err := splitComponents(entity.Path)
if err != nil {
return err
}
- hook, err := NewHook(entity.path, RemoveAction)
+ hook, err := NewHook(entity.Path, RemoveAction)
if err != nil {
return err
}
diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go
@@ -97,7 +97,10 @@ func TestMove(t *testing.T) {
setup(t)
fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass")
fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass")
- if err := fullSetup(t, true).Move(backend.QueryEntity{Path: backend.NewPath("test", "test2", "test3"), Value: "abc"}.Transaction(), backend.NewPath("test1", "test2", "test3")); err != nil {
+ if err := fullSetup(t, true).Move(nil, ""); err == nil || err.Error() != "source entity is not set" {
+ t.Errorf("no error: %v", err)
+ }
+ if err := fullSetup(t, true).Move(&backend.Entity{Path: backend.NewPath("test", "test2", "test3"), Value: "abc"}, backend.NewPath("test1", "test2", "test3")); err != nil {
t.Errorf("no error: %v", err)
}
q, err := fullSetup(t, true).Get(backend.NewPath("test1", "test2", "test3"), backend.SecretValue)
@@ -107,7 +110,7 @@ func TestMove(t *testing.T) {
if q.Value != "abc" {
t.Errorf("invalid retrieval")
}
- if err := fullSetup(t, true).Move(backend.QueryEntity{Path: backend.NewPath("test", "test2", "test1"), Value: "test"}.Transaction(), backend.NewPath("test1", "test2", "test3")); err != nil {
+ if err := fullSetup(t, true).Move(&backend.Entity{Path: backend.NewPath("test", "test2", "test1"), Value: "test"}, backend.NewPath("test1", "test2", "test3")); err != nil {
t.Errorf("no error: %v", err)
}
q, err = fullSetup(t, true).Get(backend.NewPath("test1", "test2", "test3"), backend.SecretValue)
@@ -176,10 +179,10 @@ func TestRemoves(t *testing.T) {
if err := setup(t).Remove(nil); err.Error() != "entity is empty/invalid" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).Remove(&backend.TransactionEntity{}); err.Error() != "input paths must contain at LEAST 2 components" {
+ if err := setup(t).Remove(&backend.Entity{}); err.Error() != "input paths must contain at LEAST 2 components" {
t.Errorf("wrong error: %v", err)
}
- tx := backend.QueryEntity{Path: backend.NewPath("test1", "test2", "test3")}.Transaction()
+ tx := backend.Entity{Path: backend.NewPath("test1", "test2", "test3")}
if err := setup(t).Remove(&tx); err.Error() != "failed to remove entity" {
t.Errorf("wrong error: %v", err)
}
@@ -187,14 +190,14 @@ func TestRemoves(t *testing.T) {
for _, i := range []string{"test1", "test2"} {
fullSetup(t, true).Insert(backend.NewPath(i, i, i), "pass")
}
- tx = backend.QueryEntity{Path: backend.NewPath("test1", "test1", "test1")}.Transaction()
+ tx = backend.Entity{Path: backend.NewPath("test1", "test1", "test1")}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test2", "test2", "test2")); err != nil {
t.Errorf("invalid check: %v", err)
}
- tx = backend.QueryEntity{Path: backend.NewPath("test2", "test2", "test2")}.Transaction()
+ tx = backend.Entity{Path: backend.NewPath("test2", "test2", "test2")}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
@@ -202,35 +205,35 @@ func TestRemoves(t *testing.T) {
for _, i := range []string{backend.NewPath("test", "test", "test1"), backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test", "test3"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")} {
fullSetup(t, true).Insert(i, "pass")
}
- tx = backend.QueryEntity{Path: "test/test/test3"}.Transaction()
+ tx = backend.Entity{Path: "test/test/test3"}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test", "test1"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")); err != nil {
t.Errorf("invalid check: %v", err)
}
- tx = backend.QueryEntity{Path: "test/test/test1"}.Transaction()
+ tx = backend.Entity{Path: "test/test/test1"}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")); err != nil {
t.Errorf("invalid check: %v", err)
}
- tx = backend.QueryEntity{Path: "test/test1/test5"}.Transaction()
+ tx = backend.Entity{Path: "test/test1/test5"}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test1", "test2")); err != nil {
t.Errorf("invalid check: %v", err)
}
- tx = backend.QueryEntity{Path: "test/test1/test2"}.Transaction()
+ tx = backend.Entity{Path: "test/test1/test2"}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test", "test", "test2")); err != nil {
t.Errorf("invalid check: %v", err)
}
- tx = backend.QueryEntity{Path: "test/test/test2"}.Transaction()
+ tx = backend.Entity{Path: "test/test/test2"}
if err := fullSetup(t, true).Remove(&tx); err != nil {
t.Errorf("wrong error: %v", err)
}
@@ -240,14 +243,14 @@ func TestRemoveAlls(t *testing.T) {
if err := setup(t).RemoveAll(nil); err.Error() != "no entities given" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).RemoveAll([]backend.TransactionEntity{}); err.Error() != "no entities given" {
+ if err := setup(t).RemoveAll([]backend.Entity{}); err.Error() != "no entities given" {
t.Errorf("wrong error: %v", err)
}
setup(t)
for _, i := range []string{backend.NewPath("test", "test", "test1"), backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test", "test3"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")} {
fullSetup(t, true).Insert(i, "pass")
}
- if err := fullSetup(t, true).RemoveAll([]backend.TransactionEntity{backend.QueryEntity{Path: "test/test/test3"}.Transaction(), backend.QueryEntity{Path: "test/test/test1"}.Transaction()}); err != nil {
+ if err := fullSetup(t, true).RemoveAll([]backend.Entity{{Path: "test/test/test3"}, {Path: "test/test/test1"}}); err != nil {
t.Errorf("wrong error: %v", err)
}
if err := check(t, backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")); err != nil {
diff --git a/internal/backend/core.go b/internal/backend/core.go
@@ -27,7 +27,7 @@ const (
type (
// QuerySeq2 wraps the iteration for query entities
- QuerySeq2 iter.Seq2[QueryEntity, error]
+ QuerySeq2 iter.Seq2[Entity, error]
// Transaction handles the overall operation of the transaction
Transaction struct {
valid bool
@@ -40,24 +40,13 @@ type (
Context struct {
db *gokeepasslib.Database
}
- // QueryEntity is the result of a query
- QueryEntity struct {
- Path string
- Value string
- backing gokeepasslib.Entry
- }
- // TransactionEntity objects alter the database entities
- TransactionEntity struct {
- path string
- value string
+ // Entity are database objects from results and transactional changes
+ Entity struct {
+ Path string
+ Value string
}
)
-// Transaction will convert a query entity to a transaction entity
-func (e QueryEntity) Transaction() TransactionEntity {
- return TransactionEntity{path: e.Path, value: e.Value}
-}
-
// Load will load a kdbx file for transactions
func Load(file string) (*Transaction, error) {
return loadFile(file, true)
@@ -186,7 +175,7 @@ func NewPath(segments ...string) string {
}
// Directory gets the offset location of the entry without the 'name'
-func (e QueryEntity) Directory() string {
+func (e Entity) Directory() string {
return Directory(e.Path)
}
@@ -219,8 +208,8 @@ func IsDirectory(path string) bool {
}
// Collect will create a slice from an iterable set of query sequence results
-func (s QuerySeq2) Collect() ([]QueryEntity, error) {
- var entities []QueryEntity
+func (s QuerySeq2) Collect() ([]Entity, error) {
+ var entities []Entity
for entity, err := range s {
if err != nil {
return nil, err
diff --git a/internal/backend/core_test.go b/internal/backend/core_test.go
@@ -2,7 +2,6 @@ package backend_test
import (
"errors"
- "fmt"
"testing"
"github.com/seanenck/lockbox/internal/backend"
@@ -32,14 +31,6 @@ func TestIsDirectory(t *testing.T) {
}
}
-func TestQueryToTransaction(t *testing.T) {
- q := backend.QueryEntity{Path: "abc", Value: "xyz"}
- tx := q.Transaction()
- if fmt.Sprintf("%v", tx) != "{abc xyz}" {
- t.Errorf("invalid transaction: %v", tx)
- }
-}
-
func TestBase(t *testing.T) {
b := backend.Base("")
if b != "" {
@@ -83,19 +74,19 @@ func TestDirectory(t *testing.T) {
}
func TestEntityDir(t *testing.T) {
- q := backend.QueryEntity{Path: backend.NewPath("abc", "xyz")}
+ q := backend.Entity{Path: backend.NewPath("abc", "xyz")}
if q.Directory() != "abc" {
t.Error("invalid query directory")
}
- q = backend.QueryEntity{Path: backend.NewPath("abc", "xyz", "111")}
+ q = backend.Entity{Path: backend.NewPath("abc", "xyz", "111")}
if q.Directory() != "abc/xyz" {
t.Error("invalid query directory")
}
- q = backend.QueryEntity{Path: ""}
+ q = backend.Entity{Path: ""}
if q.Directory() != "" {
t.Error("invalid query directory")
}
- q = backend.QueryEntity{Path: backend.NewPath("abc")}
+ q = backend.Entity{Path: backend.NewPath("abc")}
if q.Directory() != "" {
t.Error("invalid query directory")
}
@@ -115,23 +106,23 @@ func TestNewSuffix(t *testing.T) {
}
func generateTestSeq(hasError, extra bool) backend.QuerySeq2 {
- return func(yield func(backend.QueryEntity, error) bool) {
- if !yield(backend.QueryEntity{}, nil) {
+ return func(yield func(backend.Entity, error) bool) {
+ if !yield(backend.Entity{}, nil) {
return
}
- if !yield(backend.QueryEntity{}, nil) {
+ if !yield(backend.Entity{}, nil) {
return
}
if hasError {
- if !yield(backend.QueryEntity{}, errors.New("test collect error")) {
+ if !yield(backend.Entity{}, errors.New("test collect error")) {
return
}
}
- if !yield(backend.QueryEntity{}, nil) {
+ if !yield(backend.Entity{}, nil) {
return
}
if extra {
- if !yield(backend.QueryEntity{}, nil) {
+ if !yield(backend.Entity{}, nil) {
return
}
}
diff --git a/internal/backend/query.go b/internal/backend/query.go
@@ -55,7 +55,7 @@ const (
)
// MatchPath will try to match 1 or more elements (more elements when globbing)
-func (t *Transaction) MatchPath(path string) ([]QueryEntity, error) {
+func (t *Transaction) MatchPath(path string) ([]Entity, error) {
if !strings.HasSuffix(path, isGlob) {
e, err := t.Get(path, BlankValue)
if err != nil {
@@ -64,7 +64,7 @@ func (t *Transaction) MatchPath(path string) ([]QueryEntity, error) {
if e == nil {
return nil, nil
}
- return []QueryEntity{*e}, nil
+ return []Entity{*e}, nil
}
prefix := strings.TrimSuffix(path, isGlob)
if strings.HasSuffix(prefix, pathSep) {
@@ -74,7 +74,7 @@ func (t *Transaction) MatchPath(path string) ([]QueryEntity, error) {
}
// Get will request a singular entity
-func (t *Transaction) Get(path string, mode ValueMode) (*QueryEntity, error) {
+func (t *Transaction) Get(path string, mode ValueMode) (*Entity, error) {
_, _, err := splitComponents(path)
if err != nil {
return nil, err
@@ -108,7 +108,7 @@ func forEach(offset string, groups []gokeepasslib.Group, entries []gokeepasslib.
}
}
-func (t *Transaction) queryCollect(args QueryOptions) ([]QueryEntity, error) {
+func (t *Transaction) queryCollect(args QueryOptions) ([]Entity, error) {
e, err := t.QueryCallback(args)
if err != nil {
return nil, err
@@ -121,7 +121,11 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
if args.Mode == noneMode {
return nil, errors.New("no query mode specified")
}
- var entities []QueryEntity
+ type entity struct {
+ path string
+ backing gokeepasslib.Entry
+ }
+ var entities []entity
isSort := args.Mode != ExactMode
decrypt := args.Values != BlankValue
err := t.act(func(ctx Context) error {
@@ -152,10 +156,10 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
}
}
}
- obj := QueryEntity{backing: entry, Path: path}
+ obj := entity{backing: entry, path: path}
if isSort && len(entities) > 0 {
- i, _ := slices.BinarySearchFunc(entities, obj, func(i, j QueryEntity) int {
- return strings.Compare(i.Path, j.Path)
+ i, _ := slices.BinarySearchFunc(entities, obj, func(i, j entity) int {
+ return strings.Compare(i.path, j.path)
})
entities = slices.Insert(entities, i, obj)
} else {
@@ -185,9 +189,9 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
return nil, err
}
}
- return func(yield func(QueryEntity, error) bool) {
+ return func(yield func(Entity, error) bool) {
for _, item := range entities {
- entity := QueryEntity{Path: item.Path}
+ entity := Entity{Path: item.path}
var err error
if args.Values != BlankValue {
val := getValue(item.backing, notesKey)
diff --git a/internal/backend/query_test.go b/internal/backend/query_test.go
@@ -156,7 +156,7 @@ func TestValueModes(t *testing.T) {
}
}
-func testCollect(t *testing.T, count int, seq backend.QuerySeq2) []backend.QueryEntity {
+func testCollect(t *testing.T, count int, seq backend.QuerySeq2) []backend.Entity {
collected, err := seq.Collect()
if err != nil {
t.Errorf("invalid collect error: %v", err)