Skip to content

Commit

Permalink
add encode json and validator helper func
Browse files Browse the repository at this point in the history
  • Loading branch information
The-Gleb committed Mar 12, 2024
1 parent 7b7c0bb commit 3373ce5
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 143 deletions.
28 changes: 16 additions & 12 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"

_ "net/http/pprof"

Expand Down Expand Up @@ -38,13 +38,15 @@ func main() {
BuildVersion, BuildDate, BuildCommit,
)

if err := Run(); err != nil {
if err := Run(context.Background()); err != nil {
log.Fatal(err)
}

}

func Run() error {
func Run(ctx context.Context) error {
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT)
defer cancel()

go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
Expand All @@ -66,7 +68,7 @@ func Run() error {
}

if config.DatabaseDSN != "" {
db, err := database.NewMetricDB(context.Background(), config.DatabaseDSN)
db, err := database.NewMetricDB(ctx, config.DatabaseDSN)
if err != nil {
return err
}
Expand Down Expand Up @@ -111,24 +113,26 @@ func Run() error {

var wg sync.WaitGroup

ctx, cancelCtx := context.WithCancel(context.Background())

wg.Add(1)
go func() {
defer wg.Done()
backupService.Run(ctx)
err := backupService.Run(ctx)
if err != nil {
cancel()
}
}()

wg.Add(1)
go func() {
defer wg.Done()
ServerShutdownSignal := make(chan os.Signal, 1)
signal.Notify(ServerShutdownSignal, syscall.SIGINT)
// ServerShutdownSignal := make(chan os.Signal, 1)
// signal.Notify(ServerShutdownSignal, syscall.SIGINT)

<-ServerShutdownSignal
<-ctx.Done()

cancelCtx()
err := s.Shutdown(context.Background())
ctxShutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := s.Shutdown(ctxShutdown)
if err != nil {
panic(err)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.20
require (
github.com/caarlos0/env v3.5.0+incompatible
github.com/go-chi/chi/v5 v5.0.10
github.com/go-playground/locales v0.14.1
github.com/go-resty/resty/v2 v2.9.1
github.com/golang-migrate/migrate/v4 v4.17.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down
20 changes: 10 additions & 10 deletions internal/controller/http/v1/handler/get_metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"

Expand All @@ -16,7 +17,7 @@ const (
)

type GetMetricUsecase interface {
GetMetric(ctx context.Context, metrics entity.Metric) (entity.Metric, error)
GetMetric(ctx context.Context, metric entity.GetMetricDTO) (entity.Metric, error)
}

// getMetricHandler receives metric type, name in url params.
Expand Down Expand Up @@ -47,19 +48,18 @@ func (h *getMetricHandler) Middlewares(md ...func(http.Handler) http.Handler) *g

func (h *getMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

mType := chi.URLParam(r, "type")
mName := chi.URLParam(r, "name")
metric := entity.Metric{
MType: mType,
ID: mName,
dto := entity.GetMetricDTO{
MType: chi.URLParam(r, "type"),
ID: chi.URLParam(r, "name"),
}

if mType == "" || mName == "" {
http.Error(rw, "invalid request, metric type or metric name is empty", http.StatusBadRequest)
problems := dto.Valid()
if len(problems) > 0 {
http.Error(rw, fmt.Sprintf("invalid %T: %d problems", dto, len(problems)), http.StatusBadRequest)
return
}

metric, err := h.usecase.GetMetric(r.Context(), metric)
metric, err := h.usecase.GetMetric(r.Context(), dto)
if err != nil {

if errors.Is(err, repository.ErrNotFound) {
Expand All @@ -71,7 +71,7 @@ func (h *getMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
}

switch mType {
switch metric.MType {
case "gauge":

strVal := strconv.FormatFloat(*metric.Value, 'g', -1, 64)
Expand Down
18 changes: 5 additions & 13 deletions internal/controller/http/v1/handler/get_metric_json.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package v1

import (
"encoding/json"
"errors"
"fmt"
"net/http"

v1 "github.com/The-Gleb/go_metrics_and_alerting/internal/controller/http/v1"
"github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity"
"github.com/The-Gleb/go_metrics_and_alerting/internal/logger"
"github.com/The-Gleb/go_metrics_and_alerting/internal/repository"
Expand Down Expand Up @@ -44,19 +44,13 @@ func (h *getMetricJSONHandler) Middlewares(md ...func(http.Handler) http.Handler

func (h *getMetricJSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

var metric entity.Metric
err := json.NewDecoder(r.Body).Decode(&metric)
dto, _, err := v1.DecodeValid[entity.GetMetricDTO](r)
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}

if metric.ID == "" || metric.MType == "" {
http.Error(rw, "invalid request body,some fields are empty, but they shouldn`t", http.StatusBadRequest)
return
}

metric, err = h.usecase.GetMetric(r.Context(), metric)
metric, err := h.usecase.GetMetric(r.Context(), dto)
if err != nil {
err = fmt.Errorf("handlers.GetMetricJSON: %w", err)
logger.Log.Error(err)
Expand All @@ -72,12 +66,10 @@ func (h *getMetricJSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request
}
}

b, err := json.Marshal(metric)
err = v1.Encode(rw, r, 200, metric)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}

rw.Write(b)

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
)

func Test_getMetricJSONHandler_ServeHTTP(t *testing.T) {

type args struct {
rw http.ResponseWriter
r *http.Request
Expand Down
76 changes: 30 additions & 46 deletions internal/controller/http/v1/handler/update_metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,43 +48,20 @@ func (h *updateMetricHandler) Middlewares(md ...func(http.Handler) http.Handler)

func (h *updateMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

mType := chi.URLParam(r, "type")
mName := chi.URLParam(r, "name")
metric := entity.Metric{
MType: mType,
ID: mName,
dto := entity.Metric{
MType: chi.URLParam(r, "type"),
ID: chi.URLParam(r, "name"),
}

if mType == "" || mName == "" {
http.Error(rw, "invalid request body,some fields are empty, but they shouldn`t", http.StatusBadRequest)
return
}

switch mType {
switch dto.MType {
case "gauge":
val, err := strconv.ParseFloat(chi.URLParam(r, "value"), 64)
if err != nil {
err = fmt.Errorf("getMetricHandler: %w", err)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
metric.Value = &val

metric, err = h.usecase.UpdateMetric(r.Context(), metric)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
http.Error(rw, err.Error(), http.StatusNotFound)
return
} else {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
}

// strVal := strconv.FormatFloat(*metric.Value, 'g', -1, 64)
// rw.Write([]byte(strVal))

rw.Write([]byte(chi.URLParam(r, "value")))
dto.Value = &val

case "counter":
delta, err := strconv.ParseInt(chi.URLParam(r, "value"), 10, 64)
Expand All @@ -93,28 +70,35 @@ func (h *updateMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
metric.Delta = &delta

metric, err = h.usecase.UpdateMetric(r.Context(), metric)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
rw.WriteHeader(http.StatusNotFound)
http.Error(rw, err.Error(), http.StatusNotFound)
return
} else {
rw.WriteHeader(http.StatusBadRequest)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
}
dto.Delta = &delta
}

strVal := strconv.FormatInt(*metric.Delta, 10)
rw.Write([]byte(strVal))
problems := dto.Valid()
if len(problems) > 0 {
http.Error(rw, fmt.Sprintf("invalid %T: %d problems", dto, len(problems)), http.StatusBadRequest)
return
}

// rw.Write([]byte(chi.URLParam(r, "value")))
metric, err := h.usecase.UpdateMetric(r.Context(), dto)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
http.Error(rw, err.Error(), http.StatusNotFound)
return
} else {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
}

switch metric.MType {
case "gauge":
rw.Write([]byte(fmt.Sprint(metric.Value)))
return
case "counter":
rw.Write([]byte(fmt.Sprint(metric.Delta)))
return
default:
http.Error(rw, "invalid metric type", http.StatusBadRequest)
http.Error(rw, "metric with invalid type returned", http.StatusInternalServerError)
return
}

Expand Down
19 changes: 5 additions & 14 deletions internal/controller/http/v1/handler/update_metric_json.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package v1

import (
"encoding/json"
"fmt"
"net/http"

v1 "github.com/The-Gleb/go_metrics_and_alerting/internal/controller/http/v1"
"github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity"
"github.com/go-chi/chi/v5"
)
Expand Down Expand Up @@ -41,32 +41,23 @@ func (h *updateMetricJSONHandler) Middlewares(md ...func(http.Handler) http.Hand

func (h *updateMetricJSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

var metric entity.Metric
err := json.NewDecoder(r.Body).Decode(&metric)
dto, _, err := v1.DecodeValid[entity.Metric](r)
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}

if metric.MType == "" || metric.ID == "" ||
(metric.Delta == nil && metric.Value == nil) {
http.Error(rw, "invalid request body,some fields are empty, but they shouldn`t", http.StatusBadRequest)
return
}

metric, err = h.usecase.UpdateMetric(r.Context(), metric)
metric, err := h.usecase.UpdateMetric(r.Context(), dto)
if err != nil {
err = fmt.Errorf("updateMetricJSONHandler: %w", err)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}

b, err := json.Marshal(metric)
err = v1.Encode(rw, r, 200, metric)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}

rw.Write(b)

}
27 changes: 27 additions & 0 deletions internal/controller/http/v1/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package http

import (
"encoding/json"
"fmt"
"net/http"
)

func DecodeValid[T Validator](r *http.Request) (T, map[string]string, error) {
var v T
if err := json.NewDecoder(r.Body).Decode(&v); err != nil {
return v, nil, fmt.Errorf("decode json: %w", err)
}
if problems := v.Valid(); len(problems) > 0 {
return v, problems, fmt.Errorf("invalid %T: %d problems", v, len(problems))
}
return v, nil, nil
}

func Encode[T any](w http.ResponseWriter, r *http.Request, status int, v T) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
return fmt.Errorf("encode json: %w", err)
}
return nil
}
5 changes: 5 additions & 0 deletions internal/controller/http/v1/validatior.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package http

type Validator interface {
Valid() (problems map[string]string)
}
Loading

0 comments on commit 3373ce5

Please sign in to comment.