adds validation to the json
This commit is contained in:
parent
13535506f3
commit
ba24d8f7a2
|
@ -7,7 +7,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
@ -15,56 +14,51 @@ import (
|
||||||
type envelope map[string]any
|
type envelope map[string]any
|
||||||
|
|
||||||
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dest any) error {
|
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dest any) error {
|
||||||
|
|
||||||
maxBytes := 1_048_576
|
maxBytes := 1_048_576
|
||||||
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
|
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
dec := json.NewDecoder(r.Body)
|
||||||
decoder.DisallowUnknownFields()
|
dec.DisallowUnknownFields()
|
||||||
|
|
||||||
err := decoder.Decode(dest)
|
|
||||||
|
|
||||||
|
err := dec.Decode(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var syntaxError *json.SyntaxError
|
var syntaxError *json.SyntaxError
|
||||||
var unmarshallError *json.UnmarshalTypeError
|
var unmarshalTypeError *json.UnmarshalTypeError
|
||||||
var invalidUnmarshall *json.InvalidUnmarshalError
|
var invalidUnmarshalError *json.InvalidUnmarshalError
|
||||||
var maxBytesError *http.MaxBytesError
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &syntaxError):
|
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):
|
case errors.Is(err, io.ErrUnexpectedEOF):
|
||||||
return errors.New("body contains badly-formed JSON")
|
return errors.New("body contains badly-formed JSON")
|
||||||
|
|
||||||
case errors.As(err, &unmarshallError):
|
case errors.As(err, &unmarshalTypeError):
|
||||||
if unmarshallError.Field != "" {
|
if unmarshalTypeError.Field != "" {
|
||||||
return fmt.Errorf("the body contains incorrect json type for field %q", unmarshallError.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):
|
case errors.Is(err, io.EOF):
|
||||||
return errors.New("body must not be empty")
|
return errors.New("body must not be empty")
|
||||||
|
|
||||||
case strings.HasPrefix(err.Error(), "json: unknown field "):
|
case errors.As(err, &invalidUnmarshalError):
|
||||||
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):
|
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = decoder.Decode(&struct{}{})
|
|
||||||
|
err = dec.Decode(&struct{}{})
|
||||||
if err != io.EOF {
|
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) {
|
func (app *application) readIDParam(r *http.Request) (int64, error) {
|
||||||
|
|
|
@ -10,19 +10,19 @@ import (
|
||||||
|
|
||||||
func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) {
|
func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var input struct {
|
var input struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Year int32 `json:"year"`
|
Year int32 `json:"year"`
|
||||||
Runtime int32 `json:"runtime"`
|
Runtime data.Runtime `json:"runtime"`
|
||||||
Genres []string `json:"genres"`
|
Genres []string `json:"genres"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.readJSON(w, r, &input)
|
err := app.readJSON(w, r, &input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.badRequestResponse(w, r, err)
|
app.errorResponse(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w, "%v+\n", input)
|
fmt.Fprintf(w, "%+v\n", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) {
|
func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,5 +1,5 @@
|
||||||
module greenlight.ergz
|
module greenlight.ergz
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require github.com/julienschmidt/httprouter v1.3.0 // indirect
|
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
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrInvalidRuntimeFormat = errors.New("invalid runtime format, should be something like '102 mins'")
|
||||||
|
|
||||||
type Runtime int32
|
type Runtime int32
|
||||||
|
|
||||||
func (r Runtime) MarshalJSON() ([]byte, error) {
|
func (r Runtime) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -14,3 +18,25 @@ func (r Runtime) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(quotedJSONValue), nil
|
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