cloudfoundry/cf-k8s-controllers

View on GitHub
api/routing/handler.go

Summary

Maintainability
A
0 mins
Test Coverage
package routing

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

    apierrors "code.cloudfoundry.org/korifi/api/errors"
    "code.cloudfoundry.org/korifi/api/presenter"

    "github.com/go-logr/logr"
)

type Response struct {
    httpStatus int
    body       interface{}
    headers    map[string][]string
}

func NewResponse(httpStatus int) *Response {
    return &Response{
        httpStatus: httpStatus,
        headers:    map[string][]string{},
    }
}

func (r *Response) WithHeader(key, value string) *Response {
    r.headers[key] = append(r.headers[key], value)
    return r
}

func (r *Response) WithBody(body interface{}) *Response {
    r.body = body
    return r
}

//counterfeiter:generate -o fake -fake-name Handler . Handler

type Handler func(r *http.Request) (*Response, error)

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    logger := logr.FromContextOrDiscard(r.Context())

    handlerResponse, err := h(r)
    if err != nil {
        logger.Info("handler returned error", "reason", err)
        PresentError(logger, w, err)
        return
    }

    if err := handlerResponse.writeTo(w); err != nil {
        _ = apierrors.LogAndReturn(logger, err, "failed to write result to the HTTP response", "handlerResponse", handlerResponse, "method", r.Method, "URL", r.URL)
    }
}

func PresentError(logger logr.Logger, w http.ResponseWriter, err error) {
    var apiError apierrors.ApiError
    if errors.As(err, &apiError) {
        writeErr := NewResponse(apiError.HttpStatus()).
            WithBody(presenter.ErrorsResponse{
                Errors: []presenter.PresentedError{
                    {
                        Detail: apiError.Detail(),
                        Title:  apiError.Title(),
                        Code:   apiError.Code(),
                    },
                },
            }).
            writeTo(w)

        if writeErr != nil {
            _ = apierrors.LogAndReturn(logger, writeErr, "failed to write error to the HTTP response")
        }

        return
    }

    PresentError(logger, w, apierrors.NewUnknownError(err))
}

func (response *Response) writeTo(w http.ResponseWriter) error {
    for header, headerValues := range response.headers {
        for _, value := range headerValues {
            w.Header().Add(header, value)
        }
    }

    if response.body == nil {
        w.WriteHeader(response.httpStatus)
        return nil
    }

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

    encoder := json.NewEncoder(w)
    encoder.SetEscapeHTML(false)

    err := encoder.Encode(response.body)
    if err != nil {
        return fmt.Errorf("failed to encode and write response: %w", err)
    }

    return nil
}