Skip to content

Commit

Permalink
Added WriteJSON
Browse files Browse the repository at this point in the history
  • Loading branch information
onattech committed Jan 12, 2023
1 parent e2ec60b commit e5ad8c6
Showing 1 changed file with 110 additions and 2 deletions.
112 changes: 110 additions & 2 deletions tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package toolkit

import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -18,8 +19,10 @@ const randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
// Tools is the type used to instantiate this module. Any variable of this type will have access
// to all the methods with the receiver *Tools
type Tools struct {
MaxFileSize int
AllowedFileTypes []string
MaxFileSize int
AllowedFileTypes []string
MaxJSONSize int
AllowUnknownFields bool
}

// RandomString returns a string of random characters of length n, using randomStringSource
Expand Down Expand Up @@ -191,3 +194,108 @@ func (t *Tools) DownloadStaticFile(w http.ResponseWriter, r *http.Request, p, fi

http.ServeFile(w, r, fp)
}

// JSONResponse is the type used for sending JSON around
type JSONResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}

// ReadJSON tries to read the body of a request and converts from json into a go data variable
func (t *Tools) ReadJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1024 * 1024 // one MB
if t.MaxJSONSize != 0 {
maxBytes = t.MaxJSONSize
}

r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))

dec := json.NewDecoder(r.Body)

if !t.AllowUnknownFields {
dec.DisallowUnknownFields()
}

err := dec.Decode(data)
if err != nil {
var syntaxError *json.SyntaxError
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)

case errors.Is(err, io.ErrUnexpectedEOF):
return errors.New("body contains badly-formed JSON")

case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON 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 unknown key %s", fieldName)

case err.Error() == "http: request body too large":
return fmt.Errorf("body must not be larger than %d bytes", maxBytes)

case errors.As(err, &invalidUnmarshalError):
return fmt.Errorf("error unmarshalling JSON: %s", err.Error())

default:
return err
}
}

err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must contain only one JSON value")
}

return nil
}

// WriteJSON take a response status code and arbitrary data and writes json to the client
func (t *Tools) WriteJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
out, err := json.Marshal(data)
if err != nil {
return err
}

if len(headers) > 0 {
for key, value := range headers[0] {
w.Header()[key] = value
}
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)

_, err = w.Write(out)
if err != nil {
return err
}
return nil
}

// ErrorJSON takes an error, and optionally a status code, and generates and sends a JSON error message
func (t *Tools) ErrorJSON(w http.ResponseWriter, err error, status ...int) error {
statusCode := http.StatusBadRequest

if len(status) > 0 {
statusCode = status[0]
}

var payload JSONResponse
payload.Error = true
payload.Message = err.Error()

return t.WriteJSON(w, statusCode, payload)
}

0 comments on commit e5ad8c6

Please sign in to comment.