Skip to content

Commit

Permalink
Add lab 8 - Project Layout (#657)
Browse files Browse the repository at this point in the history
Add lab 8 - Project Layout
  • Loading branch information
alextmz authored and Jim hejtmanek committed Jul 31, 2020
1 parent fc0ef5f commit 7cf0b38
Show file tree
Hide file tree
Showing 9 changed files with 630 additions and 0 deletions.
50 changes: 50 additions & 0 deletions 08_project/alextmz/README.md
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
36 changes: 36 additions & 0 deletions 08_project/alextmz/cmd/puppy-server/main.go
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)
}
24 changes: 24 additions & 0 deletions 08_project/alextmz/cmd/puppy-server/main_test.go
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)
})
}
95 changes: 95 additions & 0 deletions 08_project/alextmz/pkg/puppy/errors.go
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
}
95 changes: 95 additions & 0 deletions 08_project/alextmz/pkg/puppy/errors_test.go
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)
}
58 changes: 58 additions & 0 deletions 08_project/alextmz/pkg/puppy/store/mapstore.go
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
}
Loading

0 comments on commit 7cf0b38

Please sign in to comment.