adds validation to the json

This commit is contained in:
Emanuel Rodriguez 2023-08-18 13:33:56 -07:00
parent 13535506f3
commit ba24d8f7a2
6 changed files with 1409 additions and 31 deletions

View File

@ -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) {

View File

@ -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
View File

@ -1,5 +1,5 @@
module greenlight.ergz
go 1.20
go 1.21
require github.com/julienschmidt/httprouter v1.3.0 // indirect

1303
greenlight.sublime-workspace Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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)
}