generationtux/brizo

View on GitHub
app/handlers/jsonutil/json.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package jsonutil provides support functions for dealing with json responses
// and specificially focuses on handling errors.
package jsonutil

import (
    "encoding/json"
    "fmt"
    "net/http"
)

// ResponseError holds details and a status code related to a specific error.
type ResponseError struct {
    Details string `json:"details"`
    Status  int    `json:"status,string"`
}

// ResponseErrorBag handles the management and usage of ResponseErrors. It is
// also responsible for maintaining a high-level status code, which expected to
// be reflected in the response header.
type ResponseErrorBag interface {
    // AddError will create and add a new ResponseError into this ResponseErrorBag
    // instance. It also accepts an optional status code specific to the error, or
    // defaults to the ResponseErrorBag's high-level status code.
    AddError(details string, status ...int) *ResponseErrorBag
    Errors() []ResponseError
    Render() string
    Status() int
}

// JSONResponseError implements a JSON specific ResponseErrorBag.
type JSONResponseError struct {
    bag    []ResponseError
    Output string
    status int
}

// NewJSONResponseError creates a new instance of a JSONResponseError. While a
// status is required, error messages are optionally added in a shorthand format
// if desired.
//     NewJSONResponseError(500)                   // new empty JSONResponseError
//     NewJSONResponseError(500, "internal error") // new JSONResponseError with single error
func NewJSONResponseError(status int, messages ...string) *JSONResponseError {
    jre := JSONResponseError{
        bag:    make([]ResponseError, 0), // marshals to "[]" instead of null when empty
        status: status,
    }
    if len(messages) > 0 {
        for _, message := range messages {
            jre.AddError(message, status)
        }
    }
    return &jre
}

// Errors acts as a getter to provide a slice of all of the ResponseErrors.
func (jre *JSONResponseError) Errors() []ResponseError {
    return jre.bag
}

// Status acts as a getter to provide the high-level status.
func (jre *JSONResponseError) Status() int {
    return jre.status
}

// AddError adds a new ResponseError, while accepting an optional status code
// specific to the new ResponseError.
func (jre *JSONResponseError) AddError(details string, status ...int) *JSONResponseError {
    var derivedStatus int
    if len(status) >= 1 {
        derivedStatus = status[0]
    } else {
        derivedStatus = jre.Status()
    }
    jre.bag = append(jre.bag, ResponseError{
        Details: details,
        Status:  derivedStatus,
    })

    return jre
}

// Render marshals the JSONResponseError to a valid JSON string and sets the
// Output field to the rendered output. This method does not simply marshal the
// instance, but also formats the output to a usable format for responses.
func (jre *JSONResponseError) Render() string {
    jsonBag := struct {
        Errors []ResponseError `json:"errors"`
        Status int             `json:"status,string"`
    }{
        jre.Errors(),
        jre.Status(),
    }
    response, _ := json.Marshal(jsonBag)
    jre.Output = string(response)

    return jre.Output
}

// RespondJSONError will bootstrap and write out a valid json repsonse using the
// provided JSONResponseError. It returns the results of fmt.Printf against the
// provided http.ResponseWriter, which is expected to be the number of bytes
// written and any write error encountered.
func RespondJSONError(w http.ResponseWriter, jre *JSONResponseError) (int, error) {
    w.Header().Set("content-type", "application/json")
    w.WriteHeader(jre.Status())
    return fmt.Fprint(w, jre.Render())
}