Clean up JSON responses with responder library

This commit is contained in:
zikaeroh 2020-06-02 23:41:04 -07:00
parent cf23018d70
commit e5018c38c2
3 changed files with 120 additions and 51 deletions

View File

@ -1,17 +1,12 @@
package main package main
import ( import (
"net/http"
"reflect" "reflect"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/tomwright/queryparam/v4" "github.com/tomwright/queryparam/v4"
) )
func httpErr(w http.ResponseWriter, code int) {
http.Error(w, http.StatusText(code), code)
}
func stringPtr(s string) *string { func stringPtr(s string) *string {
return &s return &s
} }

View File

@ -0,0 +1,65 @@
package responder
import (
"encoding/json"
"net/http"
)
type defaultResponse struct {
Status int `json:"status"`
Message string `json:"message"`
}
type response struct {
value *interface{}
status int
pretty bool
}
type Option func(r *response)
func Pretty(pretty bool) Option {
return func(r *response) {
r.pretty = pretty
}
}
func Status(status int) Option {
return func(r *response) {
r.status = status
}
}
func Body(v interface{}) Option {
return func(r *response) {
r.value = &v
}
}
func Respond(w http.ResponseWriter, opts ...Option) {
r := &response{
status: http.StatusOK,
}
for _, opt := range opts {
opt(r)
}
enc := json.NewEncoder(w)
if r.pretty {
enc.SetIndent(" ", "")
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(r.status)
if v := r.value; v != nil {
_ = enc.Encode(*v)
} else {
_ = enc.Encode(&defaultResponse{
Status: r.status,
Message: http.StatusText(r.status),
})
}
}

101
main.go
View File

@ -17,6 +17,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/tomwright/queryparam/v4" "github.com/tomwright/queryparam/v4"
"github.com/zikaeroh/codies/internal/protocol" "github.com/zikaeroh/codies/internal/protocol"
"github.com/zikaeroh/codies/internal/responder"
"github.com/zikaeroh/codies/internal/server" "github.com/zikaeroh/codies/internal/server"
"github.com/zikaeroh/codies/internal/version" "github.com/zikaeroh/codies/internal/version"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -83,19 +84,18 @@ func main() {
r.Use(middleware.NoCache) r.Use(middleware.NoCache)
r.Get("/api/time", func(w http.ResponseWriter, r *http.Request) { r.Get("/api/time", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json") responder.Respond(w, responder.Body(&protocol.TimeResponse{Time: time.Now()}))
_ = json.NewEncoder(w).Encode(&protocol.TimeResponse{Time: time.Now()})
}) })
r.Get("/api/stats", func(w http.ResponseWriter, r *http.Request) { r.Get("/api/stats", func(w http.ResponseWriter, r *http.Request) {
rooms, clients := srv.Stats() rooms, clients := srv.Stats()
responder.Respond(w,
enc := json.NewEncoder(w) responder.Body(&protocol.StatsResponse{
enc.SetIndent("", " ") Rooms: rooms,
_ = enc.Encode(&protocol.StatsResponse{ Clients: clients,
Rooms: rooms, }),
Clients: clients, responder.Pretty(true),
}) )
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
@ -106,18 +106,16 @@ func main() {
r.Get("/api/exists", func(w http.ResponseWriter, r *http.Request) { r.Get("/api/exists", func(w http.ResponseWriter, r *http.Request) {
query := &protocol.ExistsQuery{} query := &protocol.ExistsQuery{}
if err := queryparam.Parse(r.URL.Query(), query); err != nil { if err := queryparam.Parse(r.URL.Query(), query); err != nil {
httpErr(w, http.StatusBadRequest) responder.Respond(w, responder.Status(http.StatusBadRequest))
return return
} }
room := srv.FindRoomByID(query.RoomID) room := srv.FindRoomByID(query.RoomID)
if room == nil { if room == nil {
w.WriteHeader(http.StatusNotFound) responder.Respond(w, responder.Status(http.StatusNotFound))
} else { } else {
w.WriteHeader(http.StatusOK) responder.Respond(w, responder.Status(http.StatusOK))
} }
_, _ = w.Write([]byte("."))
}) })
r.Post("/api/room", func(w http.ResponseWriter, r *http.Request) { r.Post("/api/room", func(w http.ResponseWriter, r *http.Request) {
@ -125,76 +123,88 @@ func main() {
req := &protocol.RoomRequest{} req := &protocol.RoomRequest{}
if err := json.NewDecoder(r.Body).Decode(req); err != nil { if err := json.NewDecoder(r.Body).Decode(req); err != nil {
httpErr(w, http.StatusBadRequest) responder.Respond(w, responder.Status(http.StatusBadRequest))
return return
} }
w.Header().Add("Content-Type", "application/json")
if msg, valid := req.Valid(); !valid { if msg, valid := req.Valid(); !valid {
resp := &protocol.RoomResponse{ responder.Respond(w,
Error: stringPtr(msg), responder.Status(http.StatusBadRequest),
} responder.Body(&protocol.RoomResponse{
w.WriteHeader(http.StatusBadRequest) Error: stringPtr(msg),
_ = json.NewEncoder(w).Encode(resp) }),
)
return return
} }
resp := &protocol.RoomResponse{} var room *server.Room
if req.Create { if req.Create {
room, err := srv.CreateRoom(req.RoomName, req.RoomPass) var err error
room, err = srv.CreateRoom(req.RoomName, req.RoomPass)
if err != nil { if err != nil {
switch err { switch err {
case server.ErrRoomExists: case server.ErrRoomExists:
resp.Error = stringPtr("Room already exists.") responder.Respond(w,
w.WriteHeader(http.StatusBadRequest) responder.Status(http.StatusBadRequest),
responder.Body(&protocol.RoomResponse{
Error: stringPtr("Room already exists."),
}),
)
case server.ErrTooManyRooms: case server.ErrTooManyRooms:
resp.Error = stringPtr("Too many rooms.") responder.Respond(w,
w.WriteHeader(http.StatusServiceUnavailable) responder.Status(http.StatusServiceUnavailable),
responder.Body(&protocol.RoomResponse{
Error: stringPtr("Too many rooms."),
}),
)
default: default:
resp.Error = stringPtr("An unknown error occurred.") responder.Respond(w,
w.WriteHeader(http.StatusInternalServerError) responder.Status(http.StatusInternalServerError),
responder.Body(&protocol.RoomResponse{
Error: stringPtr("An unknown error occurred."),
}),
)
} }
} else { return
resp.ID = &room.ID
w.WriteHeader(http.StatusOK)
} }
} else { } else {
room := srv.FindRoom(req.RoomName) room = srv.FindRoom(req.RoomName)
if room == nil || room.Password != req.RoomPass { if room == nil || room.Password != req.RoomPass {
resp.Error = stringPtr("Room not found or password does not match.") responder.Respond(w,
w.WriteHeader(http.StatusNotFound) responder.Status(http.StatusNotFound),
} else { responder.Body(&protocol.RoomResponse{
resp.ID = &room.ID Error: stringPtr("Room not found or password does not match."),
w.WriteHeader(http.StatusOK) }),
)
return
} }
} }
_ = json.NewEncoder(w).Encode(resp) responder.Respond(w, responder.Body(&protocol.RoomResponse{
ID: &room.ID,
}))
}) })
r.Get("/api/ws", func(w http.ResponseWriter, r *http.Request) { r.Get("/api/ws", func(w http.ResponseWriter, r *http.Request) {
query := &protocol.WSQuery{} query := &protocol.WSQuery{}
if err := queryparam.Parse(r.URL.Query(), query); err != nil { if err := queryparam.Parse(r.URL.Query(), query); err != nil {
httpErr(w, http.StatusBadRequest) responder.Respond(w, responder.Status(http.StatusBadRequest))
return return
} }
if _, valid := query.Valid(); !valid { if _, valid := query.Valid(); !valid {
httpErr(w, http.StatusBadRequest) responder.Respond(w, responder.Status(http.StatusBadRequest))
return return
} }
room := srv.FindRoomByID(query.RoomID) room := srv.FindRoomByID(query.RoomID)
if room == nil { if room == nil {
httpErr(w, http.StatusNotFound) responder.Respond(w, responder.Status(http.StatusBadRequest))
return return
} }
c, err := websocket.Accept(w, r, wsOpts) c, err := websocket.Accept(w, r, wsOpts)
if err != nil { if err != nil {
log.Println(err)
return return
} }
@ -258,7 +268,6 @@ func checkVersion(next http.Handler) http.Handler {
if r.Header.Get("Upgrade") == "websocket" { if r.Header.Get("Upgrade") == "websocket" {
c, err := websocket.Accept(w, r, wsOpts) c, err := websocket.Accept(w, r, wsOpts)
if err != nil { if err != nil {
log.Println(err)
return return
} }
c.Close(4418, reason) c.Close(4418, reason)