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

3
import (
Matt Bell's avatar
Matt Bell committed
4 5 6
	"encoding/json"
	"encoding/xml"
	"fmt"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
7
	"io"
Matt Bell's avatar
Matt Bell committed
8
	"strings"
9 10
)

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

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

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

27
func (e Error) Error() string {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
28 29 30 31
	return fmt.Sprintf("%d error: %s", e.Code, e.Message)
}

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

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

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

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

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

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

	// 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{}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72
	out   io.Writer
73 74 75 76 77 78 79 80 81 82 83 84 85 86
}

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
}

87
func (r *response) Stream() io.Writer {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88
	return r.out
89 90
}

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

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

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

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

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

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

// NewResponse returns a response to match given Request
122 123
func NewResponse(req Request) Response {
	return &response{req: req}
124
}