From 1e52a86d42609fc23243c18d06d5de513ba9a805 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 31 May 2024 10:31:16 +0200 Subject: [PATCH 1/4] cli/command/context: add test-utility to create multiple contexts Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 54291dd47a46049bff5bde9bba2d84ed783fb1cb) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/list_test.go | 14 +++++++++----- cli/command/context/remove_test.go | 12 ++++-------- cli/command/context/update_test.go | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/command/context/list_test.go b/cli/command/context/list_test.go index 98ac2e480c..1b264a6e85 100644 --- a/cli/command/context/list_test.go +++ b/cli/command/context/list_test.go @@ -8,6 +8,13 @@ import ( "gotest.tools/v3/golden" ) +func createTestContexts(t *testing.T, cli command.Cli, name ...string) { + t.Helper() + for _, n := range name { + createTestContext(t, cli, n) + } +} + func createTestContext(t *testing.T, cli command.Cli, name string) { t.Helper() @@ -21,9 +28,7 @@ func createTestContext(t *testing.T, cli command.Cli, name string) { func TestList(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") - createTestContext(t, cli, "unset") + createTestContexts(t, cli, "current", "other", "unset") cli.SetCurrentContext("current") cli.OutBuffer().Reset() assert.NilError(t, runList(cli, &listOptions{})) @@ -32,8 +37,7 @@ func TestList(t *testing.T) { func TestListQuiet(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") + createTestContexts(t, cli, "current", "other") cli.SetCurrentContext("current") cli.OutBuffer().Reset() assert.NilError(t, runList(cli, &listOptions{quiet: true})) diff --git a/cli/command/context/remove_test.go b/cli/command/context/remove_test.go index 35e27781b2..f42c4fe7eb 100644 --- a/cli/command/context/remove_test.go +++ b/cli/command/context/remove_test.go @@ -13,8 +13,7 @@ import ( func TestRemove(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") + createTestContexts(t, cli, "current", "other") assert.NilError(t, RunRemove(cli, RemoveOptions{}, []string{"other"})) _, err := cli.ContextStore().GetMetadata("current") assert.NilError(t, err) @@ -24,8 +23,7 @@ func TestRemove(t *testing.T) { func TestRemoveNotAContext(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") + createTestContexts(t, cli, "current", "other") err := RunRemove(cli, RemoveOptions{}, []string{"not-a-context"}) assert.ErrorContains(t, err, `context "not-a-context" does not exist`) @@ -35,8 +33,7 @@ func TestRemoveNotAContext(t *testing.T) { func TestRemoveCurrent(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") + createTestContexts(t, cli, "current", "other") cli.SetCurrentContext("current") err := RunRemove(cli, RemoveOptions{}, []string{"current"}) assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`) @@ -50,8 +47,7 @@ func TestRemoveCurrentForce(t *testing.T) { assert.NilError(t, testCfg.Save()) cli := makeFakeCli(t, withCliConfig(testCfg)) - createTestContext(t, cli, "current") - createTestContext(t, cli, "other") + createTestContexts(t, cli, "current", "other") cli.SetCurrentContext("current") assert.NilError(t, RunRemove(cli, RemoveOptions{Force: true}, []string{"current"})) reloadedConfig, err := config.Load(configDir) diff --git a/cli/command/context/update_test.go b/cli/command/context/update_test.go index 5bad6dec2f..553f67fd1a 100644 --- a/cli/command/context/update_test.go +++ b/cli/command/context/update_test.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/context/docker" "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" + is "gotest.tools/v3/assert/cmp" ) func TestUpdateDescriptionOnly(t *testing.T) { @@ -46,7 +46,7 @@ func TestUpdateDockerOnly(t *testing.T) { dc, err := command.GetDockerContext(c) assert.NilError(t, err) assert.Equal(t, dc.Description, "description of test") - assert.Check(t, cmp.Contains(c.Endpoints, docker.DockerEndpoint)) + assert.Check(t, is.Contains(c.Endpoints, docker.DockerEndpoint)) assert.Equal(t, c.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta).Host, "tcp://some-host") } From e23a63eb4c511f62eb51c42417c1846f62eda0ef Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 31 May 2024 10:40:04 +0200 Subject: [PATCH 2/4] cli/command/context: test inspecting context with custom metadata The CLI does not currently expose options to add custom metadata to contexts, but contexts support them. - update test-utilities to allow setting custom metadata - update the inspect test to verify that custom metadata is included when inspecting a context. - update the import/export tests to verify that custom metadata is preserved. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2e9eff235dd018b64ae5560482afad012d24584b) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/create.go | 7 +++- cli/command/context/export-import_test.go | 46 ++++++++++++++------- cli/command/context/inspect_test.go | 4 +- cli/command/context/list_test.go | 6 ++- cli/command/context/remove_test.go | 2 +- cli/command/context/show_test.go | 2 +- cli/command/context/testdata/inspect.golden | 3 +- cli/command/context/update_test.go | 2 +- 8 files changed, 50 insertions(+), 22 deletions(-) diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 1c93a1881a..e4a5258bfa 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -24,6 +24,10 @@ type CreateOptions struct { Description string Docker map[string]string From string + + // Additional Metadata to store in the context. This option is not + // currently exposed to the user. + metaData map[string]any } func longCreateDescription() string { @@ -94,7 +98,8 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { docker.DockerEndpoint: dockerEP, }, Metadata: command.DockerContext{ - Description: o.Description, + Description: o.Description, + AdditionalFields: o.metaData, }, Name: o.Name, } diff --git a/cli/command/context/export-import_test.go b/cli/command/context/export-import_test.go index fc3952c300..9aabd6ab44 100644 --- a/cli/command/context/export-import_test.go +++ b/cli/command/context/export-import_test.go @@ -8,14 +8,18 @@ import ( "path/filepath" "testing" + "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/streams" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) func TestExportImportWithFile(t *testing.T) { contextFile := filepath.Join(t.TempDir(), "exported") cli := makeFakeCli(t) - createTestContext(t, cli, "test") + createTestContext(t, cli, "test", map[string]any{ + "MyCustomMetadata": t.Name(), + }) cli.ErrBuffer().Reset() assert.NilError(t, RunExport(cli, &ExportOptions{ ContextName: "test", @@ -29,18 +33,26 @@ func TestExportImportWithFile(t *testing.T) { assert.NilError(t, err) context2, err := cli.ContextStore().GetMetadata("test2") assert.NilError(t, err) - assert.DeepEqual(t, context1.Endpoints, context2.Endpoints) - assert.DeepEqual(t, context1.Metadata, context2.Metadata) - assert.Equal(t, "test", context1.Name) - assert.Equal(t, "test2", context2.Name) - assert.Equal(t, "test2\n", cli.OutBuffer().String()) - assert.Equal(t, "Successfully imported context \"test2\"\n", cli.ErrBuffer().String()) + assert.Check(t, is.DeepEqual(context1.Metadata, command.DockerContext{ + Description: "description of test", + AdditionalFields: map[string]any{"MyCustomMetadata": t.Name()}, + })) + + assert.Check(t, is.DeepEqual(context1.Endpoints, context2.Endpoints)) + assert.Check(t, is.DeepEqual(context1.Metadata, context2.Metadata)) + assert.Check(t, is.Equal("test", context1.Name)) + assert.Check(t, is.Equal("test2", context2.Name)) + + assert.Check(t, is.Equal("test2\n", cli.OutBuffer().String())) + assert.Check(t, is.Equal("Successfully imported context \"test2\"\n", cli.ErrBuffer().String())) } func TestExportImportPipe(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "test") + createTestContext(t, cli, "test", map[string]any{ + "MyCustomMetadata": t.Name(), + }) cli.ErrBuffer().Reset() cli.OutBuffer().Reset() assert.NilError(t, RunExport(cli, &ExportOptions{ @@ -56,13 +68,19 @@ func TestExportImportPipe(t *testing.T) { assert.NilError(t, err) context2, err := cli.ContextStore().GetMetadata("test2") assert.NilError(t, err) - assert.DeepEqual(t, context1.Endpoints, context2.Endpoints) - assert.DeepEqual(t, context1.Metadata, context2.Metadata) - assert.Equal(t, "test", context1.Name) - assert.Equal(t, "test2", context2.Name) - assert.Equal(t, "test2\n", cli.OutBuffer().String()) - assert.Equal(t, "Successfully imported context \"test2\"\n", cli.ErrBuffer().String()) + assert.Check(t, is.DeepEqual(context1.Metadata, command.DockerContext{ + Description: "description of test", + AdditionalFields: map[string]any{"MyCustomMetadata": t.Name()}, + })) + + assert.Check(t, is.DeepEqual(context1.Endpoints, context2.Endpoints)) + assert.Check(t, is.DeepEqual(context1.Metadata, context2.Metadata)) + assert.Check(t, is.Equal("test", context1.Name)) + assert.Check(t, is.Equal("test2", context2.Name)) + + assert.Check(t, is.Equal("test2\n", cli.OutBuffer().String())) + assert.Check(t, is.Equal("Successfully imported context \"test2\"\n", cli.ErrBuffer().String())) } func TestExportExistingFile(t *testing.T) { diff --git a/cli/command/context/inspect_test.go b/cli/command/context/inspect_test.go index 703898ed53..68ce35ffb0 100644 --- a/cli/command/context/inspect_test.go +++ b/cli/command/context/inspect_test.go @@ -10,7 +10,9 @@ import ( func TestInspect(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") + createTestContext(t, cli, "current", map[string]any{ + "MyCustomMetadata": "MyCustomMetadataValue", + }) cli.OutBuffer().Reset() assert.NilError(t, runInspect(cli, inspectOptions{ refs: []string{"current"}, diff --git a/cli/command/context/list_test.go b/cli/command/context/list_test.go index 1b264a6e85..9370bf6d2d 100644 --- a/cli/command/context/list_test.go +++ b/cli/command/context/list_test.go @@ -11,17 +11,19 @@ import ( func createTestContexts(t *testing.T, cli command.Cli, name ...string) { t.Helper() for _, n := range name { - createTestContext(t, cli, n) + createTestContext(t, cli, n, nil) } } -func createTestContext(t *testing.T, cli command.Cli, name string) { +func createTestContext(t *testing.T, cli command.Cli, name string, metaData map[string]any) { t.Helper() err := RunCreate(cli, &CreateOptions{ Name: name, Description: "description of " + name, Docker: map[string]string{keyHost: "https://someswarmserver.example.com"}, + + metaData: metaData, }) assert.NilError(t, err) } diff --git a/cli/command/context/remove_test.go b/cli/command/context/remove_test.go index f42c4fe7eb..ab2f8ba216 100644 --- a/cli/command/context/remove_test.go +++ b/cli/command/context/remove_test.go @@ -57,7 +57,7 @@ func TestRemoveCurrentForce(t *testing.T) { func TestRemoveDefault(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "other") + createTestContext(t, cli, "other", nil) cli.SetCurrentContext("current") err := RunRemove(cli, RemoveOptions{}, []string{"default"}) assert.ErrorContains(t, err, `default: context "default" cannot be removed`) diff --git a/cli/command/context/show_test.go b/cli/command/context/show_test.go index 40ac58ad05..eab0941692 100644 --- a/cli/command/context/show_test.go +++ b/cli/command/context/show_test.go @@ -8,7 +8,7 @@ import ( func TestShow(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "current") + createTestContext(t, cli, "current", nil) cli.SetCurrentContext("current") cli.OutBuffer().Reset() diff --git a/cli/command/context/testdata/inspect.golden b/cli/command/context/testdata/inspect.golden index ffc790180d..a023169d7a 100644 --- a/cli/command/context/testdata/inspect.golden +++ b/cli/command/context/testdata/inspect.golden @@ -2,7 +2,8 @@ { "Name": "current", "Metadata": { - "Description": "description of current" + "Description": "description of current", + "MyCustomMetadata": "MyCustomMetadataValue" }, "Endpoints": { "docker": { diff --git a/cli/command/context/update_test.go b/cli/command/context/update_test.go index 553f67fd1a..0e4a639145 100644 --- a/cli/command/context/update_test.go +++ b/cli/command/context/update_test.go @@ -34,7 +34,7 @@ func TestUpdateDescriptionOnly(t *testing.T) { func TestUpdateDockerOnly(t *testing.T) { cli := makeFakeCli(t) - createTestContext(t, cli, "test") + createTestContext(t, cli, "test", nil) assert.NilError(t, RunUpdate(cli, &UpdateOptions{ Name: "test", Docker: map[string]string{ From 29cd8591ee94319a1cf436607ed91025f846b79c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 31 May 2024 10:22:39 +0200 Subject: [PATCH 3/4] cli/command/context: list: add test for JSON output Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 40a1da8b426807e016ff08b8252f908d60c8f566) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/list_test.go | 28 +++++++++++++++++++ .../context/testdata/list-json-name.golden | 5 ++++ cli/command/context/testdata/list-json.golden | 5 ++++ 3 files changed, 38 insertions(+) create mode 100644 cli/command/context/testdata/list-json-name.golden create mode 100644 cli/command/context/testdata/list-json.golden diff --git a/cli/command/context/list_test.go b/cli/command/context/list_test.go index 9370bf6d2d..ea7dd1e7c9 100644 --- a/cli/command/context/list_test.go +++ b/cli/command/context/list_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/formatter" "gotest.tools/v3/assert" "gotest.tools/v3/golden" ) @@ -37,6 +38,33 @@ func TestList(t *testing.T) { golden.Assert(t, cli.OutBuffer().String(), "list.golden") } +func TestListJSON(t *testing.T) { + cli := makeFakeCli(t) + createTestContext(t, cli, "current", nil) + createTestContext(t, cli, "context1", map[string]any{"Type": "aci"}) + createTestContext(t, cli, "context2", map[string]any{"Type": "ecs"}) + createTestContext(t, cli, "context3", map[string]any{"Type": "moby"}) + cli.SetCurrentContext("current") + + t.Run("format={{json .}}", func(t *testing.T) { + cli.OutBuffer().Reset() + assert.NilError(t, runList(cli, &listOptions{format: formatter.JSONFormat})) + golden.Assert(t, cli.OutBuffer().String(), "list-json.golden") + }) + + t.Run("format=json", func(t *testing.T) { + cli.OutBuffer().Reset() + assert.NilError(t, runList(cli, &listOptions{format: formatter.JSONFormatKey})) + golden.Assert(t, cli.OutBuffer().String(), "list-json.golden") + }) + + t.Run("format={{ json .Name }}", func(t *testing.T) { + cli.OutBuffer().Reset() + assert.NilError(t, runList(cli, &listOptions{format: `{{ json .Name }}`})) + golden.Assert(t, cli.OutBuffer().String(), "list-json-name.golden") + }) +} + func TestListQuiet(t *testing.T) { cli := makeFakeCli(t) createTestContexts(t, cli, "current", "other") diff --git a/cli/command/context/testdata/list-json-name.golden b/cli/command/context/testdata/list-json-name.golden new file mode 100644 index 0000000000..fbf2dd6c33 --- /dev/null +++ b/cli/command/context/testdata/list-json-name.golden @@ -0,0 +1,5 @@ +"context1" +"context2" +"context3" +"current" +"default" diff --git a/cli/command/context/testdata/list-json.golden b/cli/command/context/testdata/list-json.golden new file mode 100644 index 0000000000..6be551b6de --- /dev/null +++ b/cli/command/context/testdata/list-json.golden @@ -0,0 +1,5 @@ +{"Current":false,"Description":"description of context1","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context1"} +{"Current":false,"Description":"description of context2","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context2"} +{"Current":false,"Description":"description of context3","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context3"} +{"Current":true,"Description":"description of current","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"current"} +{"Current":false,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","Error":"","Name":"default"} From 258e27438f7b40dce1a637d13263aa1f29732692 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 30 May 2024 14:33:15 +0200 Subject: [PATCH 4/4] context list: temporarily add ContextType to JSON output Docker Desktop currently ships with the "cloud integration" wrapper, which outputs an additional ContextType field in the JSON output. While this field is non-standard, it made its way into Visual Studio's Docker integration, which uses this to exclude "aci" and "eci" context types that are not supported by Visual Studio. This patch; - conditionally adds a ContextType field to the JSON output - but ONLY when using the default "{{json .}}" or "json" formats (which are the formats used by Visual Studio) - if the context is a "aci" or "eci" context, that type is preserved, otherwise the default "moby" type is used. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit fed9fa0f728d338ccf8ed6f507b2d6e184ba5637) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/list.go | 30 +++++++++++++++++++ cli/command/context/testdata/list-json.golden | 10 +++---- cli/command/formatter/context.go | 16 ++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/cli/command/context/list.go b/cli/command/context/list.go index aaf0195a73..55173d51ed 100644 --- a/cli/command/context/list.go +++ b/cli/command/context/list.go @@ -66,6 +66,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error { Name: rawMeta.Name, Current: isCurrent, Error: err.Error(), + + ContextType: getContextType(nil, opts.format), }) continue } @@ -80,6 +82,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error { Description: meta.Description, DockerEndpoint: dockerEndpoint.Host, Error: errMsg, + + ContextType: getContextType(meta.AdditionalFields, opts.format), } contexts = append(contexts, &desc) } @@ -96,6 +100,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error { Name: curContext, Current: true, Error: errMsg, + + ContextType: getContextType(nil, opts.format), }) } sort.Slice(contexts, func(i, j int) bool { @@ -111,6 +117,30 @@ func runList(dockerCli command.Cli, opts *listOptions) error { return nil } +// getContextType sets the LegacyContextType field for compatibility with +// Visual Studio, which depends on this field from the "cloud integration" +// wrapper. +// +// https://github.com/docker/compose-cli/blob/c156ce6da4c2b317174d42daf1b019efa87e9f92/api/context/store/contextmetadata.go#L28-L34 +// https://github.com/docker/compose-cli/blob/c156ce6da4c2b317174d42daf1b019efa87e9f92/api/context/store/store.go#L34-L51 +// +// TODO(thaJeztah): remove this and [ClientContext.ContextType] once Visual Studio is updated to no longer depend on this. +func getContextType(meta map[string]any, format string) string { + if format != formatter.JSONFormat && format != formatter.JSONFormatKey { + // We only need the ContextType field when formatting as JSON, + // which is the format-string used by Visual Studio to detect the + // context-type. + return "" + } + if ct, ok := meta["Type"]; ok { + // If the context on-disk has a context-type (ecs, aci), return it. + return ct.(string) + } + + // Use the default context-type. + return "moby" +} + func format(dockerCli command.Cli, opts *listOptions, contexts []*formatter.ClientContext) error { contextCtx := formatter.Context{ Output: dockerCli.Out(), diff --git a/cli/command/context/testdata/list-json.golden b/cli/command/context/testdata/list-json.golden index 6be551b6de..a55cf47f7c 100644 --- a/cli/command/context/testdata/list-json.golden +++ b/cli/command/context/testdata/list-json.golden @@ -1,5 +1,5 @@ -{"Current":false,"Description":"description of context1","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context1"} -{"Current":false,"Description":"description of context2","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context2"} -{"Current":false,"Description":"description of context3","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"context3"} -{"Current":true,"Description":"description of current","DockerEndpoint":"https://someswarmserver.example.com","Error":"","Name":"current"} -{"Current":false,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","Error":"","Name":"default"} +{"Name":"context1","Description":"description of context1","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"aci"} +{"Name":"context2","Description":"description of context2","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"ecs"} +{"Name":"context3","Description":"description of context3","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"moby"} +{"Name":"current","Description":"description of current","DockerEndpoint":"https://someswarmserver.example.com","Current":true,"Error":"","ContextType":"moby"} +{"Name":"default","Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","Current":false,"Error":"","ContextType":"moby"} diff --git a/cli/command/formatter/context.go b/cli/command/formatter/context.go index 985a6ff32e..293c84155a 100644 --- a/cli/command/formatter/context.go +++ b/cli/command/formatter/context.go @@ -1,5 +1,7 @@ package formatter +import "encoding/json" + const ( // ClientContextTableFormat is the default client context format. ClientContextTableFormat = "table {{.Name}}{{if .Current}} *{{end}}\t{{.Description}}\t{{.DockerEndpoint}}\t{{.Error}}" @@ -28,6 +30,13 @@ type ClientContext struct { DockerEndpoint string Current bool Error string + + // ContextType is a temporary field for compatibility with + // Visual Studio, which depends on this from the "cloud integration" + // wrapper. + // + // Deprecated: this type is only for backward-compatibility. Do not use. + ContextType string `json:"ContextType,omitempty"` } // ClientContextWrite writes formatted contexts using the Context @@ -60,6 +69,13 @@ func newClientContextContext() *clientContextContext { } func (c *clientContextContext) MarshalJSON() ([]byte, error) { + if c.c.ContextType != "" { + // We only have ContextType set for plain "json" or "{{json .}}" formatting, + // so we should be able to just use the default json.Marshal with no + // special handling. + return json.Marshal(c.c) + } + // FIXME(thaJeztah): why do we need a special marshal function here? return MarshalJSON(c) }