From 7fc4a54a58960ff4563273a6c79cf0d556aa487d Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 15:50:47 -0700 Subject: [PATCH 01/10] Add plain text error handling --- cmd/flags/get.go | 11 +++++++- cmd/flags/get_test.go | 4 +-- internal/output/error_outputter.go | 37 +++++++++++++++++++++++++ internal/output/error_outputter_test.go | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 internal/output/error_outputter.go create mode 100644 internal/output/error_outputter_test.go diff --git a/cmd/flags/get.go b/cmd/flags/get.go index ab1aef78..d3ef4df6 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -71,7 +72,15 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.EnvironmentFlag), ) if err != nil { - return err + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewErrorOutput([]byte(err.Error())), + ) + if err != nil { + return err + } + + return errors.NewError(output) } output, err := output.CmdOutput( diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index 11527792..6cda54af 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -73,7 +73,7 @@ func TestGet(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, } @@ -89,7 +89,7 @@ func TestGet(t *testing.T) { _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) - require.EqualError(t, err, "An error") + require.EqualError(t, err, `{"message": "An error"}`) }) t.Run("with missing required flags is an error", func(t *testing.T) { diff --git a/internal/output/error_outputter.go b/internal/output/error_outputter.go new file mode 100644 index 00000000..33961181 --- /dev/null +++ b/internal/output/error_outputter.go @@ -0,0 +1,37 @@ +package output + +import ( + "encoding/json" + "fmt" +) + +// errorPlaintextOutputFn converts the resource to plain text specifically for data from the +// error file. +var errorPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("%s (code: %s)", r["message"], r["code"]) +} + +type errorOutputterFn struct { + input []byte +} + +// New unmarshals a single error resource and wires up a particular plain text output function. +func (o errorOutputterFn) New() (Outputter, error) { + var r resource + err := json.Unmarshal(o.input, &r) + if err != nil { + return SingularOutputter{}, err + } + + return SingularOutputter{ + outputFn: errorPlaintextOutputFn, + resource: r, + resourceJSON: o.input, + }, nil +} + +func NewErrorOutput(input []byte) errorOutputterFn { + return errorOutputterFn{ + input: input, + } +} diff --git a/internal/output/error_outputter_test.go b/internal/output/error_outputter_test.go new file mode 100644 index 00000000..31c246ef --- /dev/null +++ b/internal/output/error_outputter_test.go @@ -0,0 +1 @@ +package output_test From 313bf1fd3d3a4250e6fa6ea1f857c3606e1f6fa4 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 08:23:35 -0700 Subject: [PATCH 02/10] Output flag errors as plain text or JSON --- cmd/flags/get.go | 10 +++--- internal/output/error_outputter.go | 6 ++-- internal/output/multiple_outputter.go | 14 ++++++++ internal/output/output.go | 49 +++++++++++++++++++++++++++ internal/output/singular_outputter.go | 20 +++++++++-- 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/cmd/flags/get.go b/cmd/flags/get.go index d3ef4df6..c8c05342 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -72,9 +72,10 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.EnvironmentFlag), ) if err != nil { - output, err := output.CmdOutput( + output, err := output.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewErrorOutput([]byte(err.Error())), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, ) if err != nil { return err @@ -83,9 +84,10 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { return err diff --git a/internal/output/error_outputter.go b/internal/output/error_outputter.go index 33961181..d58566f6 100644 --- a/internal/output/error_outputter.go +++ b/internal/output/error_outputter.go @@ -5,9 +5,9 @@ import ( "fmt" ) -// errorPlaintextOutputFn converts the resource to plain text specifically for data from the +// ErrorPlaintextOutputFn converts the resource to plain text specifically for data from the // error file. -var errorPlaintextOutputFn = func(r resource) string { +var ErrorPlaintextOutputFn = func(r resource) string { return fmt.Sprintf("%s (code: %s)", r["message"], r["code"]) } @@ -24,7 +24,7 @@ func (o errorOutputterFn) New() (Outputter, error) { } return SingularOutputter{ - outputFn: errorPlaintextOutputFn, + outputFn: ErrorPlaintextOutputFn, resource: r, resourceJSON: o.input, }, nil diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go index 0041d834..7f7d5525 100644 --- a/internal/output/multiple_outputter.go +++ b/internal/output/multiple_outputter.go @@ -48,3 +48,17 @@ func (o MultipleOutputter) JSON() string { func (o MultipleOutputter) String() string { return formatColl(o.resources.Items, o.outputFn) } + +type MultipleOutputter2 struct { + outputFn PlaintextOutputFn2 + resources resources + resourceJSON []byte +} + +func (o MultipleOutputter2) JSON() string { + return string(o.resourceJSON) +} + +func (o MultipleOutputter2) String() string { + return formatColl(o.resources.Items, o.outputFn) +} diff --git a/internal/output/output.go b/internal/output/output.go index 81482f5c..c2ad0999 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -1,6 +1,7 @@ package output import ( + "encoding/json" "strings" "ldcli/internal/errors" @@ -25,6 +26,8 @@ type OutputterFn interface { // PlaintextOutputFn represents the various ways to output a resource or resources. type PlaintextOutputFn[T any] func(t T) string +type PlaintextOutputFn2 func(resource) string + // resource is the subset of data we need to display a command's plain text response for a single // resource. // We're trading off type safety for easy of use instead of defining a type for each expected resource. @@ -36,6 +39,52 @@ type resources struct { Items []resource `json:"items"` } +// TODO: replace CmdOutput with this +func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn2) (string, error) { + var r resource + err := json.Unmarshal(input, &r) + if err != nil { + return "", err + } + + o := SingularOutputter2{ + outputFn: fn, + resource: r, + resourceJSON: input, + } + + switch outputKind { + case "json": + return o.JSON(), nil + case "plaintext": + return o.String(), nil + } + + return "", ErrInvalidOutputKind +} +func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn2) (string, error) { + var r resources + err := json.Unmarshal(input, &r) + if err != nil { + return "", err + } + + o := MultipleOutputter2{ + outputFn: fn, + resources: r, + resourceJSON: input, + } + + switch outputKind { + case "json": + return o.JSON(), nil + case "plaintext": + return o.String(), nil + } + + return "", ErrInvalidOutputKind +} + // 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() diff --git a/internal/output/singular_outputter.go b/internal/output/singular_outputter.go index 3f8f3c78..c037a25e 100644 --- a/internal/output/singular_outputter.go +++ b/internal/output/singular_outputter.go @@ -5,8 +5,8 @@ import ( "fmt" ) -// singularPlaintextOutputFn converts the resource to plain text based on its name and key. -var singularPlaintextOutputFn = func(r resource) string { +// 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"]) } @@ -23,7 +23,7 @@ func (o singularOutputterFn) New() (Outputter, error) { } return SingularOutputter{ - outputFn: singularPlaintextOutputFn, + outputFn: SingularPlaintextOutputFn, resource: r, resourceJSON: o.input, }, nil @@ -48,3 +48,17 @@ func (o SingularOutputter) JSON() string { func (o SingularOutputter) String() string { return formatColl([]resource{o.resource}, o.outputFn) } + +type SingularOutputter2 struct { + outputFn PlaintextOutputFn2 + resource resource + resourceJSON []byte +} + +func (o SingularOutputter2) JSON() string { + return string(o.resourceJSON) +} + +func (o SingularOutputter2) String() string { + return formatColl([]resource{o.resource}, o.outputFn) +} From 83745de1667b799579c3776e95e92c194d90ece9 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 09:44:45 -0700 Subject: [PATCH 03/10] Remove unused code --- cmd/flags/get.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/flags/get.go b/cmd/flags/get.go index c8c05342..c4d6e826 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -58,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), @@ -78,7 +73,7 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { output.ErrorPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(output) } return errors.NewError(output) From d41e2c1fc0c2f82c92b204da2ce8e62dbe7b2798 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 10:40:45 -0700 Subject: [PATCH 04/10] Update all commands to return plaintext or JSON --- cmd/config/config.go | 5 +-- cmd/environments/get.go | 19 +++++++--- cmd/flags/create.go | 23 +++++++----- cmd/flags/get.go | 4 +-- cmd/flags/update.go | 26 ++++++++++---- cmd/members/create.go | 21 ++++++++--- cmd/members/invite.go | 19 +++++++--- cmd/projects/create.go | 19 +++++++--- cmd/projects/list.go | 19 +++++++--- internal/output/config_outputter.go | 6 ++-- internal/output/error_outputter.go | 15 +++++++- internal/output/multiple_outputter.go | 6 ++-- internal/output/multiple_outputter_test.go | 42 +++++++++++----------- internal/output/output.go | 11 +++++- 14 files changed, 167 insertions(+), 68 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index 2f62d73d..603d1de5 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -64,9 +64,10 @@ func run() func(*cobra.Command, []string) error { return err } - output, err := output.CmdOutput( + output, err := output.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewConfigOutput(configJSON), + configJSON, + output.ConfigPlaintextOutputFn, ) if err != nil { return err diff --git a/cmd/environments/get.go b/cmd/environments/get.go index 08245463..659aca0b 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,15 +64,25 @@ func runGet( viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/create.go b/cmd/flags/create.go index 9f8c60c6..aa3d7e18 100644 --- a/cmd/flags/create.go +++ b/cmd/flags/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -53,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 { @@ -72,15 +69,25 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/get.go b/cmd/flags/get.go index c4d6e826..4c9b732d 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -73,7 +73,7 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { output.ErrorPlaintextOutputFn, ) if err != nil { - return errors.NewError(output) + return errors.NewError(err.Error()) } return errors.NewError(output) @@ -85,7 +85,7 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/update.go b/cmd/flags/update.go index c61180c4..eb9cabd8 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -117,9 +118,9 @@ 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)) + // _ = 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" { @@ -142,15 +143,26 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { patch, ) if err != nil { - return err + fmt.Println(">>> err", err) + output, err := output.CmdOutputResource( + 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.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/members/create.go b/cmd/members/create.go index a1860706..d6a2bd32 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" "ldcli/internal/output" ) @@ -42,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( @@ -52,15 +53,25 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { data, ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/members/invite.go b/cmd/members/invite.go index 70c56e6e..292e1f2c 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" "ldcli/internal/output" ) @@ -61,15 +62,25 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { memberInputs, ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResources( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.MultipleEmailPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/projects/create.go b/cmd/projects/create.go index 681632a7..072abfae 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/output" "ldcli/internal/projects" ) @@ -58,15 +59,25 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { data.Key, ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResource( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutput(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/projects/list.go b/cmd/projects/list.go index 625a5c02..11fd1c72 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,15 +34,25 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.BaseURIFlag), ) if err != nil { - return err + output, err := output.CmdOutputResource( + 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.CmdOutputResources( viper.GetString(cliflags.OutputFlag), - output.NewMultipleOutput(response), + response, + output.MultiplePlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/internal/output/config_outputter.go b/internal/output/config_outputter.go index 59414667..abbdd8b8 100644 --- a/internal/output/config_outputter.go +++ b/internal/output/config_outputter.go @@ -7,9 +7,9 @@ import ( "strings" ) -// configPlaintextOutputFn converts the resource to plain text specifically for data from the +// ConfigPlaintextOutputFn converts the resource to plain text specifically for data from the // config file. -var configPlaintextOutputFn = func(r resource) string { +var ConfigPlaintextOutputFn = func(r resource) string { keys := make([]string, 0) for k := range r { keys = append(keys, k) @@ -37,7 +37,7 @@ func (o configOutputterFn) New() (Outputter, error) { } return SingularOutputter{ - outputFn: configPlaintextOutputFn, + outputFn: ConfigPlaintextOutputFn, resource: r, resourceJSON: o.input, }, nil diff --git a/internal/output/error_outputter.go b/internal/output/error_outputter.go index d58566f6..3a0c48ad 100644 --- a/internal/output/error_outputter.go +++ b/internal/output/error_outputter.go @@ -8,7 +8,16 @@ import ( // ErrorPlaintextOutputFn converts the resource to plain text specifically for data from the // error file. var ErrorPlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("%s (code: %s)", r["message"], r["code"]) + switch { + case r["code"] == nil && r["message"] == "": + 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"]) + } } type errorOutputterFn struct { @@ -35,3 +44,7 @@ func NewErrorOutput(input []byte) errorOutputterFn { input: input, } } + +var MultipleEmailPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("* %s (%s)", r["email"], r["_id"]) +} diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go index 7f7d5525..59cd6101 100644 --- a/internal/output/multiple_outputter.go +++ b/internal/output/multiple_outputter.go @@ -5,8 +5,8 @@ import ( "fmt" ) -// multiplePlaintextOutputFn converts the resource to plain text based on its name and key in a list. -var multiplePlaintextOutputFn = func(r resource) string { +// 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"]) } @@ -23,7 +23,7 @@ func (o multipleOutputterFn) New() (Outputter, error) { } return MultipleOutputter{ - outputFn: multiplePlaintextOutputFn, + outputFn: MultiplePlaintextOutputFn, resources: r, resourceJSON: o.input, }, nil diff --git a/internal/output/multiple_outputter_test.go b/internal/output/multiple_outputter_test.go index fdcc610e..013f4776 100644 --- a/internal/output/multiple_outputter_test.go +++ b/internal/output/multiple_outputter_test.go @@ -10,27 +10,29 @@ import ( ) 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.NewMultipleOutput(input), - ) + t.Run("when response has items array", func(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.NewMultipleOutput(input), + ) - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) + require.NoError(t, err) + assert.JSONEq(t, output, string(input)) + }) } func TestMultipleOutputter_String(t *testing.T) { diff --git a/internal/output/output.go b/internal/output/output.go index c2ad0999..40278f0a 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -39,6 +39,10 @@ type resources struct { Items []resource `json:"items"` } +// 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 + // TODO: replace CmdOutput with this func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn2) (string, error) { var r resource @@ -66,7 +70,12 @@ func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn2) var r resources err := json.Unmarshal(input, &r) if err != nil { - return "", err + var r2 resourcesBare + err := json.Unmarshal(input, &r2) + if err != nil { + return "", err + } + r.Items = r2 } o := MultipleOutputter2{ From 526bbecb78e7fee14c278d64266df52a1500ab73 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 11:03:16 -0700 Subject: [PATCH 05/10] Refactor outputter --- cmd/environments/get_test.go | 3 +- cmd/flags/create_test.go | 2 +- cmd/flags/get_test.go | 3 +- cmd/flags/update_test.go | 4 +- cmd/members/create_test.go | 2 +- cmd/members/invite_test.go | 3 +- cmd/projects/create_test.go | 14 +-- cmd/projects/list_test.go | 4 +- internal/output/config_outputter.go | 50 --------- internal/output/config_outputter_test.go | 59 +++++----- internal/output/error_outputter.go | 50 --------- internal/output/multiple_outputter.go | 51 +-------- internal/output/multiple_outputter_test.go | 103 ++++++++---------- internal/output/output.go | 30 +---- ...error_outputter_test.go => output_test.go} | 2 + internal/output/plaintext_fns.go | 54 +++++++++ internal/output/singular_outputter.go | 51 +-------- internal/output/singular_outputter_test.go | 63 +++++------ 18 files changed, 176 insertions(+), 372 deletions(-) delete mode 100644 internal/output/config_outputter.go delete mode 100644 internal/output/error_outputter.go rename internal/output/{error_outputter_test.go => output_test.go} (83%) create mode 100644 internal/output/plaintext_fns.go diff --git a/cmd/environments/get_test.go b/cmd/environments/get_test.go index 4c0a2dd3..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", } diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 9518b573..799ee4aa 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -72,7 +72,7 @@ func TestCreate(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, } diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index 6cda54af..ee65f641 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -81,7 +81,6 @@ 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", @@ -89,7 +88,7 @@ func TestGet(t *testing.T) { _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) - require.EqualError(t, err, `{"message": "An error"}`) + require.EqualError(t, err, "An error") }) t.Run("with missing required flags is an error", func(t *testing.T) { diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index 14937239..70a1fde3 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -80,7 +80,7 @@ func TestUpdate(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, } @@ -207,7 +207,7 @@ func TestToggle(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, } diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 0605eb46..feb2efcb 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -76,7 +76,7 @@ func TestCreate(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, } diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index 85dac256..c98197d4 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -77,7 +77,7 @@ func TestInvite(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,7 +86,6 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, } diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index 0be76c9b..06cdeb10 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -77,19 +77,15 @@ func TestCreate(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) diff --git a/cmd/projects/list_test.go b/cmd/projects/list_test.go index 5b072adf..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) { diff --git a/internal/output/config_outputter.go b/internal/output/config_outputter.go deleted file mode 100644 index abbdd8b8..00000000 --- a/internal/output/config_outputter.go +++ /dev/null @@ -1,50 +0,0 @@ -package output - -import ( - "encoding/json" - "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") -} - -type configOutputterFn struct { - input []byte -} - -// New unmarshals a single config resource and wires up a particular plain text output function. -func (o configOutputterFn) New() (Outputter, error) { - var r resource - err := json.Unmarshal(o.input, &r) - if err != nil { - return SingularOutputter{}, err - } - - return SingularOutputter{ - outputFn: ConfigPlaintextOutputFn, - resource: r, - resourceJSON: o.input, - }, nil -} - -func NewConfigOutput(input []byte) configOutputterFn { - return configOutputterFn{ - input: input, - } -} diff --git a/internal/output/config_outputter_test.go b/internal/output/config_outputter_test.go index c0b159fe..fb6f7ae4 100644 --- a/internal/output/config_outputter_test.go +++ b/internal/output/config_outputter_test.go @@ -1,39 +1,30 @@ package output_test -import ( - "testing" +// func TestConfigOutputter_JSON(t *testing.T) { +// input := []byte(`{ +// "access-token": "test-access-token", +// "base-uri": "test-base-uri" +// }`) +// output, err := output.CmdOutput( +// "json", +// output.NewConfigOutput(input), +// ) - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" +// require.NoError(t, err) +// assert.JSONEq(t, output, string(input)) +// } - "ldcli/internal/output" -) +// func TestConfigOutputter_String(t *testing.T) { +// input := []byte(`{ +// "access-token": "test-access-token", +// "base-uri": "test-base-uri" +// }`) +// expected := "access-token: test-access-token\nbase-uri: test-base-uri" +// output, err := output.CmdOutput( +// "plaintext", +// output.NewConfigOutput(input), +// ) -func TestConfigOutputter_JSON(t *testing.T) { - input := []byte(`{ - "access-token": "test-access-token", - "base-uri": "test-base-uri" - }`) - output, err := output.CmdOutput( - "json", - output.NewConfigOutput(input), - ) - - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) -} - -func TestConfigOutputter_String(t *testing.T) { - input := []byte(`{ - "access-token": "test-access-token", - "base-uri": "test-base-uri" - }`) - expected := "access-token: test-access-token\nbase-uri: test-base-uri" - output, err := output.CmdOutput( - "plaintext", - output.NewConfigOutput(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -} +// require.NoError(t, err) +// assert.Equal(t, expected, output) +// } diff --git a/internal/output/error_outputter.go b/internal/output/error_outputter.go deleted file mode 100644 index 3a0c48ad..00000000 --- a/internal/output/error_outputter.go +++ /dev/null @@ -1,50 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" -) - -// ErrorPlaintextOutputFn converts the resource to plain text specifically for data from the -// error file. -var ErrorPlaintextOutputFn = func(r resource) string { - switch { - case r["code"] == nil && r["message"] == "": - 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"]) - } -} - -type errorOutputterFn struct { - input []byte -} - -// New unmarshals a single error resource and wires up a particular plain text output function. -func (o errorOutputterFn) New() (Outputter, error) { - var r resource - err := json.Unmarshal(o.input, &r) - if err != nil { - return SingularOutputter{}, err - } - - return SingularOutputter{ - outputFn: ErrorPlaintextOutputFn, - resource: r, - resourceJSON: o.input, - }, nil -} - -func NewErrorOutput(input []byte) errorOutputterFn { - return errorOutputterFn{ - input: input, - } -} - -var MultipleEmailPlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("* %s (%s)", r["email"], r["_id"]) -} diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go index 59cd6101..68e6482e 100644 --- a/internal/output/multiple_outputter.go +++ b/internal/output/multiple_outputter.go @@ -1,42 +1,7 @@ package output -import ( - "encoding/json" - "fmt" -) - -// 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"]) -} - -type multipleOutputterFn struct { - input []byte -} - -// New unmarshals multiple resources and wires up a particular plain text output function. -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 -} - -func NewMultipleOutput(input []byte) multipleOutputterFn { - return multipleOutputterFn{ - input: input, - } -} - type MultipleOutputter struct { - outputFn PlaintextOutputFn[resource] + outputFn PlaintextOutputFn resources resources resourceJSON []byte } @@ -48,17 +13,3 @@ func (o MultipleOutputter) JSON() string { func (o MultipleOutputter) String() string { return formatColl(o.resources.Items, o.outputFn) } - -type MultipleOutputter2 struct { - outputFn PlaintextOutputFn2 - resources resources - resourceJSON []byte -} - -func (o MultipleOutputter2) JSON() string { - return string(o.resourceJSON) -} - -func (o MultipleOutputter2) 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 index 013f4776..9bfa838d 100644 --- a/internal/output/multiple_outputter_test.go +++ b/internal/output/multiple_outputter_test.go @@ -1,61 +1,52 @@ package output_test -import ( - "testing" +// func TestMultipleOutputter_JSON(t *testing.T) { +// t.Run("when response has items array", func(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.NewMultipleOutput(input), +// ) - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" +// require.NoError(t, err) +// assert.JSONEq(t, output, string(input)) +// }) +// } - "ldcli/internal/output" -) +// 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.NewMultipleOutput(input), +// ) -func TestMultipleOutputter_JSON(t *testing.T) { - t.Run("when response has items array", func(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.NewMultipleOutput(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.NewMultipleOutput(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -} +// require.NoError(t, err) +// assert.Equal(t, expected, output) +// } diff --git a/internal/output/output.go b/internal/output/output.go index 40278f0a..42490e50 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -24,9 +24,7 @@ type OutputterFn interface { } // PlaintextOutputFn represents the various ways to output a resource or resources. -type PlaintextOutputFn[T any] func(t T) string - -type PlaintextOutputFn2 func(resource) string +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. @@ -43,15 +41,14 @@ type resources struct { // not as a value of an "items" property. type resourcesBare []resource -// TODO: replace CmdOutput with this -func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn2) (string, error) { +func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { var r resource err := json.Unmarshal(input, &r) if err != nil { return "", err } - o := SingularOutputter2{ + o := SingularOutputter{ outputFn: fn, resource: r, resourceJSON: input, @@ -66,7 +63,7 @@ func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn2) ( return "", ErrInvalidOutputKind } -func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn2) (string, error) { +func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { var r resources err := json.Unmarshal(input, &r) if err != nil { @@ -78,7 +75,7 @@ func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn2) r.Items = r2 } - o := MultipleOutputter2{ + o := MultipleOutputter{ outputFn: fn, resources: r, resourceJSON: input, @@ -94,23 +91,6 @@ func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn2) return "", ErrInvalidOutputKind } -// 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() - if err != nil { - return "", err - } - - switch outputKind { - case "json": - return o.JSON(), nil - case "plaintext": - return o.String(), nil - } - - 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 { diff --git a/internal/output/error_outputter_test.go b/internal/output/output_test.go similarity index 83% rename from internal/output/error_outputter_test.go rename to internal/output/output_test.go index 31c246ef..66300815 100644 --- a/internal/output/error_outputter_test.go +++ b/internal/output/output_test.go @@ -1 +1,3 @@ package output_test + +// diff --git a/internal/output/plaintext_fns.go b/internal/output/plaintext_fns.go new file mode 100644 index 00000000..1879b979 --- /dev/null +++ b/internal/output/plaintext_fns.go @@ -0,0 +1,54 @@ +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. +var ErrorPlaintextOutputFn = func(r resource) string { + switch { + case r["code"] == nil && r["message"] == "": + 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"]) + } +} + +// 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"]) +} + +// ConfigPlaintextOutputFn 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"]) +} diff --git a/internal/output/singular_outputter.go b/internal/output/singular_outputter.go index c037a25e..d33588a8 100644 --- a/internal/output/singular_outputter.go +++ b/internal/output/singular_outputter.go @@ -1,42 +1,7 @@ package output -import ( - "encoding/json" - "fmt" -) - -// 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"]) -} - -type singularOutputterFn struct { - input []byte -} - -// New unmarshals a single resource and wires up a particular plain text output function. -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 -} - -func NewSingularOutput(input []byte) singularOutputterFn { - return singularOutputterFn{ - input: input, - } -} - type SingularOutputter struct { - outputFn PlaintextOutputFn[resource] + outputFn PlaintextOutputFn resource resource resourceJSON []byte } @@ -48,17 +13,3 @@ func (o SingularOutputter) JSON() string { func (o SingularOutputter) String() string { return formatColl([]resource{o.resource}, o.outputFn) } - -type SingularOutputter2 struct { - outputFn PlaintextOutputFn2 - resource resource - resourceJSON []byte -} - -func (o SingularOutputter2) JSON() string { - return string(o.resourceJSON) -} - -func (o SingularOutputter2) 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 index 6283b00c..58a7e72a 100644 --- a/internal/output/singular_outputter_test.go +++ b/internal/output/singular_outputter_test.go @@ -1,41 +1,32 @@ package output_test -import ( - "testing" +// func TestSingularOutputter_JSON(t *testing.T) { +// input := []byte(`{ +// "key": "test-key", +// "name": "test-name", +// "other": "another-value" +// }`) +// output, err := output.CmdOutput( +// "json", +// output.NewSingularOutput(input), +// ) - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" +// require.NoError(t, err) +// assert.JSONEq(t, output, string(input)) +// } - "ldcli/internal/output" -) +// 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.NewSingularOutput(input), +// ) -func TestSingularOutputter_JSON(t *testing.T) { - input := []byte(`{ - "key": "test-key", - "name": "test-name", - "other": "another-value" - }`) - output, err := output.CmdOutput( - "json", - output.NewSingularOutput(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.NewSingularOutput(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -} +// require.NoError(t, err) +// assert.Equal(t, expected, output) +// } From f8de7514418656dcce079e14f7c802eb7701eb45 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 11:30:20 -0700 Subject: [PATCH 06/10] Backfill tests --- cmd/flags/update.go | 1 - internal/output/config_outputter_test.go | 30 ------ internal/output/multiple_outputter_test.go | 52 ----------- internal/output/output_test.go | 104 ++++++++++++++++++++- internal/output/plaintext_fns.go | 14 +-- internal/output/singular_outputter_test.go | 32 ------- 6 files changed, 111 insertions(+), 122 deletions(-) delete mode 100644 internal/output/config_outputter_test.go delete mode 100644 internal/output/multiple_outputter_test.go delete mode 100644 internal/output/singular_outputter_test.go diff --git a/cmd/flags/update.go b/cmd/flags/update.go index eb9cabd8..a13f7257 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -143,7 +143,6 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { patch, ) if err != nil { - fmt.Println(">>> err", err) output, err := output.CmdOutputResource( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), diff --git a/internal/output/config_outputter_test.go b/internal/output/config_outputter_test.go deleted file mode 100644 index fb6f7ae4..00000000 --- a/internal/output/config_outputter_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package output_test - -// func TestConfigOutputter_JSON(t *testing.T) { -// input := []byte(`{ -// "access-token": "test-access-token", -// "base-uri": "test-base-uri" -// }`) -// output, err := output.CmdOutput( -// "json", -// output.NewConfigOutput(input), -// ) - -// require.NoError(t, err) -// assert.JSONEq(t, output, string(input)) -// } - -// func TestConfigOutputter_String(t *testing.T) { -// input := []byte(`{ -// "access-token": "test-access-token", -// "base-uri": "test-base-uri" -// }`) -// expected := "access-token: test-access-token\nbase-uri: test-base-uri" -// output, err := output.CmdOutput( -// "plaintext", -// output.NewConfigOutput(input), -// ) - -// require.NoError(t, err) -// assert.Equal(t, expected, output) -// } diff --git a/internal/output/multiple_outputter_test.go b/internal/output/multiple_outputter_test.go deleted file mode 100644 index 9bfa838d..00000000 --- a/internal/output/multiple_outputter_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package output_test - -// func TestMultipleOutputter_JSON(t *testing.T) { -// t.Run("when response has items array", func(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.NewMultipleOutput(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.NewMultipleOutput(input), -// ) - -// require.NoError(t, err) -// assert.Equal(t, expected, output) -// } diff --git a/internal/output/output_test.go b/internal/output/output_test.go index 66300815..9db90bd9 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -1,3 +1,105 @@ package output_test -// +import ( + "ldcli/internal/output" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// CmdOutputResource - ConfigPlaintextOutputFn +// CmdOutputResource - ErrorPlaintextOutputFn +// CmdOutputResource - SingularPlaintextOutputFn + +// CmdOutputResources - MultiplePlaintextOutputFn +// CmdOutputResources - MultipleEmailPlaintextOutputFn + +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.CmdOutputResource( + "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.CmdOutputResources( + "plaintext", + []byte(tt.input), + tt.fn, + ) + + require.NoError(t, err) + assert.Equal(t, tt.expected, output) + }) + } +} diff --git a/internal/output/plaintext_fns.go b/internal/output/plaintext_fns.go index 1879b979..af1c8232 100644 --- a/internal/output/plaintext_fns.go +++ b/internal/output/plaintext_fns.go @@ -25,9 +25,11 @@ var ConfigPlaintextOutputFn = func(r resource) string { // 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"] == "": + case r["code"] == nil && (r["message"] == "" || r["message"] == nil): return "unknown error occurred" case r["code"] == nil: return r["message"].(string) @@ -38,6 +40,11 @@ var ErrorPlaintextOutputFn = func(r resource) string { } } +// 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"]) @@ -47,8 +54,3 @@ var MultiplePlaintextOutputFn = func(r resource) string { var SingularPlaintextOutputFn = func(r resource) string { return fmt.Sprintf("%s (%s)", r["name"], r["key"]) } - -// ConfigPlaintextOutputFn 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"]) -} diff --git a/internal/output/singular_outputter_test.go b/internal/output/singular_outputter_test.go deleted file mode 100644 index 58a7e72a..00000000 --- a/internal/output/singular_outputter_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package output_test - -// func TestSingularOutputter_JSON(t *testing.T) { -// input := []byte(`{ -// "key": "test-key", -// "name": "test-name", -// "other": "another-value" -// }`) -// output, err := output.CmdOutput( -// "json", -// output.NewSingularOutput(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.NewSingularOutput(input), -// ) - -// require.NoError(t, err) -// assert.Equal(t, expected, output) -// } From 614b6f00311ee0df8b35f4d97f7594eeb5af753f Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 11:32:38 -0700 Subject: [PATCH 07/10] Reorganize --- internal/output/multiple_outputter.go | 15 ---------- internal/output/output.go | 20 ++++--------- internal/output/outputters.go | 42 +++++++++++++++++++++++++++ internal/output/singular_outputter.go | 15 ---------- 4 files changed, 47 insertions(+), 45 deletions(-) delete mode 100644 internal/output/multiple_outputter.go create mode 100644 internal/output/outputters.go delete mode 100644 internal/output/singular_outputter.go diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go deleted file mode 100644 index 68e6482e..00000000 --- a/internal/output/multiple_outputter.go +++ /dev/null @@ -1,15 +0,0 @@ -package output - -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/output.go b/internal/output/output.go index 42490e50..7128b12c 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -2,7 +2,6 @@ package output import ( "encoding/json" - "strings" "ldcli/internal/errors" ) @@ -63,16 +62,18 @@ func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn) (s return "", ErrInvalidOutputKind } + func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { var r resources err := json.Unmarshal(input, &r) if err != nil { - var r2 resourcesBare - err := json.Unmarshal(input, &r2) + // 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 = r2 + r.Items = rr } o := MultipleOutputter{ @@ -90,14 +91,3 @@ func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) ( 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/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/singular_outputter.go b/internal/output/singular_outputter.go deleted file mode 100644 index d33588a8..00000000 --- a/internal/output/singular_outputter.go +++ /dev/null @@ -1,15 +0,0 @@ -package output - -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) -} From 1764f0e06731ca3a460f57f3c22d868908fd40fc Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 11:36:09 -0700 Subject: [PATCH 08/10] Refactor --- internal/output/output.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/internal/output/output.go b/internal/output/output.go index 7128b12c..eded62c7 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -47,20 +47,11 @@ func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn) (s return "", err } - o := SingularOutputter{ + return outputFromKind(outputKind, SingularOutputter{ outputFn: fn, resource: r, resourceJSON: input, - } - - switch outputKind { - case "json": - return o.JSON(), nil - case "plaintext": - return o.String(), nil - } - - return "", ErrInvalidOutputKind + }) } func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { @@ -76,12 +67,14 @@ func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) ( r.Items = rr } - o := MultipleOutputter{ + 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 From d6b08f282b098bf4766769ff1fe343f8924a6a5a Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 11:50:37 -0700 Subject: [PATCH 09/10] Remove comments --- cmd/flags/update.go | 5 ----- internal/output/output_test.go | 7 ------- 2 files changed, 12 deletions(-) diff --git a/cmd/flags/update.go b/cmd/flags/update.go index a13f7257..e2327653 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -117,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)) diff --git a/internal/output/output_test.go b/internal/output/output_test.go index 9db90bd9..e022e685 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -8,13 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -// CmdOutputResource - ConfigPlaintextOutputFn -// CmdOutputResource - ErrorPlaintextOutputFn -// CmdOutputResource - SingularPlaintextOutputFn - -// CmdOutputResources - MultiplePlaintextOutputFn -// CmdOutputResources - MultipleEmailPlaintextOutputFn - func TestCmdOutputResource(t *testing.T) { tests := map[string]struct { expected string From 62d1f6694f72fc9cdd2c3629a8ef53bf8508c673 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 14:29:40 -0700 Subject: [PATCH 10/10] Renamed functions --- cmd/config/config.go | 2 +- cmd/environments/get.go | 4 ++-- cmd/flags/create.go | 4 ++-- cmd/flags/get.go | 4 ++-- cmd/flags/update.go | 4 ++-- cmd/members/create.go | 4 ++-- cmd/members/invite.go | 4 ++-- cmd/projects/create.go | 4 ++-- cmd/projects/list.go | 4 ++-- internal/output/output.go | 8 ++++++-- internal/output/output_test.go | 4 ++-- 11 files changed, 25 insertions(+), 21 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index 603d1de5..831401d2 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -64,7 +64,7 @@ func run() func(*cobra.Command, []string) error { return err } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), configJSON, output.ConfigPlaintextOutputFn, diff --git a/cmd/environments/get.go b/cmd/environments/get.go index 659aca0b..e169adc4 100644 --- a/cmd/environments/get.go +++ b/cmd/environments/get.go @@ -64,7 +64,7 @@ func runGet( viper.GetString(cliflags.ProjectFlag), ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -76,7 +76,7 @@ func runGet( return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/flags/create.go b/cmd/flags/create.go index aa3d7e18..e9180e1d 100644 --- a/cmd/flags/create.go +++ b/cmd/flags/create.go @@ -69,7 +69,7 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.ProjectFlag), ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -81,7 +81,7 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/flags/get.go b/cmd/flags/get.go index 4c9b732d..77912d51 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -67,7 +67,7 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.EnvironmentFlag), ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -79,7 +79,7 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/flags/update.go b/cmd/flags/update.go index e2327653..f2275cfe 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -138,7 +138,7 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { patch, ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -150,7 +150,7 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/members/create.go b/cmd/members/create.go index d6a2bd32..154c06d8 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -53,7 +53,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { data, ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -65,7 +65,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/members/invite.go b/cmd/members/invite.go index 292e1f2c..450d6412 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -62,7 +62,7 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { memberInputs, ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -74,7 +74,7 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResources( + output, err := output.CmdOutputMultiple( viper.GetString(cliflags.OutputFlag), response, output.MultipleEmailPlaintextOutputFn, diff --git a/cmd/projects/create.go b/cmd/projects/create.go index 072abfae..408984fa 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -59,7 +59,7 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { data.Key, ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -71,7 +71,7 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), response, output.SingularPlaintextOutputFn, diff --git a/cmd/projects/list.go b/cmd/projects/list.go index 11fd1c72..4a038995 100644 --- a/cmd/projects/list.go +++ b/cmd/projects/list.go @@ -34,7 +34,7 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.BaseURIFlag), ) if err != nil { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), []byte(err.Error()), output.ErrorPlaintextOutputFn, @@ -46,7 +46,7 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { return errors.NewError(output) } - output, err := output.CmdOutputResources( + output, err := output.CmdOutputMultiple( viper.GetString(cliflags.OutputFlag), response, output.MultiplePlaintextOutputFn, diff --git a/internal/output/output.go b/internal/output/output.go index eded62c7..4d9e188e 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -40,7 +40,9 @@ type resources struct { // not as a value of an "items" property. type resourcesBare []resource -func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { +// 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 { @@ -54,7 +56,9 @@ func CmdOutputResource(outputKind string, input []byte, fn PlaintextOutputFn) (s }) } -func CmdOutputResources(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { +// 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 { diff --git a/internal/output/output_test.go b/internal/output/output_test.go index e022e685..5a16069f 100644 --- a/internal/output/output_test.go +++ b/internal/output/output_test.go @@ -53,7 +53,7 @@ func TestCmdOutputResource(t *testing.T) { for name, tt := range tests { tt := tt t.Run(name, func(t *testing.T) { - output, err := output.CmdOutputResource( + output, err := output.CmdOutputSingular( "plaintext", []byte(tt.input), tt.fn, @@ -85,7 +85,7 @@ func TestCmdOutputResources(t *testing.T) { for name, tt := range tests { tt := tt t.Run(name, func(t *testing.T) { - output, err := output.CmdOutputResources( + output, err := output.CmdOutputMultiple( "plaintext", []byte(tt.input), tt.fn,