api
2015-07-18
Child:57c9412e8000
api/api.go
First commit. Define common middleware, helpers, etc. that will be used across projects.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/api.go Sat Jul 18 03:39:27 2015 -0400 1.3 @@ -0,0 +1,137 @@ 1.4 +package api 1.5 + 1.6 +import ( 1.7 + "encoding/json" 1.8 + "errors" 1.9 + "log" 1.10 + "net/http" 1.11 + "strings" 1.12 + 1.13 + "bitbucket.org/ww/goautoneg" 1.14 + 1.15 + "code.secondbit.org/uuid.hg" 1.16 + 1.17 + "golang.org/x/net/context" 1.18 +) 1.19 + 1.20 +const ( 1.21 + RequestErrAccessDenied = "access_denied" 1.22 + RequestErrInsufficient = "insufficient" 1.23 + RequestErrOverflow = "overflow" 1.24 + RequestErrInvalidValue = "invalid_value" 1.25 + RequestErrInvalidFormat = "invalid_format" 1.26 + RequestErrMissing = "missing" 1.27 + RequestErrNotFound = "not_found" 1.28 + RequestErrConflict = "conflict" 1.29 + RequestErrActOfGod = "act_of_god" 1.30 +) 1.31 + 1.32 +var ( 1.33 + ActOfGodError = []RequestError{{Slug: RequestErrActOfGod}} 1.34 + InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}} 1.35 + 1.36 + Encoders = []string{"application/json"} 1.37 + 1.38 + ErrUserIDNotSet = errors.New("user ID not set") 1.39 +) 1.40 + 1.41 +type RequestError struct { 1.42 + Slug string `json:"error,omitempty"` 1.43 + Field string `json:"field,omitempty"` 1.44 + Param string `json:"param,omitempty"` 1.45 + Header string `json:"header,omitempty"` 1.46 +} 1.47 + 1.48 +type ContextHandler func(http.ResponseWriter, *http.Request, context.Context) 1.49 + 1.50 +func NegotiateMiddleware(h http.Handler) http.Handler { 1.51 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1.52 + if r.Header.Get("Accept") != "" { 1.53 + contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) 1.54 + if contentType == "" { 1.55 + w.WriteHeader(http.StatusNotAcceptable) 1.56 + w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept"))) 1.57 + return 1.58 + } 1.59 + } 1.60 + h.ServeHTTP(w, r) 1.61 + }) 1.62 +} 1.63 + 1.64 +func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) { 1.65 + contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) 1.66 + w.Header().Set("content-type", contentType) 1.67 + w.WriteHeader(status) 1.68 + var err error 1.69 + switch contentType { 1.70 + case "application/json": 1.71 + enc := json.NewEncoder(w) 1.72 + err = enc.Encode(resp) 1.73 + default: 1.74 + enc := json.NewEncoder(w) 1.75 + err = enc.Encode(resp) 1.76 + } 1.77 + if err != nil { 1.78 + log.Println(err) 1.79 + } 1.80 +} 1.81 + 1.82 +func Decode(r *http.Request, target interface{}) error { 1.83 + defer r.Body.Close() 1.84 + switch r.Header.Get("Content-Type") { 1.85 + case "application/json": 1.86 + dec := json.NewDecoder(r.Body) 1.87 + return dec.Decode(target) 1.88 + default: 1.89 + dec := json.NewDecoder(r.Body) 1.90 + return dec.Decode(target) 1.91 + } 1.92 +} 1.93 + 1.94 +func CORSMiddleware(h http.Handler) http.Handler { 1.95 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1.96 + w.Header().Set("Access-Control-Allow-Origin", "*") 1.97 + w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) 1.98 + w.Header().Set("Access-Control-Allow-Credentials", "true") 1.99 + if strings.ToLower(r.Method) == "options" { 1.100 + methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ") 1.101 + w.Header().Set("Access-Control-Allow-Methods", methods) 1.102 + w.Header().Set("Allow", methods) 1.103 + w.WriteHeader(http.StatusOK) 1.104 + return 1.105 + } 1.106 + h.ServeHTTP(w, r) 1.107 + }) 1.108 +} 1.109 + 1.110 +func ContextWrapper(c context.Context, handler ContextHandler) http.Handler { 1.111 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1.112 + handler(w, r, c) 1.113 + }) 1.114 +} 1.115 + 1.116 +func CheckScopes(r *http.Request, scopes ...string) bool { 1.117 + passedStr := r.Header.Get("scopes") 1.118 + passed := strings.Split(passedStr, " ") 1.119 + for _, scope := range scopes { 1.120 + var found bool 1.121 + for _, p := range passed { 1.122 + if scope == strings.TrimSpace(p) { 1.123 + found = true 1.124 + break 1.125 + } 1.126 + } 1.127 + if !found { 1.128 + return false 1.129 + } 1.130 + } 1.131 + return true 1.132 +} 1.133 + 1.134 +func AuthUser(r *http.Request) (uuid.ID, error) { 1.135 + rawID := r.Header.Get("User-ID") 1.136 + if rawID == "" { 1.137 + return nil, ErrUserIDNotSet 1.138 + } 1.139 + return uuid.Parse(rawID) 1.140 +}