response.go 3.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"
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"
Matt Bell's avatar
Matt Bell committed
39
	// TODO: support more encoding types
40 41
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42 43
// Marshaller is a function used by coding types.
// TODO this should just be a `coding.Codec`
Matt Bell's avatar
Matt Bell committed
44 45
type Marshaller func(v interface{}) ([]byte, error)

46
var marshallers = map[EncodingType]Marshaller{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47 48
	JSON: json.Marshal,
	XML:  xml.Marshal,
49 50
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51 52
// Response is the result of a command request. Handlers write to the response,
// setting Error or Value. Response is returned to the client.
53
type Response interface {
54 55
	io.Reader

56 57 58 59
	Request() Request

	// Set/Return the response Error
	SetError(err error, code ErrorType)
60
	Error() *Error
61 62 63 64 65 66 67 68 69 70 71 72 73 74

	// Sets/Returns the response value
	SetValue(interface{})
	Value() interface{}

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

type response struct {
	req   Request
	err   *Error
	value interface{}
75
	out   io.Reader
76 77 78 79 80 81 82 83 84 85 86 87 88 89
}

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

func (r *response) Value() interface{} {
	return r.value
}

func (r *response) SetValue(v interface{}) {
	r.value = v
}

90
func (r *response) Error() *Error {
91
	return r.err
Matt Bell's avatar
Matt Bell committed
92 93
}

94 95
func (r *response) SetError(err error, code ErrorType) {
	r.err = &Error{Message: err.Error(), Code: code}
96 97
}

98 99
func (r *response) Marshal() ([]byte, error) {
	if r.err == nil && r.value == nil {
Matt Bell's avatar
Matt Bell committed
100 101
		return nil, fmt.Errorf("No error or value set, there is nothing to marshal")
	}
102

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
103
	enc, ok := r.req.Option(EncShort)
104
	if !ok || enc.(string) == "" {
Matt Bell's avatar
Matt Bell committed
105 106 107
		return nil, fmt.Errorf("No encoding type was specified")
	}
	encType := EncodingType(strings.ToLower(enc.(string)))
Matt Bell's avatar
Matt Bell committed
108

Matt Bell's avatar
Matt Bell committed
109 110 111 112
	marshaller, ok := marshallers[encType]
	if !ok {
		return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
	}
113

114 115
	if r.err != nil {
		return marshaller(r.err)
Matt Bell's avatar
Matt Bell committed
116
	}
117 118 119
	return marshaller(r.value)
}

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
func (r *response) Read(p []byte) (int, error) {
	// if command set value to a io.Reader, set that as our output stream
	if r.out == nil {
		if out, ok := r.value.(io.Reader); ok {
			r.out = out
		}
	}

	// if there is an output stream set, read from it
	if r.out != nil {
		return r.out.Read(p)
	}

	// no stream set, so marshal the error or value
	output, err := r.Marshal()
	if err != nil {
		return 0, err
	}

	// then create a Reader from the marshalled data, and use it as our output stream
	r.out = bytes.NewReader(output)
	return r.out.Read(p)
}

144
// NewResponse returns a response to match given Request
145 146
func NewResponse(req Request) Response {
	return &response{req: req}
147
}