response.go 4.74 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 (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18 19
	ErrNormal ErrorType = iota // general errors
	ErrClient                  // error was caused by the client, (e.g. invalid CLI usage)
Matt Bell's avatar
Matt Bell committed
20
	// TODO: add more types of errors for better error-specific handling
Matt Bell's avatar
Matt Bell committed
21 22
)

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

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

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

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

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

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

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

		b, err := xml.Marshal(value)
		if err != nil {
			return nil, err
81
		}
82
		return bytes.NewReader(b), nil
83
	},
84 85
}

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

	// Set/Return the response Error
	SetError(err error, code ErrorType)
93
	Error() *Error
94 95

	// Sets/Returns the response value
96 97
	SetOutput(interface{})
	Output() interface{}
98

99 100 101 102
	// Sets/Returns the length of the output
	SetLength(uint64)
	Length() uint64

103
	// Marshal marshals out the response into a buffer. It uses the EncodingType
104
	// on the Request to chose a Marshaler (Codec).
105
	Marshal() (io.Reader, error)
106 107 108

	// Gets a io.Reader that reads the marshalled output
	Reader() (io.Reader, error)
109 110 111 112

	// Gets Stdout and Stderr, for writing to console without using SetOutput
	Stdout() io.Writer
	Stderr() io.Writer
113 114 115
}

type response struct {
116 117 118 119 120
	req    Request
	err    *Error
	value  interface{}
	out    io.Reader
	length uint64
121 122
	stdout io.Writer
	stderr io.Writer
123 124 125 126 127 128
}

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

129
func (r *response) Output() interface{} {
130 131 132
	return r.value
}

133
func (r *response) SetOutput(v interface{}) {
134 135 136
	r.value = v
}

137 138 139 140 141 142 143 144
func (r *response) Length() uint64 {
	return r.length
}

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

145
func (r *response) Error() *Error {
146
	return r.err
Matt Bell's avatar
Matt Bell committed
147 148
}

149 150
func (r *response) SetError(err error, code ErrorType) {
	r.err = &Error{Message: err.Error(), Code: code}
151 152
}

153
func (r *response) Marshal() (io.Reader, error) {
154
	if r.err == nil && r.value == nil {
155
		return bytes.NewReader([]byte{}), nil
Matt Bell's avatar
Matt Bell committed
156
	}
157

158
	enc, found, err := r.req.Option(EncShort).String()
159 160 161
	if err != nil {
		return nil, err
	}
162 163 164
	if !found {
		return nil, fmt.Errorf("No encoding type was specified")
	}
165
	encType := EncodingType(strings.ToLower(enc))
Matt Bell's avatar
Matt Bell committed
166

167 168
	// Special case: if text encoding and an error, just print it out.
	if encType == Text && r.Error() != nil {
169
		return strings.NewReader(r.Error().Error()), nil
170 171
	}

172 173 174
	var marshaller Marshaler
	if r.req.Command() != nil && r.req.Command().Marshalers != nil {
		marshaller = r.req.Command().Marshalers[encType]
175
	}
176
	if marshaller == nil {
177
		var ok bool
178 179 180 181
		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
182
	}
183

184 185 186 187 188 189 190 191
	output, err := marshaller(r)
	if err != nil {
		return nil, err
	}
	if output == nil {
		return bytes.NewReader([]byte{}), nil
	}
	return output, nil
192 193
}

194 195 196
// 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) {
197 198
	if r.out == nil {
		if out, ok := r.value.(io.Reader); ok {
199
			// if command returned a io.Reader, use that as our reader
200 201
			r.out = out

202 203 204 205 206 207
		} else {
			// otherwise, use the response marshaler output
			marshalled, err := r.Marshal()
			if err != nil {
				return nil, err
			}
208

209 210
			r.out = marshalled
		}
211 212
	}

213
	return r.out, nil
214 215
}

216 217 218 219 220 221 222 223
func (r *response) Stdout() io.Writer {
	return r.stdout
}

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

224
// NewResponse returns a response to match given Request
225
func NewResponse(req Request) Response {
226 227 228 229 230
	return &response{
		req:    req,
		stdout: os.Stdout,
		stderr: os.Stderr,
	}
231
}