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 }