adds validation to the json
This commit is contained in:
parent
13535506f3
commit
ba24d8f7a2
|
@ -7,7 +7,6 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
@ -15,56 +14,51 @@ import (
|
|||
type envelope map[string]any
|
||||
|
||||
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dest any) error {
|
||||
|
||||
maxBytes := 1_048_576
|
||||
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
err := decoder.Decode(dest)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
|
||||
err := dec.Decode(dest)
|
||||
if err != nil {
|
||||
var syntaxError *json.SyntaxError
|
||||
var unmarshallError *json.UnmarshalTypeError
|
||||
var invalidUnmarshall *json.InvalidUnmarshalError
|
||||
var maxBytesError *http.MaxBytesError
|
||||
var unmarshalTypeError *json.UnmarshalTypeError
|
||||
var invalidUnmarshalError *json.InvalidUnmarshalError
|
||||
|
||||
switch {
|
||||
case errors.As(err, &syntaxError):
|
||||
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
|
||||
return fmt.Errorf("body contains badly-formatted JSON (at character %d)", syntaxError.Offset)
|
||||
|
||||
case errors.Is(err, io.ErrUnexpectedEOF):
|
||||
return errors.New("body contains badly-formed JSON")
|
||||
|
||||
case errors.As(err, &unmarshallError):
|
||||
if unmarshallError.Field != "" {
|
||||
return fmt.Errorf("the body contains incorrect json type for field %q", unmarshallError.Field)
|
||||
case errors.As(err, &unmarshalTypeError):
|
||||
if unmarshalTypeError.Field != "" {
|
||||
return fmt.Errorf("body contains incorrect JOSN type for field %q", unmarshalTypeError.Field)
|
||||
}
|
||||
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshallError.Offset)
|
||||
|
||||
return fmt.Errorf("body contains incorrect JOSN type (at character %d)", unmarshalTypeError.Offset)
|
||||
|
||||
case errors.Is(err, io.EOF):
|
||||
return errors.New("body must not be empty")
|
||||
|
||||
case strings.HasPrefix(err.Error(), "json: unknown field "):
|
||||
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
|
||||
return fmt.Errorf("body contains uknown key %s", fieldName)
|
||||
|
||||
case errors.As(err, &maxBytesError):
|
||||
return fmt.Errorf("body must not be larger than %d bytes", maxBytesError.Limit)
|
||||
|
||||
case errors.As(err, &invalidUnmarshall):
|
||||
case errors.As(err, &invalidUnmarshalError):
|
||||
panic(err)
|
||||
|
||||
default:
|
||||
return err
|
||||
|
||||
}
|
||||
}
|
||||
err = decoder.Decode(&struct{}{})
|
||||
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
return errors.New("body must only contain a single JSON value")
|
||||
return errors.New("body must only contain a single json value")
|
||||
}
|
||||
|
||||
return nil // no errors
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *application) readIDParam(r *http.Request) (int64, error) {
|
||||
|
|
|
@ -10,19 +10,19 @@ import (
|
|||
|
||||
func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var input struct {
|
||||
Title string `json:"title"`
|
||||
Year int32 `json:"year"`
|
||||
Runtime int32 `json:"runtime"`
|
||||
Genres []string `json:"genres"`
|
||||
Title string `json:"title"`
|
||||
Year int32 `json:"year"`
|
||||
Runtime data.Runtime `json:"runtime"`
|
||||
Genres []string `json:"genres"`
|
||||
}
|
||||
|
||||
err := app.readJSON(w, r, &input)
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
app.errorResponse(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%v+\n", input)
|
||||
fmt.Fprintf(w, "%+v\n", input)
|
||||
}
|
||||
|
||||
func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,5 +1,5 @@
|
|||
module greenlight.ergz
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,14 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrInvalidRuntimeFormat = errors.New("invalid runtime format, should be something like '102 mins'")
|
||||
|
||||
type Runtime int32
|
||||
|
||||
func (r Runtime) MarshalJSON() ([]byte, error) {
|
||||
|
@ -14,3 +18,25 @@ func (r Runtime) MarshalJSON() ([]byte, error) {
|
|||
return []byte(quotedJSONValue), nil
|
||||
|
||||
}
|
||||
|
||||
func (r *Runtime) UnmarshalJSON(jsonValue []byte) error {
|
||||
unquoJSONValue, err := strconv.Unquote(string(jsonValue))
|
||||
if err != nil {
|
||||
return ErrInvalidRuntimeFormat
|
||||
}
|
||||
|
||||
jsonParts := strings.Split(unquoJSONValue, " ")
|
||||
|
||||
if len(jsonParts) != 2 || (jsonParts[1] != "mins" && jsonParts[1] != "minutes") {
|
||||
return ErrInvalidRuntimeFormat
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(jsonParts[0], 10, 32)
|
||||
if err != nil {
|
||||
return ErrInvalidRuntimeFormat
|
||||
}
|
||||
|
||||
*r = Runtime(i)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package validator
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
EmailRX = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}")
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
Errors map[string]string
|
||||
}
|
||||
|
||||
func New() *Validator {
|
||||
return &Validator{Errors: make(map[string]string)}
|
||||
}
|
||||
|
||||
func (v *Validator) Valid() bool {
|
||||
return len(v.Errors) == 0
|
||||
}
|
||||
|
||||
func (v *Validator) AddError(key, message string) {
|
||||
if _, exists := v.Errors[key]; !exists {
|
||||
v.Errors[key] = message
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) Check(ok bool, key string, message string) {
|
||||
if !ok {
|
||||
v.AddError(key, message)
|
||||
}
|
||||
}
|
||||
|
||||
func PermittedValue[T comparable](value T, permittedValues ...T) bool {
|
||||
for i := range permittedValues {
|
||||
if value == permittedValues[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func Matches(value string, rx *regexp.Regexp) bool {
|
||||
return rx.MatchString(value)
|
||||
}
|
||||
|
||||
func Unique[T comparable](values []T) bool {
|
||||
uniqueValues := make(map[T]bool)
|
||||
|
||||
for _, value := range values {
|
||||
uniqueValues[value] = true
|
||||
}
|
||||
|
||||
return len(values) == len(uniqueValues)
|
||||
}
|
Loading…
Reference in New Issue