github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/apiv3/route/handler.go (about)

     1  package route
     2  
     3  import (
     4  	"net/http"
     5  
     6  	"github.com/evergreen-ci/evergreen/apiv3"
     7  	"github.com/evergreen-ci/evergreen/apiv3/model"
     8  	"github.com/evergreen-ci/evergreen/apiv3/servicecontext"
     9  	"github.com/evergreen-ci/evergreen/util"
    10  	"github.com/mongodb/grip"
    11  )
    12  
    13  // MethodHandler contains all of the methods necessary for completely processing
    14  // an API request. It contains an Authenticator to control access to the method
    15  // and a RequestHandler to perform the required work for the request.
    16  type MethodHandler struct {
    17  	// PrefetchFunctions is a list of functions to be run before the main request
    18  	// is executed.
    19  	PrefetchFunctions []PrefetchFunc
    20  	// MethodType is the HTTP Method Type that this handler will handler.
    21  	// POST, PUT, DELETE, etc.
    22  	MethodType string
    23  
    24  	Authenticator
    25  	RequestHandler
    26  }
    27  
    28  // ResponseData holds the information that the handler function will need to form
    29  // its encoded response. A ResponseData is generated by a RequestHandler's Execute
    30  // function and parsed in the main handler method.
    31  type ResponseData struct {
    32  	// Result is the resulting API models that the API request needs to return
    33  	// to the user, either because they were queried for or because they were
    34  	// created by this request.
    35  	Result []model.Model
    36  
    37  	// Metadata is an interface that holds any additional data that the handler
    38  	// will need for encoding the API response.
    39  	Metadata interface{}
    40  }
    41  
    42  // RequestHandler is an interface that defines how to process an HTTP request
    43  // against an API resource.
    44  type RequestHandler interface {
    45  	// Handler defines how to fetch a new version of this handler.
    46  	Handler() RequestHandler
    47  
    48  	// ParseAndValidate defines how to retrieve the needed parameters from the HTTP
    49  	// request. All needed data should be retrieved during the parse function since
    50  	// other functions do not have access to the HTTP request.
    51  	ParseAndValidate(*http.Request) error
    52  
    53  	// Execute performs the necessary work on the evergreen backend and returns
    54  	// an API model to be surfaced to the user.
    55  	Execute(servicecontext.ServiceContext) (ResponseData, error)
    56  }
    57  
    58  // makeHandler makes an http.HandlerFunc that wraps calls to each of the api
    59  // Method functions. It marshalls the response to JSON and writes it out to
    60  // as the response. If any of the functions return an error, it handles creating
    61  // a JSON error and sending it as the response.
    62  func makeHandler(methodHandler MethodHandler, sc servicecontext.ServiceContext) http.HandlerFunc {
    63  	return func(w http.ResponseWriter, r *http.Request) {
    64  		for _, pf := range methodHandler.PrefetchFunctions {
    65  			if err := pf(r, sc); err != nil {
    66  				handleAPIError(err, w, r)
    67  				return
    68  			}
    69  		}
    70  
    71  		if err := methodHandler.Authenticate(sc, r); err != nil {
    72  			handleAPIError(err, w, r)
    73  			return
    74  		}
    75  		reqHandler := methodHandler.RequestHandler.Handler()
    76  
    77  		if err := reqHandler.ParseAndValidate(r); err != nil {
    78  			handleAPIError(err, w, r)
    79  			return
    80  		}
    81  		result, err := reqHandler.Execute(sc)
    82  		if err != nil {
    83  			handleAPIError(err, w, r)
    84  			return
    85  		}
    86  
    87  		// Check the type of the results metadata. If it is a PaginationMetadata,
    88  		// create the pagination headers. Otherwise, no additional processing is needed.
    89  		// NOTE: This could expand to include additional metadata types that define
    90  		// other specific cases for how to handle results.
    91  		switch m := result.Metadata.(type) {
    92  		case *PaginationMetadata:
    93  			err := m.MakeHeader(w, sc.GetURL(), r.URL.Path)
    94  			if err != nil {
    95  				handleAPIError(err, w, r)
    96  				return
    97  			}
    98  			util.WriteJSON(&w, result.Result, http.StatusOK)
    99  		default:
   100  			if len(result.Result) < 1 {
   101  				http.Error(w, "{}", http.StatusInternalServerError)
   102  				return
   103  			}
   104  			util.WriteJSON(&w, result.Result[0], http.StatusOK)
   105  		}
   106  	}
   107  }
   108  
   109  // handleAPIError handles writing the given error to the response writer.
   110  // It checks if the given error is an APIError and turns it into JSON to be
   111  // written back to the requester. If the error is unknown, it must have come
   112  // from a service layer package, in which case it is an internal server error
   113  // and is returned as such.
   114  func handleAPIError(e error, w http.ResponseWriter, r *http.Request) {
   115  
   116  	apiErr := apiv3.APIError{}
   117  
   118  	apiErr.StatusCode = http.StatusInternalServerError
   119  	apiErr.Message = e.Error()
   120  
   121  	if castError, ok := e.(apiv3.APIError); ok {
   122  		apiErr = castError
   123  		grip.Warningln("User error", r.Method, r.URL, e)
   124  	} else {
   125  		grip.Errorf("Service error %s %s %+v", r.Method, r.URL, e)
   126  	}
   127  
   128  	util.WriteJSON(&w, apiErr, apiErr.StatusCode)
   129  }