auth
2015-07-18
Parent:8ecb60d29b0d
auth/request.go
Update client to detect errors. The client doesn't treat non-200 responses as errors automatically, so we need to detect when the response.Errors property is set, and use that to return an error. To avoid the boilerplate and an extensive error system, I just wrapped them in an httpErrors type that implements the error interface. That way the errors can be returned, and callers can type-cast and interrogate them. I also updated the GetLogin function to return an auth.ErrLoginNotFound error when the httpErrors response indicates that's the reason the request failed.
1 package auth
3 import (
4 "encoding/json"
5 "log"
6 "net/http"
8 "bitbucket.org/ww/goautoneg"
9 )
11 const (
12 RequestErrAccessDenied = "access_denied"
13 RequestErrInsufficient = "insufficient"
14 RequestErrOverflow = "overflow"
15 RequestErrInvalidValue = "invalid_value"
16 RequestErrInvalidFormat = "invalid_format"
17 RequestErrMissing = "missing"
18 RequestErrNotFound = "not_found"
19 RequestErrConflict = "conflict"
20 RequestErrActOfGod = "act_of_god"
21 )
23 var (
24 actOfGodResponse = Response{Errors: []RequestError{RequestError{Slug: RequestErrActOfGod}}}
25 invalidFormatResponse = Response{Errors: []RequestError{RequestError{Slug: RequestErrInvalidFormat, Field: "/"}}}
27 encoders = []string{"application/json"}
28 )
30 type Response struct {
31 Errors []RequestError `json:"errors,omitempty"`
32 Logins []Login `json:"logins,omitempty"`
33 Profiles []Profile `json:"profiles,omitempty"`
34 Clients []Client `json:"clients,omitempty"`
35 Endpoints []Endpoint `json:"endpoints,omitempty"`
36 Sessions []Session `json:"sessions,omitempty"`
37 }
39 type RequestError struct {
40 Slug string `json:"error,omitempty"`
41 Field string `json:"field,omitempty"`
42 Param string `json:"param,omitempty"`
43 Header string `json:"header,omitempty"`
44 }
46 func negotiate(h http.Handler) http.Handler {
47 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
48 if r.Header.Get("Accept") != "" {
49 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), encoders)
50 if contentType == "" {
51 w.WriteHeader(http.StatusNotAcceptable)
52 w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept")))
53 return
54 }
55 }
56 h.ServeHTTP(w, r)
57 })
58 }
60 func encode(w http.ResponseWriter, r *http.Request, status int, resp Response) {
61 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), encoders)
62 w.Header().Set("content-type", contentType)
63 w.WriteHeader(status)
64 var err error
65 switch contentType {
66 case "application/json":
67 enc := json.NewEncoder(w)
68 err = enc.Encode(resp)
69 default:
70 enc := json.NewEncoder(w)
71 err = enc.Encode(resp)
72 }
73 if err != nil {
74 log.Println(err)
75 }
76 }
78 func decode(r *http.Request, target interface{}) error {
79 defer r.Body.Close()
80 switch r.Header.Get("Content-Type") {
81 case "application/json":
82 dec := json.NewDecoder(r.Body)
83 return dec.Decode(target)
84 default:
85 dec := json.NewDecoder(r.Body)
86 return dec.Decode(target)
87 }
88 }
90 func wrap(context Context, f func(w http.ResponseWriter, r *http.Request, context Context)) http.Handler {
91 return negotiate(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
92 w.Header().Set("Access-Control-Allow-Origin", "*")
93 w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
94 w.Header().Set("Access-Control-Allow-Credentials", "true")
95 if r.Method == "OPTIONS" {
96 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
97 w.Header().Set("Allow", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
98 w.WriteHeader(http.StatusOK)
99 return
100 }
101 f(w, r, context)
102 }))
103 }