Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion pkg/output/serialized_output.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package output

import (
"bytes"
"encoding/json"

"github.com/spf13/cobra"
Expand All @@ -12,7 +13,7 @@ import (
func SerializedOutput(cmd *cobra.Command, v any) error {
switch GetFormat(cmd) {
default:
out, err := json.Marshal(v)
out, err := marshalJSON(v)
if err != nil {
return err
}
Expand All @@ -26,3 +27,16 @@ func SerializedOutput(cmd *cobra.Command, v any) error {
}
return nil
}

// marshalJSON marshals v to JSON without HTML-escaping <, >, and & (which
// encoding/json does by default), keeping text like SQL statements readable.
func marshalJSON(v any) ([]byte, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have the similar issue to yaml format?

buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
if err := encoder.Encode(v); err != nil {
return nil, err
}
// Encode appends a single trailing newline; drop just it to match json.Marshal.
return bytes.TrimSuffix(buffer.Bytes(), []byte("\n")), nil
}
3 changes: 1 addition & 2 deletions pkg/output/table.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package output

import (
"encoding/json"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -126,7 +125,7 @@ func (t *Table) printCore(writer io.Writer, auto bool) error {

switch t.format {
default:
out, err := json.Marshal(v)
out, err := marshalJSON(v)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention and solution is straightforward, but these 2 functions are high blast-radius functions, and we need to pay additional attention before touching these.

For example, can you please evaluate and check the impact on these Confluent Cloud commands that are still using the old marshal function? What should be updated if we make changes to printCore()?
https://github.com/confluentinc/cli/blob/main/internal/schema-registry/command_configuration_describe.go#L80-L110

if err != nil {
return err
}
Expand Down
35 changes: 35 additions & 0 deletions pkg/output/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,41 @@ func TestTable_NoAutoWrap(t *testing.T) {
}
}

func TestTable_NoHTMLEscape(t *testing.T) {
// <, >, and & must appear verbatim in -o json output, not HTML-escaped.
buf := new(bytes.Buffer)
cmd := &cobra.Command{}
cmd.Flags().String("output", JSON.String(), "")
cmd.SetOut(buf)

table := NewTable(cmd)
table.Add(&out{
Id: 1,
Name: "lkc-123456",
Description: "SELECT 1 WHERE 1 < 2 AND 3 > 2 & true",
})

err := table.Print()
require.NoError(t, err)

expected := strings.Join([]string{
"{",
` "is_current": false,`,
` "id": 1,`,
` "name": "lkc-123456",`,
` "description": "SELECT 1 WHERE 1 < 2 AND 3 > 2 & true"`,
"}",
}, "\n") + "\n"
require.Equal(t, expected, buf.String())
}

func TestMarshalJSON_NoHTMLEscape(t *testing.T) {
// marshalJSON backs both the Table and SerializedOutput JSON paths.
out, err := marshalJSON(map[string]string{"statement": "SELECT 1 WHERE 1 < 2 AND 3 > 2 & true"})
require.NoError(t, err)
require.Equal(t, `{"statement":"SELECT 1 WHERE 1 < 2 AND 3 > 2 & true"}`, string(out))
}

func TestTable_Filter(t *testing.T) {
tests := map[string][]string{
Human.String(): {
Expand Down
6 changes: 3 additions & 3 deletions test/fixtures/output/api-key/7.golden
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"key": "DEACTIVATEDUSERKEY",
"description": "",
"owner": "sa-6666",
"owner_email": "\u003cdeactivated user\u003e",
"owner_email": "<deactivated user>",
"resource_type": "kafka",
"resource": "lkc-bob",
"created": "1999-02-24T00:00:00Z"
Expand All @@ -30,7 +30,7 @@
"key": "MULTICLUSTERKEY3",
"description": "works for two clusters and owned by service account",
"owner": "sa-12345",
"owner_email": "\u003cservice account\u003e",
"owner_email": "<service account>",
"resource_type": "kafka",
"resource": "lkc-abc",
"created": "1999-02-24T00:00:00Z"
Expand Down Expand Up @@ -66,7 +66,7 @@
"key": "SERVICEACCOUNTKEY1",
"description": "",
"owner": "sa-12345",
"owner_email": "\u003cservice account\u003e",
"owner_email": "<service account>",
"resource_type": "kafka",
"resource": "lkc-bob",
"created": "1999-02-24T00:00:00Z"
Expand Down