-
Notifications
You must be signed in to change notification settings - Fork 162
/
rest.go
146 lines (130 loc) · 5.2 KB
/
rest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package rest
import (
"net/http"
"strconv"
"github.com/anz-bank/go-course/10_rest/nickolee/pkg/puppy"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
const (
invalidIDMsg = "Invalid input, ensure ID is valid"
invalidJSONMsg = "Invalid input, ensure JSON is valid"
)
// PuppyHandlerAndStorer is a special guy who wraps around Storer types and gives them extra abilities
// to now handle API requests thus enhancing their storing abilities significantly!
type PuppyHandlerAndStorer struct {
Storage puppy.Storer // Using the thread safe sync.map implementation of Storer interface
}
// This guy gives u a nicely initialised brand new *PuppyHandlerAndStorer
func NewPuppyHandlerAndStorer(s puppy.Storer) *PuppyHandlerAndStorer {
return &PuppyHandlerAndStorer{Storage: s}
}
func handleStorerError(w http.ResponseWriter, err error) {
// switch on error type (type switch as opposed to value switch) - unique to Go
// So it checks type of error and based on that determines what to do
switch e := err.(type) { // e is the type-casted error
case *puppy.Error:
// use puppy error to define the response status and body
http.Error(w, e.Error(), e.Code)
return
default:
// handle otherwise (500)
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
return
}
}
// Implementing 1/4 methods of PuppyHandler: puppy handler for GET /api/puppy/{id}
func (phs *PuppyHandlerAndStorer) handleGet(w http.ResponseWriter, r *http.Request) {
// parse incoming request url param
id, err := strconv.Atoi(chi.URLParam(r, "id")) // strip off the {id} part of the endpoint and convert to int
if err != nil {
// if err != nil means user didn't provide proper id hence 404
http.Error(w, "Bad Request: "+invalidIDMsg, http.StatusBadRequest)
return
}
pup, err := phs.Storage.ReadPuppy(id)
if err != nil {
handleStorerError(w, err)
return
}
render.JSON(w, r, pup) // if retrieved from storage can now send it back out after json serialisation
}
// Implementing 2/4 methods of PuppyHandler: puppy handler for POST /api/puppy/
func (phs *PuppyHandlerAndStorer) handlePost(w http.ResponseWriter, r *http.Request) {
var pup puppy.Puppy
// the following block is saying if I take the incoming body of the request and if I can successfully unmarshal
// into the Go Puppy object this means the requestor has sent me valid puppy JSON so I can work with that
if err := render.DecodeJSON(r.Body, &pup); err != nil {
http.Error(w, "Bad Request: "+invalidJSONMsg, http.StatusBadRequest)
return
}
// Actually create a new Puppy in store
id, err := phs.Storage.CreatePuppy(&pup)
if err != nil {
handleStorerError(w, err)
return
}
// else if all is well tell the client the puppy has been successfully created
render.Status(r, http.StatusCreated)
pup.ID = id // tacking on the id generated by the server
render.JSON(w, r, pup) // confirm this is what has been created on backend
}
// Implementing 3/4 methods of PuppyHandler: puppy handler for PUT /api/puppy/{id}
func (phs *PuppyHandlerAndStorer) handlePut(w http.ResponseWriter, r *http.Request) {
// parse incoming request url param
id, err := strconv.Atoi(chi.URLParam(r, "id")) // strip off the {id} part of the endpoint and convert to int
if err != nil {
http.Error(w, "Unprocessable Entity: "+invalidIDMsg, http.StatusBadRequest)
return
}
// check if puppy with given id exists in storage
_, err = phs.Storage.ReadPuppy(id)
if err != nil {
handleStorerError(w, err)
return
}
// now deal with the body of the PUT request
var pup puppy.Puppy
// decode json and unmarshal into var pup
if err := render.DecodeJSON(r.Body, &pup); err != nil {
http.Error(w, "Unprocessable Entity: "+invalidJSONMsg, http.StatusUnprocessableEntity)
return
}
// Actually update corresponding Puppy in store
err = phs.Storage.UpdatePuppy(id, &pup)
if err != nil {
handleStorerError(w, err)
return
}
// else if all is well tell the client the puppy has been successfully updated
render.Status(r, http.StatusCreated)
render.JSON(w, r, pup) // confirm this is what has been created on backend
}
// Implementing 4/4 methods of PuppyHandler: puppy handler for DELETE /api/puppy/{id}
func (phs *PuppyHandlerAndStorer) handleDelete(w http.ResponseWriter, r *http.Request) {
// parse incoming request url param
id, err := strconv.Atoi(chi.URLParam(r, "id")) // strip off the {id} part of the endpoint and convert to int
if err != nil {
// if err != nil means user didn't provide proper id hence 400
http.Error(w, "Bad Request: "+invalidIDMsg, http.StatusBadRequest)
return
}
// actually perform delete operation
err = phs.Storage.DeletePuppy(id)
if err != nil {
handleStorerError(w, err)
return
}
// give client feedback. Opted for this rather than render.Status(r, http.StatusNoContent) as gives more feedback
render.JSON(w, r, "Puppy successfully deleted")
}
// SetupRoutes does mapping within endpoints and their corresponding handlers
// It also adds in a subrouter into our base router which ca handle subpath routing
func SetupRoutes(r chi.Router, phs PuppyHandlerAndStorer) {
r.Route("/api/puppy", func(r chi.Router) {
r.Get("/{id}", phs.handleGet)
r.Post("/", phs.handlePost)
r.Put("/{id}", phs.handlePut)
r.Delete("/{id}", phs.handleDelete)
})
}