Skip to content

Commit

Permalink
Add Lab 7 - Errors implementation (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kasun Fernando authored Sep 1, 2019
1 parent 00b50aa commit 14cef3d
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 0 deletions.
37 changes: 37 additions & 0 deletions 07_errors/kasunfdo/error.go
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,
}
}
20 changes: 20 additions & 0 deletions 07_errors/kasunfdo/error_test.go
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())
}
16 changes: 16 additions & 0 deletions 07_errors/kasunfdo/main.go
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)
}
23 changes: 23 additions & 0 deletions 07_errors/kasunfdo/main_test.go
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)
}
64 changes: 64 additions & 0 deletions 07_errors/kasunfdo/mapstore.go
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
}
100 changes: 100 additions & 0 deletions 07_errors/kasunfdo/store_test.go
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()})
}
77 changes: 77 additions & 0 deletions 07_errors/kasunfdo/syncstore.go
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
}
15 changes: 15 additions & 0 deletions 07_errors/kasunfdo/types.go
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
}

0 comments on commit 14cef3d

Please sign in to comment.