diff --git a/cmd/cmdtest.go b/cmd/cmdtest.go index 38a3edd0..5c98b384 100644 --- a/cmd/cmdtest.go +++ b/cmd/cmdtest.go @@ -11,7 +11,10 @@ import ( "ldcli/internal/analytics" ) -var ValidResponse = `{"valid": true}` +var StubbedSuccessResponse = `{ + "key": "test-key", + "name": "test-name" +}` func CallCmd( t *testing.T, diff --git a/cmd/config/config.go b/cmd/config/config.go index f9779e62..831401d2 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -13,6 +13,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/config" + "ldcli/internal/output" ) const ( @@ -63,11 +64,16 @@ func run() func(*cobra.Command, []string) error { return err } - if string(configJSON) == "{}" { - return nil + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + configJSON, + output.ConfigPlaintextOutputFn, + ) + if err != nil { + return err } - fmt.Fprint(cmd.OutOrStdout(), string(configJSON)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") case viper.GetBool(SetFlag): // flag needs two arguments: a key and value if len(args)%2 != 0 { diff --git a/cmd/environments/get.go b/cmd/environments/get.go index 8e052412..e169adc4 100644 --- a/cmd/environments/get.go +++ b/cmd/environments/get.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/environments" + "ldcli/internal/errors" "ldcli/internal/output" ) @@ -63,18 +64,28 @@ func runGet( viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/environments/get_test.go b/cmd/environments/get_test.go index 74cc9fb1..7c7c4ce2 100644 --- a/cmd/environments/get_test.go +++ b/cmd/environments/get_test.go @@ -72,7 +72,7 @@ func TestGet(t *testing.T) { client := environments.MockClient{} client. On("Get", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ EnvironmentsClient: &client, } @@ -80,7 +80,6 @@ func TestGet(t *testing.T) { "environments", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "--output", "json", "--environment", "test-env", "--project", "test-proj", } @@ -178,7 +177,7 @@ func TestGet(t *testing.T) { client := environments.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ EnvironmentsClient: &client, } diff --git a/cmd/flags/create.go b/cmd/flags/create.go index a8bc0bbf..e9180e1d 100644 --- a/cmd/flags/create.go +++ b/cmd/flags/create.go @@ -10,7 +10,9 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewCreateCmd(client flags.Client) (*cobra.Command, error) { @@ -52,10 +54,6 @@ type inputData struct { func runCreate(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.DataFlag, cmd.Flags().Lookup(cliflags.DataFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - var data inputData err := json.Unmarshal([]byte(viper.GetString(cliflags.DataFlag)), &data) if err != nil { @@ -71,10 +69,28 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + response, + output.SingularPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 8403ce33..799ee4aa 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -26,7 +26,7 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -34,6 +34,7 @@ func TestCreate(t *testing.T) { "flags", "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, "--project", "test-proj-key", } @@ -41,7 +42,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,12 +51,13 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "create", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, "--project", "test-proj-key", } @@ -63,14 +65,14 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -159,7 +161,7 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } diff --git a/cmd/flags/get.go b/cmd/flags/get.go index de18ba8d..77912d51 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -9,7 +9,9 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewGetCmd(client flags.Client) (*cobra.Command, error) { @@ -56,11 +58,6 @@ func NewGetCmd(client flags.Client) (*cobra.Command, error) { func runGet(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - _ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag)) - response, err := client.Get( context.Background(), viper.GetString(cliflags.AccessTokenFlag), @@ -70,10 +67,28 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.EnvironmentFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + response, + output.SingularPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index 636db908..ee65f641 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -25,7 +25,7 @@ func TestGet(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -33,6 +33,7 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", @@ -41,7 +42,7 @@ func TestGet(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,13 +51,14 @@ func TestGet(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "get", "--flag", "test-key", + "--output", "json", "--project", "test-proj-key", "--environment", "test-env-key", } @@ -64,14 +66,14 @@ func TestGet(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -129,13 +131,14 @@ func TestGet(t *testing.T) { "base-uri", "environment", "flag", + "output", "project", }) client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -144,6 +147,7 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", diff --git a/cmd/flags/update.go b/cmd/flags/update.go index 21775144..f2275cfe 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -10,7 +10,9 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewUpdateCmd(client flags.Client) (*cobra.Command, error) { @@ -115,11 +117,6 @@ func setToggleCommandFlags(cmd *cobra.Command) (*cobra.Command, error) { func runUpdate(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.DataFlag, cmd.Flags().Lookup(cliflags.DataFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) - var patch []flags.UpdateInput if cmd.CalledAs() == "toggle-on" || cmd.CalledAs() == "toggle-off" { _ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag)) @@ -141,10 +138,28 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { patch, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + response, + output.SingularPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index c290f7e5..70a1fde3 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -32,7 +32,7 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -40,6 +40,7 @@ func TestUpdate(t *testing.T) { "flags", "update", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -48,7 +49,7 @@ func TestUpdate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -57,12 +58,13 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "update", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -71,14 +73,14 @@ func TestUpdate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -135,13 +137,14 @@ func TestUpdate(t *testing.T) { "base-uri", "data", "flag", + "output", "project", }) client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -149,6 +152,7 @@ func TestUpdate(t *testing.T) { "flags", "update", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -179,7 +183,7 @@ func TestToggle(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -187,6 +191,7 @@ func TestToggle(t *testing.T) { "flags", "toggle-on", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-flag-key", "--project", "test-proj-key", "--environment", "test-env-key", @@ -195,14 +200,14 @@ func TestToggle(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -260,13 +265,14 @@ func TestToggle(t *testing.T) { "base-uri", "environment", "flag", + "output", "project", }) client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -274,6 +280,7 @@ func TestToggle(t *testing.T) { "flags", "toggle-on", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-flag-key", "--project", "test-proj-key", "--environment", "test-env-key", diff --git a/cmd/members/create.go b/cmd/members/create.go index f3d3406a..154c06d8 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -10,7 +10,9 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" + "ldcli/internal/output" ) func NewCreateCmd(client members.Client) (*cobra.Command, error) { @@ -41,7 +43,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { // TODO: why does viper.GetString(cliflags.DataFlag) not work? err := json.Unmarshal([]byte(cmd.Flags().Lookup(cliflags.DataFlag).Value.String()), &data) if err != nil { - return err + return errors.NewError(err.Error()) } response, err := client.Create( @@ -51,10 +53,28 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { data, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + response, + output.SingularPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 4244f716..feb2efcb 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -25,7 +25,7 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -34,6 +34,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } @@ -41,7 +42,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,16 +51,17 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "create", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } @@ -67,14 +69,14 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ MembersClient: &client, } @@ -129,12 +131,13 @@ func TestCreate(t *testing.T) { "access-token", "base-uri", "data", + "output", }) client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -143,6 +146,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } diff --git a/cmd/members/invite.go b/cmd/members/invite.go index cc118e68..450d6412 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -9,7 +9,9 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" + "ldcli/internal/output" ) func NewInviteCmd(client members.Client) (*cobra.Command, error) { @@ -60,10 +62,28 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { memberInputs, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputMultiple( + viper.GetString(cliflags.OutputFlag), + response, + output.MultipleEmailPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index 603ba3e1..c98197d4 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -28,7 +28,7 @@ func TestInvite(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -37,14 +37,14 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -53,31 +53,31 @@ func TestInvite(t *testing.T) { client := members.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "-e", - `testemail1@test.com,testemail2@test.com`, + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ MembersClient: &client, } @@ -86,8 +86,7 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, + "-e", `testemail1@test.com,testemail2@test.com`, } _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) @@ -132,12 +131,13 @@ func TestInvite(t *testing.T) { "access-token", "base-uri", "emails", + "output", }) client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -146,6 +146,7 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, } @@ -170,54 +171,48 @@ func TestInviteWithOptionalRole(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, - "--role", - "writer", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, + "--role", "writer", } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid optional short form flag calls members API", func(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, - "-r", - "writer", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, + "-r", "writer", } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("will track analytics for CLI Command Run event", func(t *testing.T) { @@ -228,13 +223,14 @@ func TestInviteWithOptionalRole(t *testing.T) { "access-token", "base-uri", "emails", + "output", "role", }) client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -242,6 +238,7 @@ func TestInviteWithOptionalRole(t *testing.T) { "members", "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, "--role", "writer", } diff --git a/cmd/projects/create.go b/cmd/projects/create.go index cd5cd382..408984fa 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -10,6 +10,8 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" + "ldcli/internal/output" "ldcli/internal/projects" ) @@ -57,10 +59,28 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { data.Key, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) + } + + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + response, + output.SingularPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index aebf0e71..06cdeb10 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -20,12 +20,16 @@ func TestCreate(t *testing.T) { "test-name", "test-key", } + stubbedSuccessResponse := `{ + "key": "test-key", + "name": "test-name" + }` t.Run("with valid flags calls API", func(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } @@ -34,6 +38,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, } @@ -41,7 +46,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,13 +55,14 @@ func TestCreate(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } args := []string{ "projects", "create", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, } @@ -64,26 +70,22 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ ProjectsClient: &client, } args := []string{ - "projects", - "create", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-d", - `{"key": "test-key", "name": "test-name"}`, + "projects", "create", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "-d", `{"key": "test-key", "name": "test-name"}`, } _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) @@ -161,7 +163,7 @@ func TestCreate(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } diff --git a/cmd/projects/list.go b/cmd/projects/list.go index 5cd2f874..4a038995 100644 --- a/cmd/projects/list.go +++ b/cmd/projects/list.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/output" "ldcli/internal/projects" ) @@ -33,18 +34,28 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.BaseURIFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputMultiple( viper.GetString(cliflags.OutputFlag), - output.NewMultipleOutputterFn(response), + response, + output.MultiplePlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } - fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/projects/list_test.go b/cmd/projects/list_test.go index 652aad48..d0ec5381 100644 --- a/cmd/projects/list_test.go +++ b/cmd/projects/list_test.go @@ -74,7 +74,7 @@ func TestList(t *testing.T) { client := projects.MockClient{} client. On("List", mockArgs...). - Return([]byte(`{}`), errors.NewError("an error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ ProjectsClient: &client, } @@ -86,7 +86,7 @@ func TestList(t *testing.T) { _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) - require.EqualError(t, err, "an error") + require.EqualError(t, err, "An error") }) t.Run("with missing required flags is an error", func(t *testing.T) { @@ -143,7 +143,7 @@ func TestList(t *testing.T) { client := projects.MockClient{} client. On("List", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go deleted file mode 100644 index 97e24505..00000000 --- a/internal/output/multiple_outputter.go +++ /dev/null @@ -1,49 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" -) - -var multiplePlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("* %s (%s)", r.Name, r.Key) -} - -// TODO: rename this to be "cleaner"? -- NewMultipleOutput() -func NewMultipleOutputterFn(input []byte) multipleOutputterFn { - return multipleOutputterFn{ - input: input, - } -} - -type multipleOutputterFn struct { - input []byte -} - -func (o multipleOutputterFn) New() (Outputter, error) { - var r resources - err := json.Unmarshal(o.input, &r) - if err != nil { - return MultipleOutputter{}, err - } - - return MultipleOutputter{ - outputFn: multiplePlaintextOutputFn, - resources: r, - resourceJSON: o.input, - }, nil -} - -type MultipleOutputter struct { - outputFn PlaintextOutputFn - resources resources - resourceJSON []byte -} - -func (o MultipleOutputter) JSON() string { - return string(o.resourceJSON) -} - -func (o MultipleOutputter) String() string { - return formatColl(o.resources.Items, o.outputFn) -} diff --git a/internal/output/multiple_outputter_test.go b/internal/output/multiple_outputter_test.go deleted file mode 100644 index b22f449e..00000000 --- a/internal/output/multiple_outputter_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package output_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "ldcli/internal/output" -) - -func TestMultipleOutputter_JSON(t *testing.T) { - input := []byte(`{ - "items": [ - { - "key": "test-key1", - "name": "test-name1", - "other": "another-value2" - }, - { - "key": "test-key2", - "name": "test-name2", - "other": "another-value2" - } - ] - }`) - output, err := output.CmdOutput( - "json", - output.NewMultipleOutputterFn(input), - ) - - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) -} - -func TestMultipleOutputter_String(t *testing.T) { - input := []byte(`{ - "items": [ - { - "key": "test-key1", - "name": "test-name1", - "other": "another-value2" - }, - { - "key": "test-key2", - "name": "test-name2", - "other": "another-value2" - } - ] - }`) - expected := "* test-name1 (test-key1)\n* test-name2 (test-key2)" - output, err := output.CmdOutput( - "plaintext", - output.NewMultipleOutputterFn(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -} diff --git a/internal/output/output.go b/internal/output/output.go index eaea59e4..4d9e188e 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -1,7 +1,7 @@ package output import ( - "strings" + "encoding/json" "ldcli/internal/errors" ) @@ -27,10 +27,8 @@ type PlaintextOutputFn func(resource) string // resource is the subset of data we need to display a command's plain text response for a single // resource. -type resource struct { - Key string `json:"key"` - Name string `json:"name"` -} +// We're trading off type safety for easy of use instead of defining a type for each expected resource. +type resource map[string]interface{} // resources is the subset of data we need to display a command's plain text response for a list // of resources. @@ -38,13 +36,49 @@ type resources struct { Items []resource `json:"items"` } -// CmdOutput returns a command's response as a string formatted based on the user's requested type. -func CmdOutput(outputKind string, outputter OutputterFn) (string, error) { - o, err := outputter.New() +// resourcesBare is for responses that return a list of resources at the top level of the response, +// not as a value of an "items" property. +type resourcesBare []resource + +// CmdOutputSingular builds a command response based on the flag the user provided and the shape of +// the input. The expected shape is a single JSON object. +func CmdOutputSingular(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { + var r resource + err := json.Unmarshal(input, &r) if err != nil { return "", err } + return outputFromKind(outputKind, SingularOutputter{ + outputFn: fn, + resource: r, + resourceJSON: input, + }) +} + +// CmdOutputMultiple builds a command response based on the flag the user provided and the shape of +// the input. The expected shape is a list of JSON objects. +func CmdOutputMultiple(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { + var r resources + err := json.Unmarshal(input, &r) + if err != nil { + // sometimes a response doesn't include each item in an "items" property + var rr resourcesBare + err := json.Unmarshal(input, &rr) + if err != nil { + return "", err + } + r.Items = rr + } + + return outputFromKind(outputKind, MultipleOutputter{ + outputFn: fn, + resources: r, + resourceJSON: input, + }) +} + +func outputFromKind(outputKind string, o Outputter) (string, error) { switch outputKind { case "json": return o.JSON(), nil @@ -54,14 +88,3 @@ func CmdOutput(outputKind string, outputter OutputterFn) (string, error) { return "", ErrInvalidOutputKind } - -// FormatColl applies a formatting function to every element in the collection and returns it as a -// string. -func formatColl[T any](coll []T, formatFn func(T) string) string { - lst := make([]string, 0, len(coll)) - for _, c := range coll { - lst = append(lst, formatFn(c)) - } - - return strings.Join(lst, "\n") -} diff --git a/internal/output/output_test.go b/internal/output/output_test.go new file mode 100644 index 00000000..5a16069f --- /dev/null +++ b/internal/output/output_test.go @@ -0,0 +1,98 @@ +package output_test + +import ( + "ldcli/internal/output" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCmdOutputResource(t *testing.T) { + tests := map[string]struct { + expected string + fn output.PlaintextOutputFn + input string + }{ + "with config file data": { + expected: "key: value\nkey2: value2", + fn: output.ConfigPlaintextOutputFn, + input: `{"key": "value", "key2": "value2"}`, + }, + "with an error with a code and message": { + expected: "test-message (code: test-code)", + fn: output.ErrorPlaintextOutputFn, + input: `{"code": "test-code", "message": "test-message"}`, + }, + "with an error with only a code": { + expected: "an error occurred (code: test-code)", + fn: output.ErrorPlaintextOutputFn, + input: `{"code": "test-code", "message": ""}`, + }, + "with an error with only a message": { + expected: "test-message", + fn: output.ErrorPlaintextOutputFn, + input: `{"message": "test-message"}`, + }, + "with an error without a code or message": { + expected: "unknown error occurred", + fn: output.ErrorPlaintextOutputFn, + input: `{"message": ""}`, + }, + "with an error without a response body": { + expected: "unknown error occurred", + fn: output.ErrorPlaintextOutputFn, + input: `{}`, + }, + "with a singular resource": { + expected: "test-name (test-key)", + fn: output.SingularPlaintextOutputFn, + input: `{"key": "test-key", "name": "test-name"}`, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + output, err := output.CmdOutputSingular( + "plaintext", + []byte(tt.input), + tt.fn, + ) + + require.NoError(t, err) + assert.Equal(t, tt.expected, output) + }) + } +} + +func TestCmdOutputResources(t *testing.T) { + tests := map[string]struct { + expected string + fn output.PlaintextOutputFn + input string + }{ + "with multiple emails not as items property": { + expected: "* test-email1 (test-id1)\n* test-email2 (test-id2)", + fn: output.MultipleEmailPlaintextOutputFn, + input: `[{"_id": "test-id1", "email": "test-email1"}, {"_id": "test-id2", "email": "test-email2"}]`, + }, + "with multiple items": { + expected: "* test-name1 (test-key1)\n* test-name2 (test-key2)", + fn: output.MultiplePlaintextOutputFn, + input: `{"items": [{"key": "test-key1", "name": "test-name1"}, {"key": "test-key2", "name": "test-name2"}]}`, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + output, err := output.CmdOutputMultiple( + "plaintext", + []byte(tt.input), + tt.fn, + ) + + require.NoError(t, err) + assert.Equal(t, tt.expected, output) + }) + } +} diff --git a/internal/output/outputters.go b/internal/output/outputters.go new file mode 100644 index 00000000..a45e0467 --- /dev/null +++ b/internal/output/outputters.go @@ -0,0 +1,42 @@ +package output + +import "strings" + +type MultipleOutputter struct { + outputFn PlaintextOutputFn + resources resources + resourceJSON []byte +} + +func (o MultipleOutputter) JSON() string { + return string(o.resourceJSON) +} + +func (o MultipleOutputter) String() string { + return formatColl(o.resources.Items, o.outputFn) +} + +type SingularOutputter struct { + outputFn PlaintextOutputFn + resource resource + resourceJSON []byte +} + +func (o SingularOutputter) JSON() string { + return string(o.resourceJSON) +} + +func (o SingularOutputter) String() string { + return formatColl([]resource{o.resource}, o.outputFn) +} + +// formatColl applies a formatting function to every element in the collection and returns it as a +// string. +func formatColl[T any](coll []T, formatFn func(T) string) string { + lst := make([]string, 0, len(coll)) + for _, c := range coll { + lst = append(lst, formatFn(c)) + } + + return strings.Join(lst, "\n") +} diff --git a/internal/output/plaintext_fns.go b/internal/output/plaintext_fns.go new file mode 100644 index 00000000..af1c8232 --- /dev/null +++ b/internal/output/plaintext_fns.go @@ -0,0 +1,56 @@ +package output + +import ( + "fmt" + "sort" + "strings" +) + +// ConfigPlaintextOutputFn converts the resource to plain text specifically for data from the +// config file. +var ConfigPlaintextOutputFn = func(r resource) string { + keys := make([]string, 0) + for k := range r { + keys = append(keys, k) + } + sort.Strings(keys) + + lst := make([]string, 0) + for _, k := range keys { + lst = append(lst, fmt.Sprintf("%s: %s", k, r[k])) + } + + return strings.Join(lst, "\n") +} + +// ErrorPlaintextOutputFn converts the resource to plain text specifically for data from the +// error file. +// An error response could have a code and message or just a message. It's also possible that +// there isn't either property. +var ErrorPlaintextOutputFn = func(r resource) string { + switch { + case r["code"] == nil && (r["message"] == "" || r["message"] == nil): + return "unknown error occurred" + case r["code"] == nil: + return r["message"].(string) + case r["message"] == "": + return fmt.Sprintf("an error occurred (code: %s)", r["code"]) + default: + return fmt.Sprintf("%s (code: %s)", r["message"], r["code"]) + } +} + +// MultipleEmailPlaintextOutputFn converts the resource to plain text specifically for member data. +var MultipleEmailPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("* %s (%s)", r["email"], r["_id"]) +} + +// MultiplePlaintextOutputFn converts the resource to plain text based on its name and key in a list. +var MultiplePlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("* %s (%s)", r["name"], r["key"]) +} + +// SingularPlaintextOutputFn converts the resource to plain text based on its name and key. +var SingularPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("%s (%s)", r["name"], r["key"]) +} diff --git a/internal/output/singular_outputter.go b/internal/output/singular_outputter.go deleted file mode 100644 index 4c953c55..00000000 --- a/internal/output/singular_outputter.go +++ /dev/null @@ -1,49 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" -) - -var singularPlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("%s (%s)", r.Name, r.Key) -} - -// TODO: rename this to be "cleaner"? -- NewSingularOutput() -func NewSingularOutputterFn(input []byte) singularOutputterFn { - return singularOutputterFn{ - input: input, - } -} - -type singularOutputterFn struct { - input []byte -} - -func (o singularOutputterFn) New() (Outputter, error) { - var r resource - err := json.Unmarshal(o.input, &r) - if err != nil { - return SingularOutputter{}, err - } - - return SingularOutputter{ - outputFn: singularPlaintextOutputFn, - resource: r, - resourceJSON: o.input, - }, nil -} - -type SingularOutputter struct { - outputFn PlaintextOutputFn - resource resource - resourceJSON []byte -} - -func (o SingularOutputter) JSON() string { - return string(o.resourceJSON) -} - -func (o SingularOutputter) String() string { - return formatColl([]resource{o.resource}, o.outputFn) -} diff --git a/internal/output/singular_outputter_test.go b/internal/output/singular_outputter_test.go deleted file mode 100644 index 81be51a1..00000000 --- a/internal/output/singular_outputter_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package output_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "ldcli/internal/output" -) - -func TestSingularOutputter_JSON(t *testing.T) { - input := []byte(`{ - "key": "test-key", - "name": "test-name", - "other": "another-value" - }`) - output, err := output.CmdOutput( - "json", - output.NewSingularOutputterFn(input), - ) - - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) -} - -func TestSingularOutputter_String(t *testing.T) { - input := []byte(`{ - "key": "test-key", - "name": "test-name", - "other": "another-value" - }`) - expected := "test-name (test-key)" - output, err := output.CmdOutput( - "plaintext", - output.NewSingularOutputterFn(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -}