This repository has been archived by the owner on Dec 29, 2023. It is now read-only.
forked from derekdowling/go-json-spec-handler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.go
168 lines (140 loc) · 3.88 KB
/
parser.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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package jsh
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
/*
ParseObject validates the HTTP request and returns a JSON object for a given
io.ReadCloser containing a raw JSON payload. Here's an example of how to use it
as part of your full flow.
func Handler(w http.ResponseWriter, r *http.Request) {
obj, error := jsh.ParseObject(r)
if error != nil {
// log your error
err := jsh.Send(w, r, error)
return
}
yourType := &YourType{}
err := object.Unmarshal("yourtype", &yourType)
if err != nil {
err := jsh.Send(w, r, err)
return
}
yourType.ID = obj.ID
// do business logic
err := object.Marshal(yourType)
if err != nil {
// log error
err := jsh.Send(w, r, err)
return
}
err := jsh.Send(w, r, object)
}
*/
func ParseObject(r *http.Request) (*Object, *Error) {
document, err := ParseDoc(r, ObjectMode)
if err != nil {
return nil, err
}
if !document.HasData() {
return nil, nil
}
object := document.First()
if r.Method != "POST" && object.ID == "" {
return nil, InputError("Missing mandatory object attribute", "id")
}
return object, nil
}
/*
ParseList validates the HTTP request and returns a resulting list of objects
parsed from the request Body. Use just like ParseObject.
*/
func ParseList(r *http.Request) (List, *Error) {
document, err := ParseDoc(r, ListMode)
if err != nil {
return nil, err
}
return document.Data, nil
}
/*
ParseDoc parses and returns a top level jsh.Document. In most cases, using
"ParseList" or "ParseObject" is preferable.
*/
func ParseDoc(r *http.Request, mode DocumentMode) (*Document, *Error) {
return NewParser(r).Document(r.Body, mode)
}
// Parser is an abstraction layer that helps to support parsing JSON payload from
// many types of sources, and allows other libraries to leverage this if desired.
type Parser struct {
Method string
Headers http.Header
}
// NewParser creates a parser from an http.Request
func NewParser(request *http.Request) *Parser {
return &Parser{
Method: request.Method,
Headers: request.Header,
}
}
/*
Document returns a single JSON data object from the parser. In the process it will
also validate any data objects against the JSON API.
*/
func (p *Parser) Document(payload io.ReadCloser, mode DocumentMode) (*Document, *Error) {
defer closeReader(payload)
err := validateHeaders(p.Headers)
if err != nil {
return nil, err
}
document := &Document{
Data: List{},
Mode: mode,
}
decodeErr := json.NewDecoder(payload).Decode(document)
if decodeErr != nil {
return nil, ISE(fmt.Sprintf("Error parsing JSON Document: %s", decodeErr.Error()))
}
// If the document has data, validate against specification
if document.HasData() {
for _, object := range document.Data {
// TODO: currently this doesn't really do any user input
// validation since it is validating against the jsh
// "Object" type. Figure out how to options pass the
// corressponding user object struct in to enable this
// without making the API super clumsy.
inputErr := validateInput(object)
if inputErr != nil {
return nil, inputErr[0]
}
// if we have a list, then all resource objects should have IDs, will
// cross the bridge of bulk creation if and when there is a use case
if len(document.Data) > 1 && object.ID == "" {
return nil, InputError("Object without ID present in list", "id")
}
}
}
return document, nil
}
/*
closeReader is a deferal helper function for closing a reader and logging any errors that might occur after the fact.
*/
func closeReader(reader io.ReadCloser) {
err := reader.Close()
if err != nil {
log.Println("Unable to close request Body")
}
}
func validateHeaders(headers http.Header) *Error {
reqContentType := headers.Get("Content-Type")
if reqContentType != ContentType {
return SpecificationError(fmt.Sprintf(
"Expected Content-Type header to be %s, got: %s",
ContentType,
reqContentType,
))
}
return nil
}