-
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.
Add lab 8 - Project Layout
- Loading branch information
Showing
9 changed files
with
630 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,50 @@ | ||
# Alex 'Puppy Project' | ||
|
||
This is part of the [Go](https://golang.org/) [Course](https://github.com/anz-bank/go-course) done by [ANZ Bank](https://www.anz.com.au) during 2019. | ||
|
||
It contains code to do basic CRUD operations (Create, Read, Update, Delete) on a "Puppy" type defined by the user, with 2 in-memory storage implementations to back it using native Go maps and [sync.map](https://golang.org/pkg/sync/). | ||
|
||
As it is, this code is not very useful to you, who is reading this now, as it does not 'do' anything. Its purpose is to teach Golang coding skills and good development practices. | ||
|
||
The project is organized as follows: | ||
- `cmd/puppy-server` contains the code for the executable. It is very crude, just barely enough to demonstrate the internal working of the package. | ||
- `pkg/puppy` contains the type definitions, interface and error values and tests for the Puppy package - very little working code here too. | ||
- `pkg/puppy/store` is where the action is, and contains the store backings and tests. | ||
|
||
## Prerequisites | ||
- Install [`go`](https://golang.org/doc/install) and alternatively [`golangci-lint`](https://github.com/golangci/golangci-lint#local-installation) if you want to run tests or lint | ||
- Clone this project outside your `$GOPATH` to enable [Go Modules](https://github.com/golang/go/wiki/Modules) | ||
|
||
## Build, install, execute | ||
--- | ||
### Short version | ||
For the anxious, you can just run the main executable quickly doing a | ||
|
||
go run ./cmd/puppy-server/main.go | ||
|
||
As promised, the output is not interesting at all. | ||
|
||
### Long version | ||
Alternatively, you can build, install and run from your `$GOPATH` with | ||
|
||
go install ./... | ||
puppy-server | ||
|
||
Or yet build and run from the the same directory as this `README` with | ||
|
||
go build -o puppy-server cmd/puppy-server/main.go | ||
./puppy-server | ||
|
||
#### Lint, test, coverage | ||
|
||
You can be sure the code adheres to (at least some) good practices by running the linter (alternatively, using -v): | ||
|
||
golangci-lint run | ||
|
||
You can also run the built-in tests with | ||
|
||
go test ./... | ||
|
||
And review the test coverage using the nice Go builtin tool with: | ||
|
||
go test -coverprofile=cover.out ./... && go tool cover -html=cover.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,36 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/anz-bank/go-course/08_project/alextmz/pkg/puppy" | ||
"github.com/anz-bank/go-course/08_project/alextmz/pkg/puppy/store" | ||
) | ||
|
||
var out io.Writer = os.Stdout | ||
|
||
func main() { | ||
s1 := store.NewMapStore() | ||
s1p1 := puppy.Puppy{Breed: "Dogo", Colour: "White", Value: 500} | ||
fmt.Fprintf(out, "%-27s : ", "Creating puppy on Mapstore") | ||
_ = s1.CreatePuppy(&s1p1) | ||
|
||
fmt.Fprintf(out, "%s : %#v\n", "Created puppy", s1p1) | ||
fmt.Fprintf(out, "%-27s : ", "Reading puppy back") | ||
s1p2, _ := s1.ReadPuppy(s1p1.ID) | ||
|
||
fmt.Fprintf(out, "%#v\n", s1p2) | ||
|
||
s2 := store.NewMapStore() | ||
s2p1 := puppy.Puppy{Breed: "Fila", Colour: "Golden", Value: 900} | ||
fmt.Fprintf(out, "%-27s : ", "Creating puppy on SyncStore") | ||
_ = s2.CreatePuppy(&s2p1) | ||
|
||
fmt.Fprintf(out, "%s : %#v \n", "Created puppy", s2p1) | ||
fmt.Fprintf(out, "%-27s : ", "Reading puppy back") | ||
s2p2, _ := s2.ReadPuppy(s2p1.ID) | ||
|
||
fmt.Fprintf(out, "%#v\n", s2p2) | ||
} |
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,24 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_main(t *testing.T) { | ||
want := `Creating puppy on Mapstore : Created puppy : puppy.Puppy{ID:1, Breed:"Dogo", Colour:"White", Value:500} | ||
Reading puppy back : puppy.Puppy{ID:1, Breed:"Dogo", Colour:"White", Value:500} | ||
Creating puppy on SyncStore : Created puppy : puppy.Puppy{ID:1, Breed:"Fila", Colour:"Golden", Value:900} | ||
Reading puppy back : puppy.Puppy{ID:1, Breed:"Fila", Colour:"Golden", Value:900} | ||
` | ||
|
||
t.Run("main test", func(t *testing.T) { | ||
var buf bytes.Buffer | ||
out = &buf | ||
main() | ||
got := buf.String() | ||
assert.Equal(t, want, got) | ||
}) | ||
} |
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,95 @@ | ||
package puppy | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
type Error struct { | ||
Message string | ||
Code int | ||
} | ||
|
||
const ( | ||
ErrNilPuppyPointer = iota | ||
ErrNegativePuppyValueOnCreate | ||
ErrNegativePuppyValueOnUpdate | ||
ErrPuppyAlreadyIdentified | ||
ErrPuppyNotFoundOnRead | ||
ErrPuppyNotFoundOnUpdate | ||
ErrPuppyNotFoundOnDelete | ||
ErrNilPuppyPointerStr = "puppy pointer is nil%s" | ||
ErrNegativePuppyValueOnCreateStr = "trying to create a puppy with a negative value%s" | ||
ErrNegativePuppyValueOnUpdateStr = "trying to update a puppy with a negative value%s" | ||
ErrPuppyAlreadyIdentifiedStr = "puppy already initialized%s" | ||
ErrPuppyNotFoundOnReadStr = "puppy%s being read does not exist" | ||
ErrPuppyNotFoundOnUpdateStr = "puppy%s being updated does not exist" | ||
ErrPuppyNotFoundOnDeleteStr = "puppy%s being deleted does not exist" | ||
) | ||
|
||
// errorCodeDescription returns the verbose error description corresponding | ||
// to a known/static error Code, allowing for an optional parameter to be passed | ||
// for a more verbose error'ing. | ||
// Passing an empty string makes it return the default error string only. | ||
func (e Error) errorCodeDescription(param string) string { | ||
errormap := map[int]struct { | ||
errmsg, paramprefix, paramsuffix string | ||
}{ | ||
ErrNilPuppyPointer: {ErrNilPuppyPointerStr, "", ""}, | ||
ErrNegativePuppyValueOnCreate: {ErrNegativePuppyValueOnCreateStr, " (", ")"}, | ||
ErrNegativePuppyValueOnUpdate: {ErrNegativePuppyValueOnUpdateStr, " (", ")"}, | ||
ErrPuppyAlreadyIdentified: {ErrPuppyAlreadyIdentifiedStr, " with ID ", ""}, | ||
ErrPuppyNotFoundOnRead: {ErrPuppyNotFoundOnReadStr, " with ID ", ""}, | ||
ErrPuppyNotFoundOnUpdate: {ErrPuppyNotFoundOnUpdateStr, " with ID ", ""}, | ||
ErrPuppyNotFoundOnDelete: {ErrPuppyNotFoundOnDeleteStr, " with ID ", ""}, | ||
} | ||
|
||
v, ok := errormap[e.Code] | ||
if !ok { | ||
return "undefined error" | ||
} | ||
|
||
if param != "" { | ||
return fmt.Sprintf(v.errmsg, v.paramprefix+param+v.paramsuffix) | ||
} | ||
|
||
return fmt.Sprintf(v.errmsg, "") | ||
} | ||
|
||
func (e Error) Error() string { | ||
if e.Message != "" { | ||
return e.Message | ||
} | ||
return fmt.Sprint(e.errorCodeDescription("")) | ||
} | ||
|
||
// Errorp returns the known parametrized (verbose) error string for | ||
// a given error code. | ||
func Errorp(err int, param interface{}) Error { | ||
var e Error | ||
e.Code = err | ||
switch v := param.(type) { | ||
case string: | ||
e.Message = e.errorCodeDescription(v) | ||
case int: | ||
e.Message = e.errorCodeDescription(fmt.Sprintf("%d", param)) | ||
case float64: | ||
e.Message = e.errorCodeDescription(fmt.Sprintf("%.2f", param)) | ||
default: | ||
panic("not implemented: param type is not either string, int or float") | ||
} | ||
return e | ||
} | ||
|
||
func NewError(err int, m string) Error { | ||
var e Error | ||
e.Code = err | ||
e.Message = m | ||
return e | ||
} | ||
|
||
func NewErrorf(err int, f string, m ...interface{}) Error { | ||
var e Error | ||
e.Code = err | ||
e.Message = fmt.Sprintf(f, m...) | ||
return e | ||
} |
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,95 @@ | ||
package puppy | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestKnownErrorCodeDescr(t *testing.T) { | ||
var tests = map[string]struct { | ||
code int | ||
parameter string | ||
want string | ||
}{ | ||
"ErrNilPuppyPointer": {ErrNilPuppyPointer, "", | ||
"puppy pointer is nil"}, | ||
"ErrNegativePuppyValueOnCreate": {ErrNegativePuppyValueOnCreate, "", | ||
"trying to create a puppy with a negative value"}, | ||
"ErrNegativePuppyValueOnCreate with parameter": {ErrNegativePuppyValueOnCreate, "-555.55", | ||
"trying to create a puppy with a negative value (-555.55)"}, | ||
"ErrNegativePuppyValueOnUpdate": {ErrNegativePuppyValueOnUpdate, "", | ||
"trying to update a puppy with a negative value"}, | ||
"ErrNegativePuppyValueOnUpdate with parameter": {ErrNegativePuppyValueOnUpdate, "-22.22", | ||
"trying to update a puppy with a negative value (-22.22)"}, | ||
"ErrPuppyAlreadyIdentified": {ErrPuppyAlreadyIdentified, "", | ||
"puppy already initialized"}, | ||
"ErrPuppyAlreadyIdentified with parameter": {ErrPuppyAlreadyIdentified, "42", | ||
"puppy already initialized with ID 42"}, | ||
"ErrPuppyNotFoundOnRead": {ErrPuppyNotFoundOnRead, "", | ||
"puppy being read does not exist"}, | ||
"ErrPuppyNotFoundOnRead with parameter": {ErrPuppyNotFoundOnRead, "11", | ||
"puppy with ID 11 being read does not exist"}, | ||
"ErrPuppyNotFoundOnUpdate": {ErrPuppyNotFoundOnUpdate, "", | ||
"puppy being updated does not exist"}, | ||
"ErrPuppyNotFoundOnUpdate with parameter": {ErrPuppyNotFoundOnUpdate, "22", | ||
"puppy with ID 22 being updated does not exist"}, | ||
"ErrPuppyNotFoundOnDelete": {ErrPuppyNotFoundOnDelete, "", | ||
"puppy being deleted does not exist"}, | ||
"ErrPuppyNotFoundOnDelete with parameter": {ErrPuppyNotFoundOnDelete, "33", | ||
"puppy with ID 33 being deleted does not exist"}, | ||
"UnknownErrorCode": { | ||
61621, "", | ||
"undefined error"}, | ||
} | ||
for name, test := range tests { | ||
test := test | ||
t.Run(name, func(t *testing.T) { | ||
e := Error{Code: test.code} | ||
got := e.errorCodeDescription(test.parameter) | ||
assert.Equal(t, test.want, got) | ||
}) | ||
} | ||
} | ||
|
||
func TestError(t *testing.T) { | ||
e := Error{Code: ErrNilPuppyPointer} | ||
assert.Equal(t, "puppy pointer is nil", e.Error()) | ||
e = Error{Code: ErrPuppyNotFoundOnDelete} | ||
assert.Equal(t, "puppy being deleted does not exist", e.Error()) | ||
} | ||
|
||
func TestErrorp(t *testing.T) { | ||
// test empyy parameters | ||
e := Errorp(ErrNilPuppyPointer, "") | ||
assert.Equal(t, "puppy pointer is nil", e.Error()) | ||
// test string parameters | ||
e = Errorp(ErrPuppyNotFoundOnDelete, "999") | ||
assert.Equal(t, "puppy with ID 999 being deleted does not exist", e.Error()) | ||
// test int parameters | ||
e = Errorp(ErrPuppyNotFoundOnDelete, 999) | ||
assert.Equal(t, "puppy with ID 999 being deleted does not exist", e.Error()) | ||
// test float parameters | ||
e = Errorp(ErrPuppyNotFoundOnDelete, 9.99) | ||
assert.Equal(t, "puppy with ID 9.99 being deleted does not exist", e.Error()) | ||
// test unimplemented parameter type | ||
assert.Panics(t, func() { | ||
_ = Errorp(ErrPuppyNotFoundOnDelete, true) | ||
}) | ||
} | ||
|
||
func TestNewError(t *testing.T) { | ||
e1 := NewError(999, "error 999") | ||
var e2 Error | ||
e2.Code = 999 | ||
e2.Message = "error 999" | ||
assert.Equal(t, e2, e1) | ||
} | ||
|
||
func TestNewErrorf(t *testing.T) { | ||
e1 := NewErrorf(999, "error %d", 999) | ||
var e2 Error | ||
e2.Code = 999 | ||
e2.Message = "error 999" | ||
assert.Equal(t, e2, e1) | ||
} |
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,58 @@ | ||
package store | ||
|
||
import ( | ||
"github.com/anz-bank/go-course/08_project/alextmz/pkg/puppy" | ||
) | ||
|
||
type MapStore struct { | ||
pmap map[int]puppy.Puppy | ||
nextID int | ||
} | ||
|
||
func NewMapStore() *MapStore { | ||
a := MapStore{pmap: make(map[int]puppy.Puppy)} | ||
return &a | ||
} | ||
|
||
func (m *MapStore) CreatePuppy(p *puppy.Puppy) error { | ||
if p == nil { | ||
return puppy.Error{Code: puppy.ErrNilPuppyPointer} | ||
} | ||
if p.Value < 0 { | ||
return puppy.Errorp(puppy.ErrNegativePuppyValueOnCreate, p.Value) | ||
} | ||
if p.ID != 0 { | ||
return puppy.Errorp(puppy.ErrPuppyAlreadyIdentified, p.ID) | ||
} | ||
m.nextID++ | ||
p.ID = m.nextID | ||
m.pmap[p.ID] = *p | ||
return nil | ||
} | ||
|
||
func (m *MapStore) ReadPuppy(id int) (puppy.Puppy, error) { | ||
v, ok := m.pmap[id] | ||
if !ok { | ||
return puppy.Puppy{}, puppy.Errorp(puppy.ErrPuppyNotFoundOnRead, id) | ||
} | ||
return v, nil | ||
} | ||
|
||
func (m *MapStore) UpdatePuppy(p puppy.Puppy) error { | ||
if _, ok := m.pmap[p.ID]; !ok { | ||
return puppy.Errorp(puppy.ErrPuppyNotFoundOnUpdate, p.ID) | ||
} | ||
if p.Value < 0 { | ||
return puppy.Errorp(puppy.ErrNegativePuppyValueOnUpdate, p.Value) | ||
} | ||
m.pmap[p.ID] = p | ||
return nil | ||
} | ||
|
||
func (m *MapStore) DeletePuppy(id int) error { | ||
if _, ok := m.pmap[id]; !ok { | ||
return puppy.Errorp(puppy.ErrPuppyNotFoundOnDelete, id) | ||
} | ||
delete(m.pmap, id) | ||
return nil | ||
} |
Oops, something went wrong.