-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
679 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,32 @@ | ||
# n0npax's puppy | ||
|
||
This project is source completed lab08 from [go course](https://github.com/anz-bank/go-course/) | ||
|
||
## Prerequisites | ||
|
||
- Install `go 1.12` according to [official installation instruction](https://golang.org/doc/install) | ||
- Clone this project outside your `$GOPATH` to enable [Go Modules](https://github.com/golang/go/wiki/Modules) | ||
- Install `golangci-lint` according to [instructions](https://github.com/golangci/golangci-lint#local-installation) | ||
|
||
## Build, execute, test, lint | ||
|
||
Build and install this project with | ||
|
||
go install ./... | ||
|
||
Build and execute its binary with | ||
|
||
go build -o lab08 cmd/puppy-server/main.go | ||
./lab08 | ||
|
||
Test it with | ||
|
||
go test ./... | ||
|
||
Lint it with | ||
|
||
golangci-lint run | ||
|
||
Review coverage with | ||
|
||
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out |
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,57 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
|
||
"gopkg.in/alecthomas/kingpin.v2" | ||
|
||
puppy "github.com/anz-bank/go-course/09_json/n0npax/pkg/puppy" | ||
store "github.com/anz-bank/go-course/09_json/n0npax/pkg/puppy/store" | ||
) | ||
|
||
var out io.Writer = os.Stdout | ||
|
||
var ( | ||
args = os.Args[1:] | ||
puppyFilePath = kingpin.Flag("data", "path to file with puppies data").Short('d').ExistingFile() | ||
) | ||
|
||
func main() { | ||
_, err := kingpin.CommandLine.Parse(args) | ||
checkError(err) | ||
if *puppyFilePath != "" { | ||
puppies := readPuppies(*puppyFilePath) | ||
store := store.MemStore{} | ||
feedStore(store, puppies) | ||
} | ||
} | ||
|
||
func readPuppies(path string) []puppy.Puppy { | ||
b, err := ioutil.ReadFile(path) | ||
checkError(err) | ||
|
||
var puppies []puppy.Puppy | ||
err = json.Unmarshal(b, &puppies) | ||
checkError(err) | ||
return puppies | ||
} | ||
|
||
func feedStore(s puppy.Storer, puppies []puppy.Puppy) { | ||
for _, p := range puppies { | ||
p := p | ||
_, err := s.CreatePuppy(&p) | ||
checkError(err) | ||
} | ||
} | ||
|
||
func checkError(err error) { | ||
if err != nil { | ||
fmt.Fprintln(out, err) | ||
os.Exit(1) | ||
} | ||
|
||
} |
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,47 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"testing" | ||
|
||
"bou.ke/monkey" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMainNoArgs(t *testing.T) { | ||
var buf bytes.Buffer | ||
out = &buf | ||
args = []string{} | ||
main() | ||
|
||
expected := "" | ||
actual := buf.String() | ||
assert.Equal(t, expected, actual) | ||
} | ||
|
||
func TestMainWithArgs(t *testing.T) { | ||
var buf bytes.Buffer | ||
out = &buf | ||
args = []string{"--data", "../../test-data/data.json"} | ||
main() | ||
|
||
expected := "" | ||
actual := buf.String() | ||
assert.Equal(t, expected, actual) | ||
} | ||
|
||
func TestMainCorruptedData(t *testing.T) { | ||
fakeExit := func(int) { | ||
panic("os.Exit called") | ||
} | ||
patch := monkey.Patch(os.Exit, fakeExit) | ||
defer patch.Unpatch() | ||
|
||
var buf bytes.Buffer | ||
out = &buf | ||
args = []string{"--data", "/dev/null"} | ||
|
||
assert.PanicsWithValue(t, "os.Exit called", main, "os.Exit was not called") | ||
} |
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,31 @@ | ||
package puppy | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// Error codes | ||
const ( | ||
ErrInvalidInputCode = 400 | ||
ErrNotFoundCode = 404 | ||
ErrInternalErrorCode = 500 | ||
) | ||
|
||
// Error wrapps errors with code, message and error itself | ||
type Error struct { | ||
Message string | ||
Code int | ||
} | ||
|
||
// Error returns error as a string | ||
func (e *Error) Error() string { | ||
return fmt.Sprintf("Error code: %v, message : %v", e.Code, e.Message) | ||
} | ||
|
||
// Errorf creates a new Error with formatting | ||
func Errorf(code int, format string, args ...interface{}) *Error { | ||
return &Error{ | ||
Message: fmt.Sprintf(format, 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,14 @@ | ||
package puppy | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestErrorf(t *testing.T) { | ||
err := Errorf(ErrInternalErrorCode, "internal error") | ||
assert.Equal(t, ErrInternalErrorCode, err.Code) | ||
errMessage := err.Error() | ||
assert.Equal(t, "Error code: 500, message : internal error", errMessage) | ||
} |
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,97 @@ | ||
package store | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
"sync" | ||
|
||
puppy "github.com/anz-bank/go-course/09_json/n0npax/pkg/puppy" | ||
|
||
"github.com/syndtr/goleveldb/leveldb" | ||
) | ||
|
||
var levelDBPath = "/tmp/leveldb" | ||
|
||
// LevelDBStore provides sync for leveldb | ||
type LevelDBStore struct { | ||
ldb *leveldb.DB | ||
sync.Mutex | ||
nextID int | ||
} | ||
|
||
// NewLevelDBStorer creates new storer for leveldb | ||
func NewLevelDBStorer() *LevelDBStore { | ||
db, err := leveldb.OpenFile(levelDBPath, nil) | ||
dbErrorPanic(err) | ||
return &LevelDBStore{nextID: 0, ldb: db} | ||
} | ||
|
||
// CreatePuppy creates puppy | ||
func (l *LevelDBStore) CreatePuppy(p *puppy.Puppy) (int, error) { | ||
l.Lock() | ||
defer l.Unlock() | ||
id, err := l.putPuppy(l.nextID, p) | ||
l.nextID++ | ||
return id, err | ||
} | ||
|
||
// ReadPuppy reads puppy from backend | ||
func (l *LevelDBStore) ReadPuppy(id int) (*puppy.Puppy, error) { | ||
byteID := []byte(strconv.Itoa(id)) | ||
if puppyData, err := l.ldb.Get(byteID, nil); err == nil { | ||
var p puppy.Puppy | ||
err := json.Unmarshal(puppyData, &p) | ||
if err != nil { | ||
return nil, puppy.Errorf(puppy.ErrInternalErrorCode, "Internal error. Could not cast stored data to puppy object") | ||
} | ||
return &p, nil | ||
} | ||
return nil, puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
|
||
// UpdatePuppy updates puppy | ||
func (l *LevelDBStore) UpdatePuppy(id int, p *puppy.Puppy) error { | ||
if id != p.ID { | ||
return puppy.Errorf(puppy.ErrInvalidInputCode, "ID is corrupted. Please ensure object ID matched provided ID") | ||
} | ||
l.Lock() | ||
defer l.Unlock() | ||
if _, err := l.ReadPuppy(id); err != nil { | ||
return puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
_, err := l.putPuppy(id, p) | ||
return err | ||
} | ||
|
||
// DeletePuppy deletes puppy | ||
func (l *LevelDBStore) DeletePuppy(id int) (bool, error) { | ||
l.Lock() | ||
defer l.Unlock() | ||
if _, err := l.ReadPuppy(id); err != nil { | ||
return false, puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
byteID := []byte(strconv.Itoa(id)) | ||
err := l.ldb.Delete(byteID, nil) | ||
dbErrorPanic(err) | ||
return true, nil | ||
} | ||
|
||
// putPuppy stores puppy in backend | ||
func (l *LevelDBStore) putPuppy(id int, p *puppy.Puppy) (int, error) { | ||
if p.Value < 0 { | ||
return -1, puppy.Errorf(puppy.ErrInvalidInputCode, "Puppy value have to be positive number") | ||
} | ||
puppyByte, _ := json.Marshal(p) | ||
byteID := []byte(strconv.Itoa(id)) | ||
err := l.ldb.Put(byteID, puppyByte, nil) | ||
dbErrorPanic(err) | ||
return id, nil | ||
} | ||
|
||
// dbErrorPanic causes panic in error is not nil | ||
func dbErrorPanic(err error) { | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
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,17 @@ | ||
package store | ||
|
||
import ( | ||
"testing" | ||
|
||
puppy "github.com/anz-bank/go-course/09_json/n0npax/pkg/puppy" | ||
) | ||
|
||
func TestDbErrorPanic(t *testing.T) { | ||
defer func() { | ||
if r := recover(); r == nil { | ||
t.Errorf("Panic expected") | ||
} | ||
}() | ||
err := puppy.Errorf(puppy.ErrInvalidInputCode, "test invalid input") | ||
dbErrorPanic(err) | ||
} |
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,57 @@ | ||
package store | ||
|
||
import ( | ||
"fmt" | ||
|
||
puppy "github.com/anz-bank/go-course/09_json/n0npax/pkg/puppy" | ||
) | ||
|
||
// MemStore map based type for storing puppies data | ||
type MemStore map[int]puppy.Puppy | ||
|
||
// NewMemStore creates new storer for map | ||
func NewMemStore() MemStore { | ||
return MemStore{} | ||
} | ||
|
||
// CreatePuppy creates puppy | ||
func (m MemStore) CreatePuppy(p *puppy.Puppy) (int, error) { | ||
if p.Value < 0 { | ||
return -1, puppy.Errorf(puppy.ErrInvalidInputCode, "Puppy value have to be positive number") | ||
} | ||
id := len(m) | ||
m[id] = *p | ||
return id, nil | ||
} | ||
|
||
// ReadPuppy reads puppy from backend | ||
func (m MemStore) ReadPuppy(id int) (*puppy.Puppy, error) { | ||
if puppy, ok := m[id]; ok { | ||
return &puppy, nil | ||
} | ||
return nil, puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
|
||
// UpdatePuppy updates puppy | ||
func (m MemStore) UpdatePuppy(id int, p *puppy.Puppy) error { | ||
if p.Value < 0 { | ||
return puppy.Errorf(puppy.ErrInvalidInputCode, "Puppy value have to be positive number") | ||
} | ||
if id != p.ID { | ||
return puppy.Errorf(puppy.ErrInvalidInputCode, "ID is corrupted. Please ensure object ID matched provided ID") | ||
} | ||
if _, ok := m[id]; !ok { | ||
return puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
m[id] = *p | ||
return nil | ||
} | ||
|
||
// DeletePuppy deletes puppy | ||
func (m MemStore) DeletePuppy(id int) (bool, error) { | ||
if _, ok := m[id]; !ok { | ||
return false, puppy.Errorf(puppy.ErrNotFoundCode, fmt.Sprintf("Puppy with ID (%v) not found", id)) | ||
} | ||
delete(m, id) | ||
return true, nil | ||
} |
Oops, something went wrong.