github.com/w3security/vervet/v5@v5.3.1-0.20230618081846-5bd9b5d799dc/versionware/handler.go (about)

     1  // Package versionware provides routing and middleware for building versioned
     2  // HTTP services.
     3  package versionware
     4  
     5  import (
     6  	"fmt"
     7  	"net/http"
     8  
     9  	"github.com/w3security/vervet/v5"
    10  )
    11  
    12  const (
    13  	// HeaderW3SecurityVersionRequested is a response header acknowledging the API
    14  	// version that was requested.
    15  	HeaderW3SecurityVersionRequested = "w3security-version-requested"
    16  
    17  	// HeaderW3SecurityVersionServed is a response header indicating the actual API
    18  	// version that was matched and served the response.
    19  	HeaderW3SecurityVersionServed = "w3security-version-served"
    20  )
    21  
    22  // Handler is a multiplexing http.Handler that dispatches requests based on the
    23  // version query parameter according to vervet's API version matching rules.
    24  type Handler struct {
    25  	handlers map[vervet.Version]http.Handler
    26  	index    vervet.VersionIndex
    27  	errFunc  VersionErrorHandler
    28  }
    29  
    30  // VersionErrorHandler defines a function which handles versioning error
    31  // responses in requests.
    32  type VersionErrorHandler func(w http.ResponseWriter, r *http.Request, status int, err error)
    33  
    34  // VersionHandler expresses a pairing of Version and http.Handler.
    35  type VersionHandler struct {
    36  	Version vervet.Version
    37  	Handler http.Handler
    38  }
    39  
    40  // NewHandler returns a new Handler instance, which handles versioned requests
    41  // with the matching version handler.
    42  func NewHandler(vhs ...VersionHandler) *Handler {
    43  	h := &Handler{
    44  		handlers: map[vervet.Version]http.Handler{},
    45  		errFunc:  DefaultVersionError,
    46  	}
    47  	versions := make([]vervet.Version, len(vhs))
    48  	for i := range vhs {
    49  		v := vhs[i].Version
    50  		versions[i] = v
    51  		h.handlers[v] = vhs[i].Handler
    52  	}
    53  	h.index = vervet.NewVersionIndex(versions)
    54  	return h
    55  }
    56  
    57  // DefaultVersionError provides a basic implementation of VersionErrorHandler
    58  // that uses http.Error.
    59  func DefaultVersionError(w http.ResponseWriter, r *http.Request, status int, err error) {
    60  	http.Error(w, http.StatusText(status), status)
    61  }
    62  
    63  // HandleErrors changes the default error handler to the provided function. It
    64  // may be used to control the format of versioning error responses.
    65  func (h *Handler) HandleErrors(errFunc VersionErrorHandler) {
    66  	h.errFunc = errFunc
    67  }
    68  
    69  // Resolve returns the resolved version and its associated http.Handler for the
    70  // requested version.
    71  func (h *Handler) Resolve(requested vervet.Version) (*vervet.Version, http.Handler, error) {
    72  	resolvedVersion, err := h.index.Resolve(requested)
    73  	if err != nil {
    74  		return nil, nil, err
    75  	}
    76  	return &resolvedVersion, h.handlers[resolvedVersion], nil
    77  }
    78  
    79  // ServeHTTP implements http.Handler with the handler matching the version
    80  // query parameter on the request. If no matching version is found, responds
    81  // 404.
    82  func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    83  	versionParam := req.URL.Query().Get("version")
    84  	if versionParam == "" {
    85  		h.errFunc(w, req, http.StatusBadRequest, fmt.Errorf("missing required query parameter 'version'"))
    86  		return
    87  	}
    88  	requested, err := vervet.ParseVersion(versionParam)
    89  	if err != nil {
    90  		h.errFunc(w, req, http.StatusBadRequest, err)
    91  		return
    92  	}
    93  	resolved, handler, err := h.Resolve(requested)
    94  	if err != nil {
    95  		h.errFunc(w, req, http.StatusNotFound, err)
    96  		return
    97  	}
    98  	w.Header().Set(HeaderW3SecurityVersionRequested, requested.String())
    99  	w.Header().Set(HeaderW3SecurityVersionServed, resolved.String())
   100  	handler.ServeHTTP(w, req)
   101  }