package main import ( "context" "errors" "flag" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/render" ) //var routes = flag.Bool("routes", false, "Generate router documentation") func main() { flag.Parse() r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.URLFormat) r.Use(render.SetContentType(render.ContentTypeJSON)) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello world")) }) r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pong")) }) r.Get("/panic", func(w http.ResponseWriter, r *http.Request) { panic("oh no") }) r.Route("/messages", func(r chi.Router) { r.Route("/{messageID}", func(r chi.Router) { r.Use(MessageCtx) // Load message r.Get("/", GetMessage) }) }) http.ListenAndServe(":3000", r) } type ErrResponse struct { Err error `json:"-"` // low-level runtime error HTTPStatusCode int `json:"-"` // http response status code StatusText string `json:"status"` // user-level status message AppCode int64 `json:"code,omitempty"` // application-specific error code ErrorText string `json:"error,omitempty"` // application-level error message, for debugging } func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { render.Status(r, e.HTTPStatusCode) return nil } func ErrInvalidRequest(err error) render.Renderer { return &ErrResponse{ Err: err, HTTPStatusCode: 400, StatusText: "Invalid request.", ErrorText: err.Error(), } } func ErrRender(err error) render.Renderer { return &ErrResponse{ Err: err, HTTPStatusCode: 422, StatusText: "Error rendering response.", ErrorText: err.Error(), } } var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."} type messageKey struct{} func MessageCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var message *Message var err error if messageID := chi.URLParam(r, "messageID"); messageID != "" { message, err = dbGetMessage(messageID) } else { render.Render(w, r, ErrNotFound) return } if err != nil { render.Render(w, r, ErrNotFound) return } ctx := context.WithValue(r.Context(), messageKey{}, message) next.ServeHTTP(w, r.WithContext(ctx)) }) } func GetMessage(w http.ResponseWriter, r *http.Request) { message, ok := r.Context().Value(messageKey{}).(*Message) if !ok || message == nil { render.Render(w, r, ErrNotFound) return } if err := render.Render(w, r, NewMessageResponse(message)); err != nil { render.Render(w, r, ErrRender(err)) return } } func NewMessageResponse(message *Message) *MessageResponse { resp := &MessageResponse{Message: message} if resp.User == nil { if user, _ := dbGetUser(resp.UserID); user != nil { resp.User = NewUserPayloadResponse(user) } } return resp } func NewUserPayloadResponse(user *User) *UserPayload { return &UserPayload{User: user} } type User struct { ID int64 `json:"id"` Name string `json:"name"` } type Message struct { ID string `json:"id"` UserID int64 `json:"user_id"` Body string `json:"body"` Timestamp int64 `json:"timestamp"` } var users = []*User{ {ID: 1, Name: "duby"}, {ID: 2, Name: "astolfo"}, } var messages = []*Message{ {ID: "1", UserID: 1, Body: "hello", Timestamp: 1234567890}, {ID: "2", UserID: 2, Body: "world", Timestamp: 1234567890}, {ID: "3", UserID: 1, Body: "abababa", Timestamp: 1234567890}, {ID: "4", UserID: 2, Body: "bitch", Timestamp: 1234567890}, } type UserPayload struct { *User } type MessageRequest struct { *Message User *UserPayload `json:"user"` ProtectedID string `json:"id"` } type MessageResponse struct { *Message User *UserPayload `json:"user,omitempty"` Elapsed int64 `json:"elapsed"` } func (mr *MessageResponse) Render(w http.ResponseWriter, r *http.Request) error { return nil } func dbGetUser(id int64) (*User, error) { for _, u := range users { if u.ID == id { return u, nil } } return nil, errors.New("User not found") } func dbGetMessage(id string) (*Message, error) { for _, a := range messages { if a.ID == id { return a, nil } } return nil, errors.New("Message not found") }