-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Lab 7 - Errors implementation (#573)
- Loading branch information
Kasun Fernando
authored
Sep 1, 2019
1 parent
00b50aa
commit 14cef3d
Showing
8 changed files
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
type ErrCode uint32 | ||
|
||
type Error struct { | ||
Message string | ||
Code ErrCode | ||
} | ||
|
||
const ( | ||
ErrInvalid ErrCode = 400 | ||
ErrNotFound ErrCode = 404 | ||
) | ||
|
||
func (e ErrCode) String() string { | ||
switch e { | ||
case ErrInvalid: | ||
return "invalid input: %v" | ||
case ErrNotFound: | ||
return "not found: %v" | ||
default: | ||
return "error occurred" | ||
} | ||
} | ||
|
||
func (e *Error) Error() string { | ||
return e.Message | ||
} | ||
|
||
func NewError(code ErrCode, args ...interface{}) *Error { | ||
return &Error{ | ||
Message: fmt.Sprintf(code.String(), args...), | ||
Code: code, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestErrCodeString(t *testing.T) { | ||
assert.Equal(t, "invalid input: %v", ErrInvalid.String()) | ||
assert.Equal(t, "not found: %v", ErrNotFound.String()) | ||
|
||
var ErrFoo ErrCode = 900 | ||
assert.Equal(t, "error occurred", ErrFoo.String()) | ||
} | ||
|
||
func TestError(t *testing.T) { | ||
err := NewError(ErrNotFound, "error message") | ||
assert.Equal(t, "not found: error message", err.Error()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
) | ||
|
||
var out io.Writer = os.Stdout | ||
|
||
func main() { | ||
store := NewMapStore() | ||
puppy := Puppy{Breed: "Husky", Colour: "White", Value: 4999.98} | ||
id, _ := store.CreatePuppy(puppy) | ||
fmt.Fprintf(out, "Puppy(%d) added to store\n", id) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMainOutput(t *testing.T) { | ||
os.RemoveAll("./data") | ||
defer os.RemoveAll("./data") | ||
|
||
var buf bytes.Buffer | ||
out = &buf | ||
|
||
main() | ||
|
||
expected := "Puppy(1) added to store\n" | ||
actual := buf.String() | ||
assert.Equal(t, expected, actual) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
// MapStore is map based implementation of Storer interface | ||
type MapStore struct { | ||
store map[uint64]Puppy | ||
nextID uint64 | ||
} | ||
|
||
// NewMapStore creates a new MapStore | ||
func NewMapStore() *MapStore { | ||
return &MapStore{nextID: 1, store: map[uint64]Puppy{}} | ||
} | ||
|
||
// CreatePuppy inserts given puppy in store and returns given id to the puppy | ||
// Will return an error if value of puppy is negative | ||
func (m *MapStore) CreatePuppy(puppy Puppy) (uint64, error) { | ||
if puppy.Value < 0 { | ||
return 0, NewError(ErrInvalid, "value of puppy is negative") | ||
} | ||
|
||
puppy.ID = m.nextID | ||
m.nextID++ | ||
m.store[puppy.ID] = puppy | ||
return puppy.ID, nil | ||
} | ||
|
||
// ReadPuppy reads puppy with given id from the store | ||
// Will return an error if puppy with given id does not exist | ||
func (m *MapStore) ReadPuppy(id uint64) (Puppy, error) { | ||
puppy, exists := m.store[id] | ||
|
||
if !exists { | ||
return puppy, NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", id)) | ||
} | ||
return puppy, nil | ||
} | ||
|
||
// UpdatePuppy update puppy with given id in store if puppy exists | ||
// Returns nil if puppy with given id exists. Otherwise returns an error | ||
func (m *MapStore) UpdatePuppy(puppy Puppy) error { | ||
if puppy.Value < 0 { | ||
return NewError(ErrInvalid, "value of puppy is negative") | ||
} | ||
|
||
if _, exists := m.store[puppy.ID]; !exists { | ||
return NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", puppy.ID)) | ||
} | ||
|
||
m.store[puppy.ID] = puppy | ||
return nil | ||
} | ||
|
||
// DeletePuppy deletes puppy with given id from store | ||
// Returns nil if puppy with given id exists. Otherwise returns an errorZ | ||
func (m *MapStore) DeletePuppy(id uint64) error { | ||
if _, exists := m.store[id]; !exists { | ||
return NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", id)) | ||
} | ||
|
||
delete(m.store, id) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type StorerTest struct { | ||
suite.Suite | ||
store Storer | ||
id uint64 | ||
} | ||
|
||
func (suite *StorerTest) SetupTest() { | ||
os.RemoveAll("./test") | ||
suite.id, _ = suite.store.CreatePuppy(Puppy{Breed: "Labrador", Colour: "Cream", Value: 2999.99}) | ||
} | ||
|
||
func (suite *StorerTest) TearDownSuite() { | ||
os.RemoveAll("./test") | ||
} | ||
|
||
func (suite *StorerTest) TestCreatePuppy() { | ||
id, err := suite.store.CreatePuppy(Puppy{Breed: "German Shepard", Colour: "Brown", Value: 3499.99}) | ||
suite.True(id > 1) | ||
suite.Nil(err) | ||
|
||
id, err = suite.store.CreatePuppy(Puppy{Breed: "Terrier", Colour: "White", Value: -3499.99}) | ||
suite.True(id == 0) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ := err.(*Error) | ||
suite.Equal(ErrInvalid, customErr.Code) | ||
suite.Equal("invalid input: value of puppy is negative", customErr.Message) | ||
} | ||
|
||
func (suite *StorerTest) TestReadPuppy() { | ||
puppy, err := suite.store.ReadPuppy(suite.id) | ||
|
||
suite.Nil(err) | ||
suite.Equal(puppy.ID, suite.id) | ||
suite.Equal(puppy.Breed, "Labrador") | ||
suite.Equal(puppy.Colour, "Cream") | ||
suite.Equal(puppy.Value, 2999.99) | ||
|
||
_, err = suite.store.ReadPuppy(100) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ := err.(*Error) | ||
suite.Equal(ErrNotFound, customErr.Code) | ||
suite.Equal("not found: puppy with id: 100 is not found", customErr.Message) | ||
} | ||
|
||
func (suite *StorerTest) TestUpdatePuppy() { | ||
err := suite.store.UpdatePuppy(Puppy{ID: suite.id, Breed: "Labrador Retriever", Colour: "Brown", Value: 3999.99}) | ||
|
||
suite.Nil(err) | ||
puppy, err := suite.store.ReadPuppy(suite.id) | ||
|
||
suite.Nil(err) | ||
suite.Equal(puppy.ID, suite.id) | ||
suite.Equal(puppy.Breed, "Labrador Retriever") | ||
suite.Equal(puppy.Colour, "Brown") | ||
suite.Equal(puppy.Value, 3999.99) | ||
|
||
err = suite.store.UpdatePuppy(Puppy{ID: suite.id, Breed: "Poodle", Colour: "White", Value: -1999.99}) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ := err.(*Error) | ||
suite.Equal(ErrInvalid, customErr.Code) | ||
suite.Equal("invalid input: value of puppy is negative", customErr.Message) | ||
|
||
err = suite.store.UpdatePuppy(Puppy{ID: 100, Breed: "Poodle", Colour: "White", Value: 1999.99}) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ = err.(*Error) | ||
suite.Equal(ErrNotFound, customErr.Code) | ||
suite.Equal("not found: puppy with id: 100 is not found", customErr.Message) | ||
} | ||
|
||
func (suite *StorerTest) TestDeletePuppy() { | ||
err := suite.store.DeletePuppy(suite.id) | ||
suite.Nil(err) | ||
|
||
_, err = suite.store.ReadPuppy(suite.id) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ := err.(*Error) | ||
suite.Equal(ErrNotFound, customErr.Code) | ||
suite.Equal(fmt.Sprintf("not found: puppy with id: %v is not found", suite.id), customErr.Message) | ||
|
||
err = suite.store.DeletePuppy(suite.id) | ||
suite.Require().IsType(&Error{}, err) | ||
customErr, _ = err.(*Error) | ||
suite.Equal(ErrNotFound, customErr.Code) | ||
suite.Equal(fmt.Sprintf("not found: puppy with id: %v is not found", suite.id), customErr.Message) | ||
} | ||
|
||
func TestStore(t *testing.T) { | ||
suite.Run(t, &StorerTest{store: NewMapStore()}) | ||
suite.Run(t, &StorerTest{store: NewSyncStore()}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
// SyncStore is sync.Map based implementation of Storer interface | ||
type SyncStore struct { | ||
store sync.Map | ||
sync.Mutex | ||
nextID uint64 | ||
} | ||
|
||
// NewSyncStore creates a new SyncStore | ||
func NewSyncStore() *SyncStore { | ||
return &SyncStore{nextID: 1, store: sync.Map{}} | ||
} | ||
|
||
// CreatePuppy inserts given puppy in store and returns given id to the puppy | ||
// Will return an error if value of puppy is negative | ||
func (s *SyncStore) CreatePuppy(puppy Puppy) (uint64, error) { | ||
if puppy.Value < 0 { | ||
return 0, NewError(ErrInvalid, "value of puppy is negative") | ||
} | ||
|
||
s.Lock() | ||
defer s.Unlock() | ||
|
||
puppy.ID = s.nextID | ||
s.nextID++ | ||
s.store.Store(puppy.ID, &puppy) | ||
return puppy.ID, nil | ||
} | ||
|
||
// ReadPuppy reads puppy with given id from the store | ||
// Will return an error if puppy with given id does not exist | ||
func (s *SyncStore) ReadPuppy(id uint64) (Puppy, error) { | ||
value, exists := s.store.Load(id) | ||
|
||
if !exists { | ||
return Puppy{}, NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", id)) | ||
} | ||
return *value.(*Puppy), nil | ||
} | ||
|
||
// UpdatePuppy update puppy with given id in store if puppy exists | ||
// Returns nil if puppy with given id exists. Otherwise returns an error | ||
func (s *SyncStore) UpdatePuppy(puppy Puppy) error { | ||
if puppy.Value < 0 { | ||
return NewError(ErrInvalid, "value of puppy is negative") | ||
} | ||
|
||
s.Lock() | ||
defer s.Unlock() | ||
|
||
if _, exists := s.store.Load(puppy.ID); !exists { | ||
return NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", puppy.ID)) | ||
} | ||
|
||
s.store.Store(puppy.ID, &puppy) | ||
return nil | ||
} | ||
|
||
// DeletePuppy deletes puppy with given id from store | ||
// Returns nil if puppy with given id exists. Otherwise returns an error | ||
func (s *SyncStore) DeletePuppy(id uint64) error { | ||
s.Lock() | ||
defer s.Unlock() | ||
|
||
if _, exists := s.store.Load(id); !exists { | ||
return NewError(ErrNotFound, fmt.Sprintf("puppy with id: %v is not found", id)) | ||
} | ||
|
||
s.store.Delete(id) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package main | ||
|
||
type Puppy struct { | ||
ID uint64 `json:"id"` | ||
Breed string `json:"breed"` | ||
Colour string `json:"colour"` | ||
Value float64 `json:"value"` | ||
} | ||
|
||
type Storer interface { | ||
CreatePuppy(puppy Puppy) (uint64, error) | ||
ReadPuppy(id uint64) (Puppy, error) | ||
UpdatePuppy(puppy Puppy) error | ||
DeletePuppy(id uint64) error | ||
} |