/
server.go
176 lines (161 loc) · 5.15 KB
/
server.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2014 Google Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to writing, software distributed
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
// This package implements a simple HTTP server providing a REST API to a task handler.
//
// It provides four methods:
//
// GET /task/ Retrieves all the tasks.
// POST /task/ Creates a new task given a title.
// GET /task/{taskID} Retrieves the task with the given id.
// PUT /task/{taskID} Updates the task with the given id.
//
// Every method below gives more information about every API call, its parameters, and its results.
package server
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/campoy/todo/task"
"github.com/gorilla/mux"
)
var tasks = task.NewTaskManager()
const PathPrefix = "/task/"
func RegisterHandlers() {
r := mux.NewRouter()
r.HandleFunc(PathPrefix, errorHandler(ListTasks)).Methods("GET")
r.HandleFunc(PathPrefix, errorHandler(NewTask)).Methods("POST")
r.HandleFunc(PathPrefix+"{id}", errorHandler(GetTask)).Methods("GET")
r.HandleFunc(PathPrefix+"{id}", errorHandler(UpdateTask)).Methods("PUT")
http.Handle(PathPrefix, r)
}
// badRequest is handled by setting the status code in the reply to StatusBadRequest.
type badRequest struct{ error }
// notFound is handled by setting the status code in the reply to StatusNotFound.
type notFound struct{ error }
// errorHandler wraps a function returning an error by handling the error and returning a http.Handler.
// If the error is of the one of the types defined above, it is handled as described for every type.
// If the error is of another type, it is considered as an internal error and its message is logged.
func errorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := f(w, r)
if err == nil {
return
}
switch err.(type) {
case badRequest:
http.Error(w, err.Error(), http.StatusBadRequest)
case notFound:
http.Error(w, "task not found", http.StatusNotFound)
default:
log.Println(err)
http.Error(w, "oops", http.StatusInternalServerError)
}
}
}
// ListTask handles GET requests on /task.
// There's no parameters and it returns an object with a Tasks field containing a list of tasks.
//
// Example:
//
// req: GET /task/
// res: 200 {"Tasks": [
// {"ID": 1, "Title": "Learn Go", "Done": false},
// {"ID": 2, "Title": "Buy bread", "Done": true}
// ]}
func ListTasks(w http.ResponseWriter, r *http.Request) error {
res := struct{ Tasks []*task.Task }{tasks.All()}
return json.NewEncoder(w).Encode(res)
}
// NewTask handles POST requests on /task.
// The request body must contain a JSON object with a Title field.
// The status code of the response is used to indicate any error.
//
// Examples:
//
// req: POST /task/ {"Title": ""}
// res: 400 empty title
//
// req: POST /task/ {"Title": "Buy bread"}
// res: 200
func NewTask(w http.ResponseWriter, r *http.Request) error {
req := struct{ Title string }{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return badRequest{err}
}
t, err := task.NewTask(req.Title)
if err != nil {
return badRequest{err}
}
return tasks.Save(t)
}
// parseID obtains the id variable from the given request url,
// parses the obtained text and returns the result.
func parseID(r *http.Request) (int64, error) {
txt, ok := mux.Vars(r)["id"]
if !ok {
return 0, fmt.Errorf("task id not found")
}
return strconv.ParseInt(txt, 10, 0)
}
// GetTask handles GET requsts to /task/{taskID}.
// There's no parameters and it returns a JSON encoded task.
//
// Examples:
//
// req: GET /task/1
// res: 200 {"ID": 1, "Title": "Buy bread", "Done": true}
//
// req: GET /task/42
// res: 404 task not found
func GetTask(w http.ResponseWriter, r *http.Request) error {
id, err := parseID(r)
log.Println("Task is ", id)
if err != nil {
return badRequest{err}
}
t, ok := tasks.Find(id)
log.Println("Found", ok)
if !ok {
return notFound{}
}
return json.NewEncoder(w).Encode(t)
}
// UpdateTask handles PUT requests to /task/{taskID}.
// The request body must contain a JSON encoded task.
//
// Example:
//
// req: PUT /task/1 {"ID": 1, "Title": "Learn Go", "Done": true}
// res: 200
//
// req: PUT /task/2 {"ID": 2, "Title": "Learn Go", "Done": true}
// res: 400 inconsistent task IDs
func UpdateTask(w http.ResponseWriter, r *http.Request) error {
id, err := parseID(r)
if err != nil {
return badRequest{err}
}
var t task.Task
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
return badRequest{err}
}
if t.ID != id {
return badRequest{fmt.Errorf("inconsistent task IDs")}
}
if _, ok := tasks.Find(id); !ok {
return notFound{}
}
return tasks.Save(&t)
}