github.com/snyk/vervet/v5@v5.11.1-0.20240202085829-ad4dd7fb6101/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/snyk/vervet/v5"
    10  )
    11  
    12  const (
    13  	// HeaderSnykVersionRequested is a response header acknowledging the API
    14  	// version that was requested.
    15  	HeaderSnykVersionRequested = "snyk-version-requested"
    16  
    17  	// HeaderSnykVersionServed is a response header indicating the actual API
    18  	// version that was matched and served the response.
    19  	HeaderSnykVersionServed = "snyk-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(HeaderSnykVersionRequested, requested.String())
    99  	w.Header().Set(HeaderSnykVersionServed, resolved.String())
   100  	handler.ServeHTTP(w, req)
   101  }