Skip to content

Commit 549d39a

Browse files
committed
cli-plugins/manager: fix Plugin marshaling with regular errors
Go does not by default marshal `error` type fields to JSON. The manager package therefore implemented a `pluginError` type that implements [encoding.TextMarshaler]. However, the field was marked as a regular `error`, which made it brittle; assining any other type of error would result in the error being discarded in the marshaled JSON (as used in `docker info` output), resulting in the error being marshaled as `{}`. This patch adds a custom `MarshalJSON()` on the `Plugin` type itself so that any error is rendered. It checks if the error used already implements [encoding.TextMarshaler], otherwise wraps the error in a `pluginError`. [encoding.TextMarshaler]: https://pkg.go.dev/encoding#TextMarshaler Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 54367b3 commit 549d39a

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

cli-plugins/manager/plugin.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package manager
22

33
import (
44
"context"
5+
"encoding"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -31,6 +32,22 @@ type Plugin struct {
3132
ShadowedPaths []string `json:",omitempty"`
3233
}
3334

35+
// MarshalJSON implements [json.Marshaler] to handle marshaling the
36+
// [Plugin.Err] field (Go doesn't marshal errors by default).
37+
func (p *Plugin) MarshalJSON() ([]byte, error) {
38+
type Alias Plugin // avoid recursion
39+
40+
cp := *p // shallow copy to avoid mutating original
41+
42+
if cp.Err != nil {
43+
if _, ok := cp.Err.(encoding.TextMarshaler); !ok {
44+
cp.Err = &pluginError{cp.Err}
45+
}
46+
}
47+
48+
return json.Marshal((*Alias)(&cp))
49+
}
50+
3451
// pluginCandidate represents a possible plugin candidate, for mocking purposes.
3552
type pluginCandidate interface {
3653
Path() string

cli-plugins/manager/plugin_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package manager
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"testing"
7+
8+
"gotest.tools/v3/assert"
9+
is "gotest.tools/v3/assert/cmp"
10+
)
11+
12+
func TestPluginMarshal(t *testing.T) {
13+
const jsonWithError = `{"Name":"some-plugin","Err":"something went wrong"}`
14+
const jsonNoError = `{"Name":"some-plugin"}`
15+
16+
tests := []struct {
17+
doc string
18+
error error
19+
expected string
20+
}{
21+
{
22+
doc: "no error",
23+
expected: jsonNoError,
24+
},
25+
{
26+
doc: "regular error",
27+
error: errors.New("something went wrong"),
28+
expected: jsonWithError,
29+
},
30+
{
31+
doc: "custom error",
32+
error: NewPluginError("something went wrong"),
33+
expected: jsonWithError,
34+
},
35+
}
36+
for _, tc := range tests {
37+
t.Run(tc.doc, func(t *testing.T) {
38+
actual, err := json.Marshal(&Plugin{Name: "some-plugin", Err: tc.error})
39+
assert.NilError(t, err)
40+
assert.Check(t, is.Equal(string(actual), tc.expected))
41+
})
42+
}
43+
}

cli/command/system/info_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package system
22

33
import (
44
"encoding/base64"
5+
"errors"
56
"net"
67
"testing"
78
"time"
@@ -226,7 +227,7 @@ var samplePluginsInfo = []pluginmanager.Plugin{
226227
{
227228
Name: "badplugin",
228229
Path: "/path/to/docker-badplugin",
229-
Err: pluginmanager.NewPluginError("something wrong"),
230+
Err: errors.New("something wrong"),
230231
},
231232
}
232233

0 commit comments

Comments
 (0)