response.go 5.14 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2
package commands

3
import (
4
	"bytes"
Matt Bell's avatar
Matt Bell committed
5 6 7
	"encoding/json"
	"encoding/xml"
	"fmt"
8
	"io"
9
	"os"
Matt Bell's avatar
Matt Bell committed
10
	"strings"
11 12
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
13
// ErrorType signfies a category of errors
Matt Bell's avatar
Matt Bell committed
14
type ErrorType uint
Matt Bell's avatar
Matt Bell committed
15

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
16
// ErrorTypes convey what category of error ocurred
Matt Bell's avatar
Matt Bell committed
17
const (
18 19 20
	ErrNormal         ErrorType = iota // general errors
	ErrClient                          // error was caused by the client, (e.g. invalid CLI usage)
	ErrImplementation                  // programmer error in the server
Matt Bell's avatar
Matt Bell committed
21
	// TODO: add more types of errors for better error-specific handling
Matt Bell's avatar
Matt Bell committed
22 23
)

24 25
// Error is a struct for marshalling errors
type Error struct {
Matt Bell's avatar
Matt Bell committed
26 27
	Message string
	Code    ErrorType
28 29
}

30
func (e Error) Error() string {
31
	return e.Message
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
32 33 34
}

// EncodingType defines a supported encoding
35
type EncodingType string
Matt Bell's avatar
Matt Bell committed
36

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
37
// Supported EncodingType constants.
38
const (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
39 40
	JSON = "json"
	XML  = "xml"
41
	Text = "text"
Matt Bell's avatar
Matt Bell committed
42
	// TODO: support more encoding types
43 44
)

45 46 47 48 49
func marshalJson(value interface{}) (io.Reader, error) {
	b, err := json.MarshalIndent(value, "", "  ")
	if err != nil {
		return nil, err
	}
50
	b = append(b, '\n')
51 52 53
	return bytes.NewReader(b), nil
}

54
var marshallers = map[EncodingType]Marshaler{
55
	JSON: func(res Response) (io.Reader, error) {
56 57
		ch, ok := res.Output().(<-chan interface{})
		if ok {
58 59 60
			return &ChannelMarshaler{
				Channel:   ch,
				Marshaler: marshalJson,
61
				Res:       res,
62 63 64
			}, nil
		}

65
		var value interface{}
66
		if res.Error() != nil {
67 68 69
			value = res.Error()
		} else {
			value = res.Output()
70
		}
71
		return marshalJson(value)
72
	},
73 74
	XML: func(res Response) (io.Reader, error) {
		var value interface{}
75
		if res.Error() != nil {
76 77 78 79 80 81 82 83
			value = res.Error()
		} else {
			value = res.Output()
		}

		b, err := xml.Marshal(value)
		if err != nil {
			return nil, err
84
		}
85
		return bytes.NewReader(b), nil
86
	},
87 88
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
89 90
// Response is the result of a command request. Handlers write to the response,
// setting Error or Value. Response is returned to the client.
91 92 93 94 95
type Response interface {
	Request() Request

	// Set/Return the response Error
	SetError(err error, code ErrorType)
96
	Error() *Error
97 98

	// Sets/Returns the response value
99 100
	SetOutput(interface{})
	Output() interface{}
101

102 103 104 105
	// Sets/Returns the length of the output
	SetLength(uint64)
	Length() uint64

106 107 108 109
	// underlying http connections need to be cleaned up, this is for that
	Close() error
	SetCloser(io.Closer)

110
	// Marshal marshals out the response into a buffer. It uses the EncodingType
111
	// on the Request to chose a Marshaler (Codec).
112
	Marshal() (io.Reader, error)
113 114 115

	// Gets a io.Reader that reads the marshalled output
	Reader() (io.Reader, error)
116 117 118 119

	// Gets Stdout and Stderr, for writing to console without using SetOutput
	Stdout() io.Writer
	Stderr() io.Writer
120 121 122
}

type response struct {
123 124 125 126 127
	req    Request
	err    *Error
	value  interface{}
	out    io.Reader
	length uint64
128 129
	stdout io.Writer
	stderr io.Writer
130
	closer io.Closer
131 132 133 134 135 136
}

func (r *response) Request() Request {
	return r.req
}

137
func (r *response) Output() interface{} {
138 139 140
	return r.value
}

141
func (r *response) SetOutput(v interface{}) {
142 143 144
	r.value = v
}

145 146 147 148 149 150 151 152
func (r *response) Length() uint64 {
	return r.length
}

func (r *response) SetLength(l uint64) {
	r.length = l
}

153
func (r *response) Error() *Error {
154
	return r.err
Matt Bell's avatar
Matt Bell committed
155 156
}

157 158
func (r *response) SetError(err error, code ErrorType) {
	r.err = &Error{Message: err.Error(), Code: code}
159 160
}

161
func (r *response) Marshal() (io.Reader, error) {
162
	if r.err == nil && r.value == nil {
163
		return bytes.NewReader([]byte{}), nil
Matt Bell's avatar
Matt Bell committed
164
	}
165

166
	enc, found, err := r.req.Option(EncShort).String()
167 168 169
	if err != nil {
		return nil, err
	}
170 171 172
	if !found {
		return nil, fmt.Errorf("No encoding type was specified")
	}
173
	encType := EncodingType(strings.ToLower(enc))
Matt Bell's avatar
Matt Bell committed
174

175 176
	// Special case: if text encoding and an error, just print it out.
	if encType == Text && r.Error() != nil {
177
		return strings.NewReader(r.Error().Error()), nil
178 179
	}

180 181 182
	var marshaller Marshaler
	if r.req.Command() != nil && r.req.Command().Marshalers != nil {
		marshaller = r.req.Command().Marshalers[encType]
183
	}
184
	if marshaller == nil {
185
		var ok bool
186 187 188 189
		marshaller, ok = marshallers[encType]
		if !ok {
			return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
		}
Matt Bell's avatar
Matt Bell committed
190
	}
191

192 193 194 195 196 197 198 199
	output, err := marshaller(r)
	if err != nil {
		return nil, err
	}
	if output == nil {
		return bytes.NewReader([]byte{}), nil
	}
	return output, nil
200 201
}

202 203 204
// Reader returns an `io.Reader` representing marshalled output of this Response
// Note that multiple calls to this will return a reference to the same io.Reader
func (r *response) Reader() (io.Reader, error) {
205 206
	if r.out == nil {
		if out, ok := r.value.(io.Reader); ok {
207
			// if command returned a io.Reader, use that as our reader
208 209
			r.out = out

210 211 212 213 214 215
		} else {
			// otherwise, use the response marshaler output
			marshalled, err := r.Marshal()
			if err != nil {
				return nil, err
			}
216

217 218
			r.out = marshalled
		}
219 220
	}

221
	return r.out, nil
222 223
}

224 225 226 227 228 229 230 231 232 233 234
func (r *response) Close() error {
	if r.closer != nil {
		return r.closer.Close()
	}
	return nil
}

func (r *response) SetCloser(c io.Closer) {
	r.closer = c
}

235 236 237 238 239 240 241 242
func (r *response) Stdout() io.Writer {
	return r.stdout
}

func (r *response) Stderr() io.Writer {
	return r.stderr
}

243
// NewResponse returns a response to match given Request
244
func NewResponse(req Request) Response {
245 246 247 248 249
	return &response{
		req:    req,
		stdout: os.Stdout,
		stderr: os.Stderr,
	}
250
}