forked from rs/rest-layer
/
request.go
103 lines (96 loc) · 3.1 KB
/
request.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package rest
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/rs/rest-layer/schema"
)
// requestHandler handles the request life cycle
type requestHandler struct {
h *Handler
req *http.Request
res http.ResponseWriter
s ResponseSender
skipBody bool
}
func (r *requestHandler) send(status int, data interface{}) {
r.s.Send(r.res, status, data)
}
func (r *requestHandler) sendError(err error) {
r.s.SendError(r.res, err, r.skipBody)
}
func (r *requestHandler) sendItem(status int, i *Item) {
r.s.SendItem(r.res, status, i, r.skipBody)
}
func (r *requestHandler) sendList(l *ItemList) {
r.s.SendList(r.res, l, r.skipBody)
}
// decodePayload decodes the payload from the provided request
func (r *requestHandler) decodePayload(payload *map[string]interface{}) *Error {
// Check content-type, if not specified, assume it's JSON and fail later
if ct := r.req.Header.Get("Content-Type"); ct != "" && ct != "application/json" {
return &Error{501, fmt.Sprintf("Invalid Content-Type header: `%s' not supported", ct), nil}
}
decoder := json.NewDecoder(r.req.Body)
defer r.req.Body.Close()
if err := decoder.Decode(payload); err != nil {
return &Error{400, fmt.Sprintf("Malformed body: %s", err.Error()), nil}
}
return nil
}
// checkIntegrityRequest ensures that orignal item exists and complies with conditions
// expressed by If-Match and/or If-Unmodified-Since headers if present.
func (r *requestHandler) checkIntegrityRequest(original *Item) *Error {
ifMatch := r.req.Header.Get("If-Match")
ifUnmod := r.req.Header.Get("If-Unmodified-Since")
if ifMatch != "" || ifUnmod != "" {
if original == nil {
return NotFoundError
}
if ifMatch != "" && original.Etag != ifMatch {
return PreconditionFailedError
}
if ifUnmod != "" {
if ifUnmodTime, err := time.Parse(time.RFC1123, ifUnmod); err != nil {
return &Error{400, "Invalid If-Unmodified-Since header", nil}
} else if original.Updated.After(ifUnmodTime) {
return PreconditionFailedError
}
}
}
return nil
}
// checkReferences checks that fields with the Reference validator reference an existing object
func (r *requestHandler) checkReferences(payload map[string]interface{}, s schema.Validator) *Error {
for name, value := range payload {
field := s.GetField(name)
if field == nil {
continue
}
// Check reference if validator is of type Reference
if field.Validator != nil {
if ref, ok := field.Validator.(*schema.Reference); ok {
resource := r.h.getResource(ref.Path)
if resource == nil {
return &Error{500, fmt.Sprintf("Invalid resource reference for field `%s': %s", name, ref.Path), nil}
}
lookup := NewLookup()
lookup.Fields["id"] = value
list, _ := resource.handler.Find(lookup, 1, 1)
if len(list.Items) == 0 {
return &Error{404, fmt.Sprintf("Resource reference not found for field `%s'", name), nil}
}
}
}
// Check sub-schema if any
if field.Schema != nil && value != nil {
if subPayload, ok := value.(map[string]interface{}); ok {
if err := r.checkReferences(subPayload, field.Schema); err != nil {
return err
}
}
}
}
return nil
}