/
handler.go
155 lines (125 loc) · 3.77 KB
/
handler.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
package planets
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"github.com/go-chi/chi"
"github.com/renanferr/swapi-golang-rest-api/pkg/adding"
"github.com/renanferr/swapi-golang-rest-api/pkg/listing"
)
// Router returns the /planets router
func Handler(a adding.Service, l listing.Service) *chi.Mux {
r := chi.NewRouter()
r.Post("/", addPlanet(a))
r.Get("/", getPlanets(l))
r.Get("/{planetID}", getPlanet(l))
return r
}
type validationErrorResponse struct {
Message string `json:"message"`
Fields map[string]string `json:"fields"`
}
type errorResponse struct {
Message string `json:"message"`
}
func sendErrorResponse(w http.ResponseWriter, status int, message string) {
w.WriteHeader(status)
w.Header().Set("Content-type", "application/json")
if err := json.NewEncoder(w).Encode(&errorResponse{message}); err != nil {
log.Panicf("error encoding error response: %s", err)
}
}
// addPlanet returns a handler for POST /planets requests
func addPlanet(s adding.Service) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var newPlanet adding.Planet
err := decoder.Decode(&newPlanet)
if err != nil {
log.Printf("error decoding planet: %s", err.Error())
sendErrorResponse(w, http.StatusBadRequest, "JSON decoding error")
return
}
id, err := s.AddPlanet(r.Context(), newPlanet)
var e *adding.ValidationError
if errors.As(err, &e) {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&validationErrorResponse{"validation error", e.Fields})
if err != nil {
log.Panicf("error encoding bad request response: %s", err.Error())
}
return
}
if err != nil {
log.Panicf("error adding planet: %s", err)
}
w.Header().Set("Location", fmt.Sprintf("/%s", id))
w.WriteHeader(http.StatusCreated)
}
}
func getOffset(limit int64, page int64) int64 {
if page < 1 {
return 0
}
return (page - 1) * limit
}
func getPaginationInfo(query url.Values) (int64, int64, error) {
info := map[string]int64{"limit": 20, "page": 1}
var err error
for k := range info {
v := query.Get(k)
if v != "" {
val, err := strconv.ParseInt(v, 10, 64)
info[k] = val
if err != nil {
log.Println(err)
return 0, 0, fmt.Errorf("%s value must be an integer. got: %s", k, v)
}
}
}
return info["limit"], info["page"], err
}
// getPlanets returns a handler for GET /planets requests
func getPlanets(s listing.Service) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
limit, page, err := getPaginationInfo(r.URL.Query())
if err != nil {
log.Println(err)
sendErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if limit < 1 || page < 1 {
err = errors.New("pagination out of range")
log.Println(err)
sendErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
offset := getOffset(limit, page)
list, total := s.GetPlanets(r.Context(), offset, limit)
w.Header().Set("X-Total-Count", strconv.FormatInt(total, 10))
json.NewEncoder(w).Encode(list)
}
}
// getPlanet returns a handler for GET /planets/:id requests
func getPlanet(s listing.Service) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ID := chi.URLParam(r, "planetID")
planet, err := s.GetPlanet(r.Context(), ID)
if err != nil {
if errors.Is(err, listing.ErrPlanetNotFound) {
log.Printf("Planet with ID %s not found", ID)
w.WriteHeader(http.StatusNotFound)
return
}
panic(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(planet)
}
}