package web import ( "encoding/json" "net/http" "path/filepath" "strconv" "sync" "github.com/gorilla/mux" ) // Stats implement a statistics counter for hits to the FizzBuzz endpoint. type Stats struct { sync.Mutex hits map[string]int maxKey string maxHits int } // Incr records a hit in a way that supports reporting in O(1) time and space. func (s *Stats) Incr(int1, int2, limit int, str1, str2 string) { s.Lock() defer s.Unlock() key := filepath.Join(strconv.Itoa(int1), strconv.Itoa(int2), strconv.Itoa(limit), str1, str2) s.hits[key]++ if s.hits[key] <= s.maxHits { return } s.maxHits = s.hits[key] s.maxKey = key } // MarshalJSON implements [json.Marshaler]. func (s *Stats) MarshalJSON() ([]byte, error) { raw := map[string]any{ "key": s.maxKey, "hits": s.maxHits, } return json.Marshal(raw) } // Handler provides the statistics endpoint. func (s *Stats) Handler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set(CT, JSON) enc := json.NewEncoder(w) enc.SetEscapeHTML(true) enc.SetIndent("", " ") enc.Encode(s) } } // Middleware provides the statistics middleware. func (s *Stats) Middleware() mux.MiddlewareFunc { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { int1, int2, limit, str1, str2, status := argsFromRequest(r) if status >= http.StatusBadRequest { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } s.Incr(int1, int2, limit, str1, str2) h.ServeHTTP(w, r) }) } } func NewStats() *Stats { return &Stats{hits: make(map[string]int)} }