response.go 3.51 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"
Matt Bell's avatar
Matt Bell committed
9
	"strings"
10 11
)

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

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

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

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

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

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

var marshallers = map[EncodingType]Marshaller{
44 45 46 47
	JSON: func(res Response) ([]byte, error) {
		if res.Error() != nil {
			return json.Marshal(res.Error())
		}
48
		return json.Marshal(res.Output())
49 50 51 52 53
	},
	XML: func(res Response) ([]byte, error) {
		if res.Error() != nil {
			return xml.Marshal(res.Error())
		}
54
		return xml.Marshal(res.Output())
55 56 57 58 59 60 61
	},
	Text: func(res Response) ([]byte, error) {
		format := res.Request().Command().Format
		if format == nil {
			return nil, ErrNoFormatter
		}

62
		bytes, err := format(res)
63 64 65
		if err != nil {
			return nil, err
		}
66
		return bytes, nil
67
	},
68 69
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70 71
// Response is the result of a command request. Handlers write to the response,
// setting Error or Value. Response is returned to the client.
72 73 74 75 76
type Response interface {
	Request() Request

	// Set/Return the response Error
	SetError(err error, code ErrorType)
77
	Error() *Error
78 79

	// Sets/Returns the response value
80 81
	SetOutput(interface{})
	Output() interface{}
82 83 84 85

	// Marshal marshals out the response into a buffer. It uses the EncodingType
	// on the Request to chose a Marshaller (Codec).
	Marshal() ([]byte, error)
86 87 88

	// Gets a io.Reader that reads the marshalled output
	Reader() (io.Reader, error)
89 90 91 92 93 94
}

type response struct {
	req   Request
	err   *Error
	value interface{}
95
	out   io.Reader
96 97 98 99 100 101
}

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

102
func (r *response) Output() interface{} {
103 104 105
	return r.value
}

106
func (r *response) SetOutput(v interface{}) {
107 108 109
	r.value = v
}

110
func (r *response) Error() *Error {
111
	return r.err
Matt Bell's avatar
Matt Bell committed
112 113
}

114 115
func (r *response) SetError(err error, code ErrorType) {
	r.err = &Error{Message: err.Error(), Code: code}
116 117
}

118 119
func (r *response) Marshal() ([]byte, error) {
	if r.err == nil && r.value == nil {
120
		return []byte{}, nil
Matt Bell's avatar
Matt Bell committed
121
	}
122

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123
	enc, ok := r.req.Option(EncShort)
124
	if !ok || enc.(string) == "" {
Matt Bell's avatar
Matt Bell committed
125 126 127
		return nil, fmt.Errorf("No encoding type was specified")
	}
	encType := EncodingType(strings.ToLower(enc.(string)))
Matt Bell's avatar
Matt Bell committed
128

Matt Bell's avatar
Matt Bell committed
129 130 131 132
	marshaller, ok := marshallers[encType]
	if !ok {
		return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
	}
133

134
	return marshaller(r)
135 136
}

137 138 139 140
// 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) {
	// if command set value to a io.Reader, use that as our reader
141 142 143 144 145 146
	if r.out == nil {
		if out, ok := r.value.(io.Reader); ok {
			r.out = out
		}
	}

147 148 149 150 151 152
	if r.out == nil {
		// no reader set, so marshal the error or value
		marshalled, err := r.Marshal()
		if err != nil {
			return nil, err
		}
153

154 155
		// create a Reader from the marshalled data
		r.out = bytes.NewReader(marshalled)
156 157
	}

158
	return r.out, nil
159 160
}

161
// NewResponse returns a response to match given Request
162 163
func NewResponse(req Request) Response {
	return &response{req: req}
164
}