From 900b69a1ffb62a29dfc3145ff1bac46aef4d3a5a Mon Sep 17 00:00:00 2001 From: Jose Antonio Insua Date: Thu, 26 Feb 2026 18:44:37 +0100 Subject: [PATCH] Refactor pretty-print JSON into cmd/pretty package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the output package with cmd/pretty. The new pretty.JSON() function returns a prettified string instead of printing directly, letting callers handle their own output. Move prettifying out of ims/decode.go — DecodeToken now returns raw JSON strings, and cmd/decode.go applies pretty.JSON before printing. Remove unused DecodedToken.Signature field. --- cmd/admin/organizations.go | 4 +- cmd/admin/profile.go | 4 +- cmd/decode.go | 5 ++- cmd/organizations.go | 4 +- output/output.go => cmd/pretty/json.go | 20 ++++----- .../output_test.go => cmd/pretty/json_test.go | 44 ++++--------------- .../pretty}/testdata/empty_string.txt | 0 {output => cmd/pretty}/testdata/non_json.txt | 0 cmd/profile.go | 4 +- cmd/validate/access_token.go | 4 +- cmd/validate/authorization_code.go | 4 +- cmd/validate/device_token.go | 4 +- cmd/validate/refresh_token.go | 4 +- ims/decode.go | 36 +++------------ 14 files changed, 42 insertions(+), 95 deletions(-) rename output/output.go => cmd/pretty/json.go (62%) rename output/output_test.go => cmd/pretty/json_test.go (63%) rename {output => cmd/pretty}/testdata/empty_string.txt (100%) rename {output => cmd/pretty}/testdata/non_json.txt (100%) diff --git a/cmd/admin/organizations.go b/cmd/admin/organizations.go index 53c86cf..5be7b21 100644 --- a/cmd/admin/organizations.go +++ b/cmd/admin/organizations.go @@ -13,8 +13,8 @@ package admin import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -33,7 +33,7 @@ func OrganizationsCmd(imsConfig *ims.Config) *cobra.Command { if err != nil { return fmt.Errorf("error in get admin organizations cmd: %w", err) } - output.PrintPrettyJSON(resp) + fmt.Println(pretty.JSON(resp)) return nil }, } diff --git a/cmd/admin/profile.go b/cmd/admin/profile.go index b285447..9bc545d 100644 --- a/cmd/admin/profile.go +++ b/cmd/admin/profile.go @@ -13,8 +13,8 @@ package admin import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -31,7 +31,7 @@ func ProfileCmd(imsConfig *ims.Config) *cobra.Command { if err != nil { return fmt.Errorf("error in get admin profile cmd: %w", err) } - output.PrintPrettyJSON(resp) + fmt.Println(pretty.JSON(resp)) return nil }, } diff --git a/cmd/decode.go b/cmd/decode.go index 518c5ce..4ae70d7 100644 --- a/cmd/decode.go +++ b/cmd/decode.go @@ -13,6 +13,7 @@ package cmd import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" "github.com/spf13/cobra" ) @@ -32,9 +33,9 @@ func decodeCmd(imsConfig *ims.Config) *cobra.Command { return fmt.Errorf("error decoding the token: %w", err) } - fmt.Println(decoded.Header) + fmt.Println(pretty.JSON(decoded.Header)) fmt.Println() - fmt.Println(decoded.Payload) + fmt.Println(pretty.JSON(decoded.Payload)) return nil }, diff --git a/cmd/organizations.go b/cmd/organizations.go index 4929f3c..c15e3bf 100644 --- a/cmd/organizations.go +++ b/cmd/organizations.go @@ -13,8 +13,8 @@ package cmd import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -33,7 +33,7 @@ func organizationsCmd(imsConfig *ims.Config) *cobra.Command { if err != nil { return fmt.Errorf("error in get organizations cmd: %w", err) } - output.PrintPrettyJSON(resp) + fmt.Println(pretty.JSON(resp)) return nil }, } diff --git a/output/output.go b/cmd/pretty/json.go similarity index 62% rename from output/output.go rename to cmd/pretty/json.go index 12d43cf..00fb1ee 100644 --- a/output/output.go +++ b/cmd/pretty/json.go @@ -8,23 +8,19 @@ // OF ANY KIND, either express or implied. See the License for the specific language // governing permissions and limitations under the License. -package output +package pretty import ( "bytes" "encoding/json" - "fmt" ) -// PrintPrettyJSON prints a JSON string with indentation. -// If the input is not valid JSON, it prints the original string. -func PrintPrettyJSON(jsonStr string) { - var prettyBuf bytes.Buffer - err := json.Indent(&prettyBuf, []byte(jsonStr), "", " ") - if err != nil { - // Not valid JSON, print as-is - fmt.Println(jsonStr) - return +// JSON returns the input with JSON indentation applied. +// If the input is not valid JSON, it returns the original string. +func JSON(jsonStr string) string { + var buf bytes.Buffer + if err := json.Indent(&buf, []byte(jsonStr), "", " "); err != nil { + return jsonStr } - fmt.Println(prettyBuf.String()) + return buf.String() } diff --git a/output/output_test.go b/cmd/pretty/json_test.go similarity index 63% rename from output/output_test.go rename to cmd/pretty/json_test.go index 73005f1..d8acc7b 100644 --- a/output/output_test.go +++ b/cmd/pretty/json_test.go @@ -8,49 +8,25 @@ // OF ANY KIND, either express or implied. See the License for the specific language // governing permissions and limitations under the License. -package output +package pretty import ( - "bytes" - "io" "os" "path/filepath" + "strings" "testing" ) -// captureStdout captures the output of a function that writes to stdout. -func captureStdout(t *testing.T, fn func()) string { - t.Helper() - old := os.Stdout - r, w, err := os.Pipe() - if err != nil { - t.Fatalf("failed to create pipe: %v", err) - } - os.Stdout = w - - fn() - - w.Close() - os.Stdout = old - - var buf bytes.Buffer - if _, err := io.Copy(&buf, r); err != nil { - t.Fatalf("failed to read pipe: %v", err) - } - return buf.String() -} - -// loadExpected reads the expected output from a golden file in testdata/. func loadExpected(t *testing.T, filename string) string { t.Helper() data, err := os.ReadFile(filepath.Join("testdata", filename)) if err != nil { t.Fatalf("failed to read golden file %s: %v", filename, err) } - return string(data) + return strings.TrimSuffix(string(data), "\n") } -func TestPrintPrettyJSON(t *testing.T) { +func TestJSON(t *testing.T) { tests := []struct { name string input string @@ -62,20 +38,16 @@ func TestPrintPrettyJSON(t *testing.T) { {name: "empty object", input: `{}`, file: "empty_object.json"}, {name: "empty array", input: `[]`, file: "empty_array.json"}, {name: "nested objects", input: `{"a":{"b":{"c":1}}}`, file: "nested_objects.json"}, - {name: "non-JSON is printed as-is", input: "this is not JSON", file: "non_json.txt"}, - {name: "empty string is printed as-is", input: "", file: "empty_string.txt"}, + {name: "non-JSON is returned as-is", input: "this is not JSON", file: "non_json.txt"}, + {name: "empty string is returned as-is", input: "", file: "empty_string.txt"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { expected := loadExpected(t, tt.file) - - got := captureStdout(t, func() { - PrintPrettyJSON(tt.input) - }) - + got := JSON(tt.input) if got != expected { - t.Errorf("PrintPrettyJSON(%q)\ngot:\n%s\nexpected output is in testdata/%s", tt.input, got, tt.file) + t.Errorf("JSON(%q)\ngot:\n%s\nexpected:\n%s", tt.input, got, expected) } }) } diff --git a/output/testdata/empty_string.txt b/cmd/pretty/testdata/empty_string.txt similarity index 100% rename from output/testdata/empty_string.txt rename to cmd/pretty/testdata/empty_string.txt diff --git a/output/testdata/non_json.txt b/cmd/pretty/testdata/non_json.txt similarity index 100% rename from output/testdata/non_json.txt rename to cmd/pretty/testdata/non_json.txt diff --git a/cmd/profile.go b/cmd/profile.go index 7324205..4ab29ba 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -13,8 +13,8 @@ package cmd import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -32,7 +32,7 @@ func profileCmd(imsConfig *ims.Config) *cobra.Command { if err != nil { return fmt.Errorf("error in get profile cmd: %w", err) } - output.PrintPrettyJSON(resp) + fmt.Println(pretty.JSON(resp)) return nil }, } diff --git a/cmd/validate/access_token.go b/cmd/validate/access_token.go index c8ee4a5..70f4428 100644 --- a/cmd/validate/access_token.go +++ b/cmd/validate/access_token.go @@ -13,8 +13,8 @@ package validate import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ func AccessTokenCmd(imsConfig *ims.Config) *cobra.Command { if !resp.Valid { return fmt.Errorf("invalid token: %v", resp.Info) } - output.PrintPrettyJSON(resp.Info) + fmt.Println(pretty.JSON(resp.Info)) return nil }, } diff --git a/cmd/validate/authorization_code.go b/cmd/validate/authorization_code.go index 9625aa1..66337bc 100644 --- a/cmd/validate/authorization_code.go +++ b/cmd/validate/authorization_code.go @@ -13,8 +13,8 @@ package validate import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ func AuthzCodeCmd(imsConfig *ims.Config) *cobra.Command { if !resp.Valid { return fmt.Errorf("invalid token: %v", resp.Info) } - output.PrintPrettyJSON(resp.Info) + fmt.Println(pretty.JSON(resp.Info)) return nil }, } diff --git a/cmd/validate/device_token.go b/cmd/validate/device_token.go index 99cd5f6..7209bfc 100644 --- a/cmd/validate/device_token.go +++ b/cmd/validate/device_token.go @@ -13,8 +13,8 @@ package validate import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command { if !resp.Valid { return fmt.Errorf("invalid token: %v", resp.Info) } - output.PrintPrettyJSON(resp.Info) + fmt.Println(pretty.JSON(resp.Info)) return nil }, } diff --git a/cmd/validate/refresh_token.go b/cmd/validate/refresh_token.go index dd6d1c6..e149111 100644 --- a/cmd/validate/refresh_token.go +++ b/cmd/validate/refresh_token.go @@ -13,8 +13,8 @@ package validate import ( "fmt" + "github.com/adobe/imscli/cmd/pretty" "github.com/adobe/imscli/ims" - "github.com/adobe/imscli/output" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command { if !resp.Valid { return fmt.Errorf("invalid token: %v", resp.Info) } - output.PrintPrettyJSON(resp.Info) + fmt.Println(pretty.JSON(resp.Info)) return nil }, } diff --git a/ims/decode.go b/ims/decode.go index 50f94dc..991f1f4 100644 --- a/ims/decode.go +++ b/ims/decode.go @@ -11,18 +11,15 @@ package ims import ( - "bytes" "encoding/base64" - "encoding/json" "fmt" "strings" ) // DecodedToken represents the decoded parts of a JWT token. type DecodedToken struct { - Header string - Payload string - Signature string + Header string + Payload string } func (i Config) validateDecodeTokenConfig() error { @@ -44,40 +41,21 @@ func (i Config) DecodeToken() (*DecodedToken, error) { return nil, fmt.Errorf("the JWT is not composed by 3 parts") } - // Decode header and payload (not signature since it's binary) - decoded := &DecodedToken{ - Signature: parts[2], - } + decoded := &DecodedToken{} - // Decode and prettify header + // Decode header headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { return nil, fmt.Errorf("error decoding token header: %w", err) } - decoded.Header, err = prettyJSON(headerBytes) - if err != nil { - return nil, fmt.Errorf("error formatting token header: %w", err) - } + decoded.Header = string(headerBytes) - // Decode and prettify payload + // Decode payload payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, fmt.Errorf("error decoding token payload: %w", err) } - decoded.Payload, err = prettyJSON(payloadBytes) - if err != nil { - return nil, fmt.Errorf("error formatting token payload: %w", err) - } + decoded.Payload = string(payloadBytes) return decoded, nil } - -// prettyJSON formats JSON bytes with indentation. -func prettyJSON(data []byte) (string, error) { - var prettyBuf bytes.Buffer - err := json.Indent(&prettyBuf, data, "", " ") - if err != nil { - return "", err - } - return prettyBuf.String(), nil -}