bnkamalesh/webgo

View on GitHub
responses.go

Summary

Maintainability
A
0 mins
Test Coverage
package webgo

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

var (
    jsonErrPayload = []byte{}
)

// ErrorData used to render the error page
type ErrorData struct {
    ErrCode        int
    ErrDescription string
}

// dOutput is the standard/valid output wrapped in `{data: <payload>, status: <http response status>}`
type dOutput struct {
    Data   interface{} `json:"data"`
    Status int         `json:"status"`
}

// errOutput is the error output wrapped in `{errors:<errors>, status: <http response status>}`
type errOutput struct {
    Errors interface{} `json:"errors"`
    Status int         `json:"status"`
}

const (
    // HeaderContentType is the key for mentioning the response header content type
    HeaderContentType = "Content-Type"
    // JSONContentType is the MIME type when the response is JSON
    JSONContentType = "application/json"
    // HTMLContentType is the MIME type when the response is HTML
    HTMLContentType = "text/html; charset=UTF-8"

    // ErrInternalServer to send when there's an internal server error
    ErrInternalServer = "Internal server error"
)

// SendHeader is used to send only a response header, i.e no response body
func SendHeader(w http.ResponseWriter, rCode int) {
    w.WriteHeader(rCode)
}

func crwAsserter(w http.ResponseWriter, rCode int) http.ResponseWriter {
    if crw, ok := w.(*customResponseWriter); ok {
        crw.statusCode = rCode
        return crw
    }

    return newCRW(w, rCode)
}

// Send sends a completely custom response without wrapping in the
// `{data: <data>, status: <int>` struct
func Send(w http.ResponseWriter, contentType string, data interface{}, rCode int) {
    w = crwAsserter(w, rCode)
    w.Header().Set(HeaderContentType, contentType)
    _, err := fmt.Fprint(w, data)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        _, _ = w.Write([]byte(ErrInternalServer))
        LOGHANDLER.Error(err)
    }
}

// SendResponse is used to respond to any request (JSON response) based on the code, data etc.
func SendResponse(w http.ResponseWriter, data interface{}, rCode int) {
    w = crwAsserter(w, rCode)
    w.Header().Add(HeaderContentType, JSONContentType)
    err := json.NewEncoder(w).Encode(dOutput{Data: data, Status: rCode})
    if err == nil {
        return
    }

    // assuming the error was related to JSON encoding, so reattempting to respond
    // with a static payload. This could still fail in case of network write or other error(s)
    w = crwAsserter(w, http.StatusInternalServerError)
    _, _ = w.Write(jsonErrPayload)
    LOGHANDLER.Error(err)
}

// SendError is used to respond to any request with an error
func SendError(w http.ResponseWriter, data interface{}, rCode int) {
    w = crwAsserter(w, rCode)
    w.Header().Add(HeaderContentType, JSONContentType)
    err := json.NewEncoder(w).Encode(errOutput{data, rCode})
    if err == nil {
        return
    }

    // assuming the error was related to JSON encoding, so reattempting to respond
    // with a static payload. This could still fail in case of network write or other error(s)
    w = crwAsserter(w, http.StatusInternalServerError)
    _, _ = w.Write(jsonErrPayload)
    LOGHANDLER.Error(err)
}

// Render is used for rendering templates (HTML)
func Render(w http.ResponseWriter, data interface{}, rCode int, tpl *template.Template) {
    w = crwAsserter(w, rCode)

    // In case of HTML response, setting appropriate header type for text/HTML response
    w.Header().Set(HeaderContentType, HTMLContentType)

    // Rendering an HTML template with appropriate data
    err := tpl.Execute(w, data)
    if err != nil {
        Send(w, "text/plain", ErrInternalServer, http.StatusInternalServerError)
        LOGHANDLER.Error(err.Error())
    }
}

// R200 - Successful/OK response
func R200(w http.ResponseWriter, data interface{}) {
    SendResponse(w, data, http.StatusOK)
}

// R201 - New item created
func R201(w http.ResponseWriter, data interface{}) {
    SendResponse(w, data, http.StatusCreated)
}

// R204 - empty, no content
func R204(w http.ResponseWriter) {
    SendHeader(w, http.StatusNoContent)
}

// R302 - Temporary redirect
func R302(w http.ResponseWriter, data interface{}) {
    SendResponse(w, data, http.StatusFound)
}

// R400 - Invalid request, any incorrect/erraneous value in the request body
func R400(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusBadRequest)
}

// R403 - Unauthorized access
func R403(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusForbidden)
}

// R404 - Resource not found
func R404(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusNotFound)
}

// R406 - Unacceptable header. For any error related to values set in header
func R406(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusNotAcceptable)
}

// R451 - Resource taken down because of a legal request
func R451(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusUnavailableForLegalReasons)
}

// R500 - Internal server error
func R500(w http.ResponseWriter, data interface{}) {
    SendError(w, data, http.StatusInternalServerError)
}