api

Paddy 2015-07-18 Child:57c9412e8000

0:6cf0c15d53e4 Go to Latest

api/api.go

First commit. Define common middleware, helpers, etc. that will be used across projects.

History
paddy@0 1 package api
paddy@0 2
paddy@0 3 import (
paddy@0 4 "encoding/json"
paddy@0 5 "errors"
paddy@0 6 "log"
paddy@0 7 "net/http"
paddy@0 8 "strings"
paddy@0 9
paddy@0 10 "bitbucket.org/ww/goautoneg"
paddy@0 11
paddy@0 12 "code.secondbit.org/uuid.hg"
paddy@0 13
paddy@0 14 "golang.org/x/net/context"
paddy@0 15 )
paddy@0 16
paddy@0 17 const (
paddy@0 18 RequestErrAccessDenied = "access_denied"
paddy@0 19 RequestErrInsufficient = "insufficient"
paddy@0 20 RequestErrOverflow = "overflow"
paddy@0 21 RequestErrInvalidValue = "invalid_value"
paddy@0 22 RequestErrInvalidFormat = "invalid_format"
paddy@0 23 RequestErrMissing = "missing"
paddy@0 24 RequestErrNotFound = "not_found"
paddy@0 25 RequestErrConflict = "conflict"
paddy@0 26 RequestErrActOfGod = "act_of_god"
paddy@0 27 )
paddy@0 28
paddy@0 29 var (
paddy@0 30 ActOfGodError = []RequestError{{Slug: RequestErrActOfGod}}
paddy@0 31 InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}}
paddy@0 32
paddy@0 33 Encoders = []string{"application/json"}
paddy@0 34
paddy@0 35 ErrUserIDNotSet = errors.New("user ID not set")
paddy@0 36 )
paddy@0 37
paddy@0 38 type RequestError struct {
paddy@0 39 Slug string `json:"error,omitempty"`
paddy@0 40 Field string `json:"field,omitempty"`
paddy@0 41 Param string `json:"param,omitempty"`
paddy@0 42 Header string `json:"header,omitempty"`
paddy@0 43 }
paddy@0 44
paddy@0 45 type ContextHandler func(http.ResponseWriter, *http.Request, context.Context)
paddy@0 46
paddy@0 47 func NegotiateMiddleware(h http.Handler) http.Handler {
paddy@0 48 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
paddy@0 49 if r.Header.Get("Accept") != "" {
paddy@0 50 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
paddy@0 51 if contentType == "" {
paddy@0 52 w.WriteHeader(http.StatusNotAcceptable)
paddy@0 53 w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept")))
paddy@0 54 return
paddy@0 55 }
paddy@0 56 }
paddy@0 57 h.ServeHTTP(w, r)
paddy@0 58 })
paddy@0 59 }
paddy@0 60
paddy@0 61 func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) {
paddy@0 62 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
paddy@0 63 w.Header().Set("content-type", contentType)
paddy@0 64 w.WriteHeader(status)
paddy@0 65 var err error
paddy@0 66 switch contentType {
paddy@0 67 case "application/json":
paddy@0 68 enc := json.NewEncoder(w)
paddy@0 69 err = enc.Encode(resp)
paddy@0 70 default:
paddy@0 71 enc := json.NewEncoder(w)
paddy@0 72 err = enc.Encode(resp)
paddy@0 73 }
paddy@0 74 if err != nil {
paddy@0 75 log.Println(err)
paddy@0 76 }
paddy@0 77 }
paddy@0 78
paddy@0 79 func Decode(r *http.Request, target interface{}) error {
paddy@0 80 defer r.Body.Close()
paddy@0 81 switch r.Header.Get("Content-Type") {
paddy@0 82 case "application/json":
paddy@0 83 dec := json.NewDecoder(r.Body)
paddy@0 84 return dec.Decode(target)
paddy@0 85 default:
paddy@0 86 dec := json.NewDecoder(r.Body)
paddy@0 87 return dec.Decode(target)
paddy@0 88 }
paddy@0 89 }
paddy@0 90
paddy@0 91 func CORSMiddleware(h http.Handler) http.Handler {
paddy@0 92 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
paddy@0 93 w.Header().Set("Access-Control-Allow-Origin", "*")
paddy@0 94 w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
paddy@0 95 w.Header().Set("Access-Control-Allow-Credentials", "true")
paddy@0 96 if strings.ToLower(r.Method) == "options" {
paddy@0 97 methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ")
paddy@0 98 w.Header().Set("Access-Control-Allow-Methods", methods)
paddy@0 99 w.Header().Set("Allow", methods)
paddy@0 100 w.WriteHeader(http.StatusOK)
paddy@0 101 return
paddy@0 102 }
paddy@0 103 h.ServeHTTP(w, r)
paddy@0 104 })
paddy@0 105 }
paddy@0 106
paddy@0 107 func ContextWrapper(c context.Context, handler ContextHandler) http.Handler {
paddy@0 108 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
paddy@0 109 handler(w, r, c)
paddy@0 110 })
paddy@0 111 }
paddy@0 112
paddy@0 113 func CheckScopes(r *http.Request, scopes ...string) bool {
paddy@0 114 passedStr := r.Header.Get("scopes")
paddy@0 115 passed := strings.Split(passedStr, " ")
paddy@0 116 for _, scope := range scopes {
paddy@0 117 var found bool
paddy@0 118 for _, p := range passed {
paddy@0 119 if scope == strings.TrimSpace(p) {
paddy@0 120 found = true
paddy@0 121 break
paddy@0 122 }
paddy@0 123 }
paddy@0 124 if !found {
paddy@0 125 return false
paddy@0 126 }
paddy@0 127 }
paddy@0 128 return true
paddy@0 129 }
paddy@0 130
paddy@0 131 func AuthUser(r *http.Request) (uuid.ID, error) {
paddy@0 132 rawID := r.Header.Get("User-ID")
paddy@0 133 if rawID == "" {
paddy@0 134 return nil, ErrUserIDNotSet
paddy@0 135 }
paddy@0 136 return uuid.Parse(rawID)
paddy@0 137 }