Skip to content

Commit 6c60fc4

Browse files
authored
Merge pull request #128 from ipfs/fix/65
split the value encoder and the error encoder
2 parents f4d1d56 + 29d4721 commit 6c60fc4

15 files changed

+128
-97
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ sudo: false
66
language: go
77

88
go:
9-
- 1.9.x
9+
- 1.11.x
1010

1111
install:
1212
- make deps

cli/responseemitter.go

-4
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ func (re *responseEmitter) SetLength(l uint64) {
6666
re.length = l
6767
}
6868

69-
func (re *responseEmitter) SetEncoder(enc func(io.Writer) cmds.Encoder) {
70-
re.enc = enc(re.stdout)
71-
}
72-
7369
func (re *responseEmitter) CloseWithError(err error) error {
7470
if err == nil {
7571
return re.Close()

command.go

-14
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,6 @@ func (c *Command) call(req *Request, re ResponseEmitter, env Environment) error
104104
return err
105105
}
106106

107-
// If this ResponseEmitter encodes messages (e.g. http, cli or writer - but not chan),
108-
// we need to update the encoding to the one specified by the command.
109-
if re_, ok := re.(EncodingEmitter); ok {
110-
encType := GetEncoding(req)
111-
112-
if enc, ok := cmd.Encoders[encType]; ok {
113-
re_.SetEncoder(enc(req))
114-
} else if enc, ok := Encoders[encType]; ok {
115-
re_.SetEncoder(enc(req))
116-
} else {
117-
return fmt.Errorf("unknown encoding %q", encType)
118-
}
119-
}
120-
121107
return cmd.Run(req, re, env)
122108
}
123109

encoding.go

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"encoding/xml"
66
"fmt"
7+
"github.com/ipfs/go-ipfs-cmdkit"
78
"io"
89
"reflect"
910
)
@@ -130,3 +131,32 @@ func (e TextEncoder) Encode(v interface{}) error {
130131
_, err := fmt.Fprintf(e.w, "%s%s", v, e.suffix)
131132
return err
132133
}
134+
135+
// GetEncoders takes a request and returns returns the encoding type, an error encoder, and a value encoder.
136+
func GetEncoders(req *Request, w io.Writer) (encType EncodingType, valEnc, errEnc Encoder, err error) {
137+
encType = GetEncoding(req)
138+
139+
if fn, ok := Encoders[encType]; ok {
140+
errEnc = fn(req)(w)
141+
} else {
142+
return encType, nil, nil, cmdkit.Errorf(cmdkit.ErrClient, "invalid encoding: %s", encType)
143+
}
144+
145+
// Only override the value encoder.
146+
if fn, ok := req.Command.Encoders[encType]; ok {
147+
valEnc = fn(req)(w)
148+
} else {
149+
valEnc = errEnc
150+
}
151+
return encType, valEnc, errEnc, nil
152+
}
153+
154+
// GetDecoder takes a request and returns the encoding type and the decoder.
155+
func GetDecoder(req *Request, r io.Reader) (encType EncodingType, dec Decoder, err error) {
156+
encType = GetEncoding(req)
157+
158+
if fn, ok := Decoders[encType]; ok {
159+
return encType, fn(r), nil
160+
}
161+
return encType, nil, cmdkit.Errorf(cmdkit.ErrClient, "invalid encoding: %s", encType)
162+
}

executor.go

-20
Original file line numberDiff line numberDiff line change
@@ -49,26 +49,6 @@ func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) (e
4949
return err
5050
}
5151

52-
// If this ResponseEmitter encodes messages (e.g. http, cli or writer - but not chan),
53-
// we need to update the encoding to the one specified by the command.
54-
if ee, ok := re.(EncodingEmitter); ok {
55-
encType := GetEncoding(req)
56-
57-
// use JSON if text was requested but the command doesn't have a text-encoder
58-
if _, ok := cmd.Encoders[encType]; encType == Text && !ok {
59-
encType = JSON
60-
}
61-
62-
if enc, ok := cmd.Encoders[encType]; ok {
63-
ee.SetEncoder(enc(req))
64-
} else if enc, ok := Encoders[encType]; ok {
65-
ee.SetEncoder(enc(req))
66-
} else {
67-
log.Errorf("unknown encoding %q, using json", encType)
68-
ee.SetEncoder(Encoders[JSON](req))
69-
}
70-
}
71-
7252
if cmd.PreRun != nil {
7353
err = cmd.PreRun(req, env)
7454
if err != nil {

executor_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ func TestExecutor(t *testing.T) {
5050
}
5151

5252
var buf bytes.Buffer
53-
re := NewWriterResponseEmitter(wc{&buf, nopCloser{}}, req, Encoders[Text])
53+
re, err := NewWriterResponseEmitter(wc{&buf, nopCloser{}}, req)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
5457

5558
x := NewExecutor(root)
5659
x.Execute(req, re, &env)

http/client.go

-16
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,6 @@ func NewClient(address string, opts ...ClientOpt) Client {
7575
func (c *client) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
7676
cmd := req.Command
7777

78-
// If this ResponseEmitter encodes messages (e.g. http, cli or writer - but not chan),
79-
// we need to update the encoding to the one specified by the command.
80-
if ee, ok := re.(cmds.EncodingEmitter); ok {
81-
encType := cmds.GetEncoding(req)
82-
83-
// note the difference: cmd.Encoders vs. cmds.Encoders
84-
if enc, ok := cmd.Encoders[encType]; ok {
85-
ee.SetEncoder(enc(req))
86-
} else if enc, ok := cmds.Encoders[encType]; ok {
87-
ee.SetEncoder(enc(req))
88-
} else {
89-
log.Errorf("unknown encoding %q, using json", encType)
90-
ee.SetEncoder(cmds.Encoders[cmds.JSON](req))
91-
}
92-
}
93-
9478
if cmd.PreRun != nil {
9579
err := cmd.PreRun(req, env)
9680
if err != nil {

http/errors_test.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import (
99
"strings"
1010
"testing"
1111

12+
"github.com/ipfs/go-ipfs-cmdkit"
1213
"github.com/ipfs/go-ipfs-cmds"
1314
)
1415

1516
func TestErrors(t *testing.T) {
1617
type testcase struct {
18+
opts cmdkit.OptMap
1719
path []string
1820
bodyStr string
1921
status string
@@ -46,6 +48,25 @@ func TestErrors(t *testing.T) {
4648
errTrailer: "an error occurred",
4749
},
4850

51+
{
52+
path: []string{"encode"},
53+
opts: cmdkit.OptMap{
54+
cmds.EncLong: cmds.Text,
55+
},
56+
status: "500 Internal Server Error",
57+
bodyStr: "an error occurred",
58+
},
59+
60+
{
61+
path: []string{"lateencode"},
62+
opts: cmdkit.OptMap{
63+
cmds.EncLong: cmds.Text,
64+
},
65+
status: "200 OK",
66+
bodyStr: "hello\n",
67+
errTrailer: "an error occurred",
68+
},
69+
4970
{
5071
path: []string{"doubleclose"},
5172
status: "200 OK",
@@ -69,7 +90,7 @@ func TestErrors(t *testing.T) {
6990
return func(t *testing.T) {
7091
_, srv := getTestServer(t, nil) // handler_test:/^func getTestServer/
7192
c := NewClient(srv.URL)
72-
req, err := cmds.NewRequest(context.Background(), tc.path, nil, nil, nil, cmdRoot)
93+
req, err := cmds.NewRequest(context.Background(), tc.path, tc.opts, nil, nil, cmdRoot)
7394
if err != nil {
7495
t.Fatal(err)
7596
}

http/handler.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
157157
}
158158
}
159159

160-
re := NewResponseEmitter(w, r.Method, req)
160+
re, err := NewResponseEmitter(w, r.Method, req)
161+
if err != nil {
162+
re.CloseWithError(err)
163+
return
164+
}
161165
h.root.Call(req, re, h.env)
162166
}
163167

http/handler_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,34 @@ var (
8282
},
8383
Type: "",
8484
},
85+
"encode": &cmds.Command{
86+
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
87+
return errors.New("an error occurred")
88+
},
89+
Type: "",
90+
Encoders: cmds.EncoderMap{
91+
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, v string) error {
92+
fmt.Fprintln(w, v)
93+
return nil
94+
}),
95+
},
96+
},
97+
"lateencode": &cmds.Command{
98+
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
99+
re.Emit("hello")
100+
return errors.New("an error occurred")
101+
},
102+
Type: "",
103+
Encoders: cmds.EncoderMap{
104+
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, v string) error {
105+
fmt.Fprintln(w, v)
106+
if v != "hello" {
107+
return fmt.Errorf("expected hello, got %s", v)
108+
}
109+
return nil
110+
}),
111+
},
112+
},
85113
"doubleclose": &cmds.Command{
86114
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
87115
t, ok := getTestingT(env)

http/responseemitter.go

+11-17
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,20 @@ var (
2828
)
2929

3030
// NewResponeEmitter returns a new ResponseEmitter.
31-
func NewResponseEmitter(w http.ResponseWriter, method string, req *cmds.Request) ResponseEmitter {
32-
encType := cmds.GetEncoding(req)
33-
34-
var enc cmds.Encoder
35-
36-
if _, ok := cmds.Encoders[encType]; ok {
37-
enc = cmds.Encoders[encType](req)(w)
31+
func NewResponseEmitter(w http.ResponseWriter, method string, req *cmds.Request) (ResponseEmitter, error) {
32+
encType, valEnc, errEnc, err := cmds.GetEncoders(req, w)
33+
if err != nil {
34+
return nil, err
3835
}
39-
4036
re := &responseEmitter{
4137
w: w,
4238
encType: encType,
43-
enc: enc,
39+
errEnc: errEnc,
40+
valEnc: valEnc,
4441
method: method,
4542
req: req,
4643
}
47-
return re
44+
return re, nil
4845
}
4946

5047
type ResponseEmitter interface {
@@ -55,7 +52,8 @@ type ResponseEmitter interface {
5552
type responseEmitter struct {
5653
w http.ResponseWriter
5754

58-
enc cmds.Encoder
55+
errEnc cmds.Encoder
56+
valEnc cmds.Encoder // overrides the normal encoder
5957
encType cmds.EncodingType
6058
req *cmds.Request
6159

@@ -121,7 +119,7 @@ func (re *responseEmitter) Emit(value interface{}) error {
121119
case io.Reader:
122120
err = flushCopy(re.w, v)
123121
default:
124-
err = re.enc.Encode(value)
122+
err = re.valEnc.Encode(value)
125123
}
126124

127125
if isSingle && err == nil {
@@ -259,7 +257,7 @@ func (re *responseEmitter) doPreamble(value interface{}) {
259257
err = &cmdkit.Error{Message: err.Error()}
260258
}
261259

262-
err = re.enc.Encode(err)
260+
err = re.errEnc.Encode(err)
263261
if err != nil {
264262
log.Error("error sending error value after non-200 response", err)
265263
}
@@ -272,10 +270,6 @@ type responseWriterer interface {
272270
Lower() http.ResponseWriter
273271
}
274272

275-
func (re *responseEmitter) SetEncoder(enc func(io.Writer) cmds.Encoder) {
276-
re.enc = enc(re.w)
277-
}
278-
279273
func flushCopy(w io.Writer, r io.Reader) error {
280274
buf := make([]byte, 4096)
281275
f, ok := w.(http.Flusher)

response_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ func TestMarshalling(t *testing.T) {
3838

3939
buf := bytes.NewBuffer(nil)
4040
wc := writecloser{Writer: buf, Closer: nopCloser{}}
41-
re := NewWriterResponseEmitter(wc, req, Encoders[JSON])
41+
re, err := NewWriterResponseEmitter(wc, req)
42+
if err != nil {
43+
t.Fatal(err)
44+
}
4245

4346
err = re.Emit(TestOutput{"beep", "boop", 1337})
4447
if err != nil {

responseemitter.go

-6
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ type ResponseEmitter interface {
5050
Emit(value interface{}) error
5151
}
5252

53-
type EncodingEmitter interface {
54-
ResponseEmitter
55-
56-
SetEncoder(func(io.Writer) Encoder)
57-
}
58-
5953
// Copy sends all values received on res to re. If res is closed, it closes re.
6054
func Copy(re ResponseEmitter, res Response) error {
6155
re.SetLength(res.Length())

single_test.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@ func TestSingleWriter(t *testing.T) {
5959
}
6060

6161
pr, pw := io.Pipe()
62-
re := NewWriterResponseEmitter(pw, req, Encoders["json"])
63-
res := NewReaderResponse(pr, "json", req)
62+
re, err := NewWriterResponseEmitter(pw, req)
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
res, err := NewReaderResponse(pr, req)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
6470

6571
var wg sync.WaitGroup
6672

0 commit comments

Comments
 (0)