response.go 5.22 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
21
	ErrNotFound                        // == HTTP 404 Not Found
Matt Bell's avatar
Matt Bell committed
22
	// TODO: add more types of errors for better error-specific handling
Matt Bell's avatar
Matt Bell committed
23 24
)

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

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

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

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

47
func marshalJson(value interface{}) (io.Reader, error) {
Jeromy's avatar
Jeromy committed
48
	b, err := json.Marshal(value)
49 50 51
	if err != nil {
		return nil, err
	}
52
	b = append(b, '\n')
53 54 55
	return bytes.NewReader(b), nil
}

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

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

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

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

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

	// Sets/Returns the response value
101 102
	SetOutput(interface{})
	Output() interface{}
103

104 105 106 107
	// Sets/Returns the length of the output
	SetLength(uint64)
	Length() uint64

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

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

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

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

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

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

139
func (r *response) Output() interface{} {
140 141 142
	return r.value
}

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

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

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

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

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

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

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

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

182 183 184
	var marshaller Marshaler
	if r.req.Command() != nil && r.req.Command().Marshalers != nil {
		marshaller = r.req.Command().Marshalers[encType]
185
	}
186
	if marshaller == nil {
187
		var ok bool
188 189 190 191
		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
192
	}
193

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

204 205 206
// 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) {
207 208
	if r.out == nil {
		if out, ok := r.value.(io.Reader); ok {
209
			// if command returned a io.Reader, use that as our reader
210 211
			r.out = out

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

219 220
			r.out = marshalled
		}
221 222
	}

223
	return r.out, nil
224 225
}

226 227 228 229 230 231 232 233 234 235 236
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
}

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

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

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