From 4e7eb77583bca111d259d0558b3f66eb10356ac0 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Sun, 21 Jan 2024 15:39:51 +0000 Subject: [PATCH] fix: add restrictions when setting IDs. Main change: Add restrictions when adding IDs for cards and statuses. Errors are returned when an attempt is made to set an ID lower than 1, or an attempt is made to set an ID that has already been set. Additional changes: - Error types in the board packages are now defined in errors.go. - Small formatting to error messages. --- internal/board/card.go | 29 +++++++++++----------- internal/board/errors.go | 53 ++++++++++++++++++++++++++++++++++++++++ internal/board/status.go | 39 ++++++++++------------------- internal/db/database.go | 48 ++++++++++++++++++++---------------- 4 files changed, 108 insertions(+), 61 deletions(-) create mode 100644 internal/board/errors.go diff --git a/internal/board/card.go b/internal/board/card.go index 944ddf8..9913489 100644 --- a/internal/board/card.go +++ b/internal/board/card.go @@ -1,15 +1,5 @@ package board -import "fmt" - -type CardNotExistError struct { - ID int -} - -func (e CardNotExistError) Error() string { - return fmt.Sprintf("card ID '%d' does not exist in the database", e.ID) -} - // Card represents a card on a Kanban board. type Card struct { ID int @@ -18,12 +8,23 @@ type Card struct { Created string } -// UpdateId updates the ID of the Card value. -func (c *Card) UpdateId(id int) { +// SetID updates the ID of the Card value only if +// the ID is < 1 (i.e. unset). +func (c *Card) SetID(id int) error { + if id < 1 { + return InvalidIDError{id} + } + + if c.ID > 0 { + return IDAlreadySetError{} + } + c.ID = id + + return nil } -// Id returns the ID of the Card value. -func (c *Card) Id() int { +// GetID returns the ID of the Card value. +func (c *Card) GetID() int { return c.ID } diff --git a/internal/board/errors.go b/internal/board/errors.go new file mode 100644 index 0000000..387ee25 --- /dev/null +++ b/internal/board/errors.go @@ -0,0 +1,53 @@ +package board + +import "fmt" + +// CardNotExistError is returned when a card does not exist in the database. +type CardNotExistError struct { + ID int +} + +func (e CardNotExistError) Error() string { + return fmt.Sprintf("card ID '%d' does not exist in the database", e.ID) +} + +// InvalidIDError is an error for when an attempt to set an ID lower than 1 is made. +type InvalidIDError struct { + ID int +} + +func ( e InvalidIDError) Error() string { + return fmt.Sprintf("'%d' is an invalid ID", e.ID) +} + +// IDAlreadySetError is an error for when an attempt is made to set an ID that has already been set. +type IDAlreadySetError struct {} + +func (e IDAlreadySetError) Error() string { + return "the item's ID is already set" +} + +// StatusListEmptyError is an error for unexpected empty list of statuses. +type StatusListEmptyError struct{} + +func (e StatusListEmptyError) Error() string { + return "the status list must not be empty" +} + +// StatusNotExistError is an error for when a status cannot be found in the database. +type StatusNotExistError struct { + ID int +} + +func (e StatusNotExistError) Error() string { + return fmt.Sprintf("status ID '%d' does not exist in the database", e.ID) +} + +// StatusNotEmptyError is an error for when an attempt is made to delete non-empty statuses. +type StatusNotEmptyError struct { + ID int +} + +func (e StatusNotEmptyError) Error() string { + return fmt.Sprintf("status ID '%d' must contain no cards before deletion", e.ID) +} diff --git a/internal/board/status.go b/internal/board/status.go index 6ac1d64..2a38fc0 100644 --- a/internal/board/status.go +++ b/internal/board/status.go @@ -1,32 +1,9 @@ package board import ( - "fmt" "sort" ) -type StatusListEmptyError struct{} - -func (e StatusListEmptyError) Error() string { - return "the status list must not be empty" -} - -type StatusNotExistError struct { - ID int -} - -func (e StatusNotExistError) Error() string { - return fmt.Sprintf("status ID '%d' does not exist in the database", e.ID) -} - -type StatusNotEmptyError struct { - ID int -} - -func (e StatusNotEmptyError) Error() string { - return fmt.Sprintf("status ID '%d' must contain no cards before deletion", e.ID) -} - // Status represents the status of the Kanban board. type Status struct { ID int @@ -36,12 +13,22 @@ type Status struct { } // UpdateID updates the ID of the Status value. -func (s *Status) UpdateId(id int) { +func (s *Status) SetID(id int) error { + if id < 1 { + return InvalidIDError{id} + } + + if s.ID > 0 { + return IDAlreadySetError{} + } + s.ID = id + + return nil } -// Id returns the ID of the Status value. -func (s *Status) Id() int { +// GetID returns the ID of the Status value. +func (s *Status) GetID() int { return s.ID } diff --git a/internal/db/database.go b/internal/db/database.go index 6f54980..57e1b60 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -18,8 +18,8 @@ const ( ) type BoltItem interface { - UpdateId(int) - Id() int + SetID(int) error + GetID() int } // OpenDatabase opens the database, at a given path, for reading and writing. @@ -65,7 +65,7 @@ func Read(db *bolt.DB, bucketName string, itemID int) ([]byte, error) { return nil }); err != nil { - return nil, fmt.Errorf("error while reading the Bolt item from the database, %w", err) + return nil, fmt.Errorf("error while reading the Bolt item from the database; %w", err) } return data, nil @@ -116,13 +116,13 @@ func ReadAll(db *bolt.DB, bucketName string) ([][]byte, error) { return nil }); err != nil { - return fmt.Errorf("unable to load status, %w", err) + return fmt.Errorf("unable to load the Bolt item; %w", err) } return nil }) if err != nil { - return nil, fmt.Errorf("error while loading statuses from the database, %w", err) + return nil, fmt.Errorf("error while loading the Bolt items from the database; %w", err) } return output, nil @@ -140,12 +140,15 @@ func Write(db *bolt.DB, bucketName string, item BoltItem) (int, error) { return bucketNotExistError{bucket: string(bucketNameBytes)} } - if item.Id() < 1 { + if item.GetID() < 1 { var id uint64 if id, err = bucket.NextSequence(); err != nil { - return fmt.Errorf("unable to generate an ID for the card, %w", err) + return fmt.Errorf("unable to generate an ID for the Bolt item; %w", err) + } + + if err = item.SetID(int(id)); err != nil { + return fmt.Errorf("unable to set the generated ID to the Bolt item; %w", err) } - item.UpdateId(int(id)) } buf := new(bytes.Buffer) @@ -154,17 +157,17 @@ func Write(db *bolt.DB, bucketName string, item BoltItem) (int, error) { return fmt.Errorf("unable to encode data, %w", err) } - if err = bucket.Put([]byte(strconv.Itoa(item.Id())), buf.Bytes()); err != nil { - return fmt.Errorf("unable to write the card to the bucket, %w", err) + if err = bucket.Put([]byte(strconv.Itoa(item.GetID())), buf.Bytes()); err != nil { + return fmt.Errorf("unable to write the Bolt item to the bucket; %w", err) } return nil }) if err != nil { - return 0, fmt.Errorf("error while saving the card to the database, %w", err) + return 0, fmt.Errorf("error while saving the Bolt item to the database; %w", err) } - return item.Id(), nil + return item.GetID(), nil } // WriteMany saves one or more Bolt items to the status bucket. @@ -187,12 +190,15 @@ func WriteMany(database *bolt.DB, bucketName string, items []BoltItem) ([]int, e for ind, item := range items { var err error - if item.Id() < 1 { + if item.GetID() < 1 { var id uint64 if id, err = bucket.NextSequence(); err != nil { return fmt.Errorf("unable to generate ID, %w", err) } - item.UpdateId(int(id)) + + if err = item.SetID(int(id)); err != nil { + return fmt.Errorf("unable to set the generated ID to the Bolt item; %w", err) + } } buf := new(bytes.Buffer) @@ -201,11 +207,11 @@ func WriteMany(database *bolt.DB, bucketName string, items []BoltItem) ([]int, e return fmt.Errorf("unable to encode data, %w", err) } - if err = bucket.Put([]byte(strconv.Itoa(item.Id())), buf.Bytes()); err != nil { + if err = bucket.Put([]byte(strconv.Itoa(item.GetID())), buf.Bytes()); err != nil { return fmt.Errorf("unable to add the Bolt Item to the %s bucket, %w", bucketName, err) } - ids[ind] = item.Id() + ids[ind] = item.GetID() } return nil @@ -229,12 +235,12 @@ func Delete(db *bolt.DB, bucketName string, itemID int) error { } if err := bucket.Delete([]byte(strconv.Itoa(itemID))); err != nil { - return fmt.Errorf("an error occurred when deleting Bolt item '%d', %w", itemID, err) + return fmt.Errorf("an error occurred when deleting Bolt item '%d'; %w", itemID, err) } return nil }); err != nil { - return fmt.Errorf("error deleting data from the '%s' bucket, %w", bucketName, err) + return fmt.Errorf("error deleting data from the '%s' bucket; %w", bucketName, err) } return nil @@ -245,7 +251,7 @@ func mkDataDir(path string) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o700); err != nil { - return fmt.Errorf("error while making directory %s, %w", dir, err) + return fmt.Errorf("error while making directory %s; %w", dir, err) } return nil @@ -258,14 +264,14 @@ func ensureBuckets(db *bolt.DB) error { err := db.Update(func(tx *bolt.Tx) error { for _, v := range buckets { if _, err := tx.CreateBucketIfNotExists([]byte(v)); err != nil { - return fmt.Errorf("unable to ensure that %s bucket is created in the database, %w", v, err) + return fmt.Errorf("unable to ensure that %s bucket is created in the database; %w", v, err) } } return nil }) if err != nil { - return fmt.Errorf("error while ensuring buckets exist in the database, %w", err) + return fmt.Errorf("error while ensuring buckets exist in the database; %w", err) } return nil