Răsfoiți Sursa

feat: add request validation middleware and error handling

lblt 2 săptămâni în urmă
părinte
comite
0b022fddc5
6 a modificat fișierele cu 100 adăugiri și 7 ștergeri
  1. 4 5
      go.mod
  2. 7 2
      go.sum
  3. 2 0
      pkg/config/config.go
  4. 1 0
      pkg/server/errors.go
  5. 63 0
      pkg/server/middleware.go
  6. 23 0
      pkg/validation/validation.go

+ 4 - 5
go.mod

@@ -3,18 +3,19 @@ module git.linuxforward.com/byom/byom-golang-lib
 go 1.22.5
 
 require (
+	github.com/gin-gonic/gin v1.10.0
 	github.com/mattn/go-sqlite3 v1.14.24
 	github.com/minio/minio-go/v7 v7.0.86
+	github.com/wneessen/go-mail v0.6.1
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
 	github.com/bytedance/sonic v1.12.8 // indirect
 	github.com/bytedance/sonic/loader v0.2.3 // indirect
 	github.com/cloudwego/base64x v0.1.5 // indirect
-	github.com/cloudwego/iasm v0.2.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/gin-contrib/sse v1.0.0 // indirect
-	github.com/gin-gonic/gin v1.10.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.25.0 // indirect
@@ -26,10 +27,8 @@ require (
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
-	github.com/wneessen/go-mail v0.6.1 // indirect
 	golang.org/x/arch v0.14.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 require (
@@ -37,7 +36,7 @@ require (
 	github.com/go-ini/ini v1.67.0 // indirect
 	github.com/goccy/go-json v0.10.5 // indirect
 	github.com/golang-jwt/jwt/v5 v5.2.1
-	github.com/google/uuid v1.6.0 // indirect
+	github.com/google/uuid v1.6.0
 	github.com/klauspost/compress v1.17.11 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.9 // indirect
 	github.com/minio/crc64nvme v1.0.0 // indirect

+ 7 - 2
go.sum

@@ -5,9 +5,9 @@ github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wio
 github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
 github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
-github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -19,6 +19,8 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 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-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -29,6 +31,7 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -61,6 +64,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
 github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
@@ -76,6 +80,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
@@ -160,9 +165,9 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
 google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 2 - 0
pkg/config/config.go

@@ -9,6 +9,7 @@ import (
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/database"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/logger"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/server"
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/smtp"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/storage"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/webhook"
 	"gopkg.in/yaml.v3"
@@ -21,6 +22,7 @@ type Config struct {
 	Database       *database.Config `yaml:"database"`
 	Log            *logger.Config   `yaml:"log"`
 	Storage        *storage.Config  `yaml:"storage"`
+	Smtp           *smtp.Config     `yaml:"smtp,omitempty"`
 	Webhook        *webhook.Config  `yaml:"webhook,omitempty"`
 	CloudComputing *CloudComputing  `yaml:"cloud_computing,omitempty"`
 	SocialNetworks *SocialNetworks  `yaml:"social_networks,omitempty"`

+ 1 - 0
pkg/server/errors.go

@@ -33,6 +33,7 @@ var (
 	// HTTP error types
 	ErrBadRequest         = errors.New("bad request")
 	ErrUnauthorized       = errors.New("unauthorized")
+	ErrValidationFailed   = errors.New("validation failed")
 	ErrForbidden          = errors.New("forbidden")
 	ErrNotFound           = errors.New("not found")
 	ErrMethodNotAllowed   = errors.New("method not allowed")

+ 63 - 0
pkg/server/middleware.go

@@ -5,6 +5,7 @@ import (
 	"runtime/debug"
 	"time"
 
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/validation"
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
 )
@@ -133,3 +134,65 @@ func (s *Server) errorHandlerMiddleware() gin.HandlerFunc {
 		}
 	}
 }
+
+// ValidateRequest returns a middleware function that validates request bodies against
+// the specified request type. It integrates with the gin context for error handling and logging.
+func ValidateRequest(validateFn func(interface{}) []validation.ValidationError) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		requestID := c.GetString("RequestID")
+		var req interface{}
+
+		// Bind JSON request body
+		if err := c.ShouldBindJSON(&req); err != nil {
+			bindErr := NewRequestError(
+				"validate_request",
+				c.Request.Method,
+				c.Request.URL.Path,
+				http.StatusBadRequest,
+				err,
+				"invalid request format",
+			)
+
+			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
+				"error":      bindErr.Error(),
+				"request_id": requestID,
+				"errors":     []validation.ValidationError{{Field: "body", Message: err.Error()}},
+			})
+			return
+		}
+
+		// Validate request struct
+		if errors := validateFn(req); len(errors) > 0 {
+			validationErr := NewRequestError(
+				"validate_request",
+				c.Request.Method,
+				c.Request.URL.Path,
+				http.StatusBadRequest,
+				ErrValidationFailed,
+				"validation failed",
+			)
+
+			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
+				"error":      validationErr.Error(),
+				"request_id": requestID,
+				"errors":     errors,
+			})
+			return
+		}
+
+		// Store validated request in context
+		c.Set("ValidatedRequest", req)
+		c.Next()
+	}
+}
+
+// GetValidatedRequest retrieves the validated request from the gin context.
+// Returns the request and true if found and type matches, nil and false otherwise.
+func GetValidatedRequest[T any](c *gin.Context) (*T, bool) {
+	if req, exists := c.Get("ValidatedRequest"); exists {
+		if typed, ok := req.(*T); ok {
+			return typed, true
+		}
+	}
+	return nil, false
+}

+ 23 - 0
pkg/validation/validation.go

@@ -0,0 +1,23 @@
+package validation
+
+// ValidationError represents a single validation error
+type ValidationError struct {
+	Field   string `json:"field"`
+	Message string `json:"message"`
+}
+
+// Validator defines the interface for request validation
+type Validator interface {
+	Validate() []ValidationError
+}
+
+// Validate validates a struct that implements the Validator interface
+func Validate(v interface{}) []ValidationError {
+	if validator, ok := v.(Validator); ok {
+		return validator.Validate()
+	}
+	return []ValidationError{{
+		Field:   "request",
+		Message: "request type does not implement Validator interface",
+	}}
+}