-
Notifications
You must be signed in to change notification settings - Fork 3
/
json_response.go
142 lines (125 loc) · 4.07 KB
/
json_response.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
// Copyright (c) 2018, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file
// distributed with the sources of this project regarding your rights to use or distribute this
// software.
package jsonresp
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func (e *Error) Error() string {
if e.Message != "" {
return fmt.Sprintf("%v (%v %v)", e.Message, e.Code, http.StatusText(e.Code))
}
return fmt.Sprintf("%v %v", e.Code, http.StatusText(e.Code))
}
// PageDetails specifies paging information.
type PageDetails struct {
Prev string `json:"prev,omitempty"`
Next string `json:"next,omitempty"`
TotalSize int `json:"totalSize,omitempty"`
}
// Error describes an error condition.
type Error struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
var (
// JSONErrorUnauthorized is a generic 401 unauthorized response
JSONErrorUnauthorized = &Error{
Code: http.StatusUnauthorized,
Message: "Unauthorized",
}
)
// Response is the top level container of all of our REST API responses.
type Response struct {
Data interface{} `json:"data,omitempty"`
Page *PageDetails `json:"page,omitempty"`
Error *Error `json:"error,omitempty"`
}
// NewError returns an error that contains the given code and message.
func NewError(code int, message string) *Error {
return &Error{
Code: code,
Message: message,
}
}
func encodeResponse(w http.ResponseWriter, jr Response, code int) error {
// We _could_ encode the JSON directly to the response, but in so doing, the response code is
// written out the first time Write() is called under the hood. This makes it difficult to
// return an appropriate HTTP code when JSON encoding fails, so we use an intermediate buffer
// in order to preserve our ability to set the correct HTTP code.
b, err := json.Marshal(jr)
if err != nil {
return fmt.Errorf("jsonresp: failed to encode response: %v", err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
if _, err := w.Write(b); err != nil {
return fmt.Errorf("jsonresp: failed to write response: %v", err)
}
return nil
}
// WriteError encodes the supplied error in a response, and writes to w.
func WriteError(w http.ResponseWriter, error string, code int) error {
jr := Response{
Error: &Error{
Code: code,
Message: error,
},
}
return encodeResponse(w, jr, code)
}
// WriteResponsePage encodes the supplied data in a paged JSON response, and writes to w.
func WriteResponsePage(w http.ResponseWriter, data interface{}, pd *PageDetails, code int) error {
jr := Response{
Data: data,
Page: pd,
}
return encodeResponse(w, jr, code)
}
// WriteResponse encodes the supplied data in a response, and writes to w.
func WriteResponse(w http.ResponseWriter, data interface{}, code int) error {
return WriteResponsePage(w, data, nil, code)
}
// ReadResponsePage reads a paged JSON response, and unmarshals the supplied data.
func ReadResponsePage(r io.Reader, v interface{}) (pd *PageDetails, err error) {
var u struct {
Data json.RawMessage `json:"data"`
Page *PageDetails `json:"page"`
Error *Error `json:"error"`
}
if err := json.NewDecoder(r).Decode(&u); err != nil {
return nil, fmt.Errorf("jsonresp: failed to read response: %v", err)
}
if u.Error != nil {
return nil, u.Error
}
if v != nil {
if err := json.Unmarshal(u.Data, v); err != nil {
return nil, fmt.Errorf("jsonresp: failed to unmarshal response: %v", err)
}
}
return u.Page, nil
}
// ReadResponse reads a JSON response, and unmarshals the supplied data.
func ReadResponse(r io.Reader, v interface{}) error {
_, err := ReadResponsePage(r, v)
return err
}
// ReadError attempts to unmarshal JSON-encoded error details from the supplied reader. It returns
// nil if an error could not be parsed from the response, or if the parsed error was nil.
func ReadError(r io.Reader) error {
var u struct {
Error *Error `json:"error"`
}
if err := json.NewDecoder(r).Decode(&u); err != nil {
return nil
}
if u.Error == nil {
return nil
}
return u.Error
}