diff --git a/internal/cmd/base/list.go b/internal/cmd/base/list.go index 122c016a..d91ee573 100644 --- a/internal/cmd/base/list.go +++ b/internal/cmd/base/list.go @@ -163,6 +163,15 @@ func (lc *ListCmd[T, S]) Run(s state.State, cmd *cobra.Command, args []string) e t := output.NewTable[T](out) lc.OutputTable(t, s.Client()) + + warnings, err := t.ValidateColumns(cols) + if err != nil { + return err + } + for _, warning := range warnings { + cmd.PrintErrln("Warning:", warning) + } + if !outOpts.IsSet("noheader") { t.WriteHeader(cols) } diff --git a/internal/cmd/context/list.go b/internal/cmd/context/list.go index f5c3c591..b11bed31 100644 --- a/internal/cmd/context/list.go +++ b/internal/cmd/context/list.go @@ -43,9 +43,13 @@ func runList(s state.State, cmd *cobra.Command, _ []string) error { } tw := newListOutputTable(cmd.OutOrStdout()) - if err := tw.ValidateColumns(cols); err != nil { + warnings, err := tw.ValidateColumns(cols) + if err != nil { return err } + for _, warning := range warnings { + cmd.PrintErrln("Warning:", warning) + } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) diff --git a/internal/cmd/output/output.go b/internal/cmd/output/output.go index c09ce64b..e15817c3 100644 --- a/internal/cmd/output/output.go +++ b/internal/cmd/output/output.go @@ -174,6 +174,7 @@ func NewTable[T any](out io.Writer) *Table[T] { fieldMapping: map[string]FieldFn[T]{}, fieldAlias: map[string]string{}, allowedFields: map[string]bool{}, + deprecations: map[string]string{}, } } @@ -187,6 +188,7 @@ type Table[T any] struct { fieldMapping map[string]FieldFn[T] fieldAlias map[string]string allowedFields map[string]bool + deprecations map[string]string } // Columns returns a list of known output columns. @@ -212,6 +214,13 @@ func (o *Table[T]) AddFieldFn(field string, fn FieldFn[T]) *Table[T] { return o } +// MarkFieldAsDeprecated marks the specified field as deprecated. The message will be printed +// to stderr if the column is used. +func (o *Table[T]) MarkFieldAsDeprecated(field string, message string) *Table[T] { + o.deprecations[field] = message + return o +} + // AddAllowedFields reads all first level fieldnames of the struct and allows them to be used. func (o *Table[T]) AddAllowedFields(obj T) *Table[T] { v := reflect.ValueOf(obj) @@ -250,18 +259,21 @@ func (o *Table[T]) RemoveAllowedField(fields ...string) *Table[T] { return o } -// ValidateColumns returns an error if invalid columns are specified. -func (o *Table[T]) ValidateColumns(cols []string) error { - var invalidCols []string +// ValidateColumns returns a list of warnings for the used columns and an error if invalid columns are specified. +func (o *Table[T]) ValidateColumns(cols []string) ([]string, error) { + var warnings, invalidCols []string for _, col := range cols { + if warning, isDeprecated := o.deprecations[strings.ToLower(col)]; isDeprecated { + warnings = append(warnings, warning) + } if _, ok := o.allowedFields[strings.ToLower(col)]; !ok { invalidCols = append(invalidCols, col) } } if len(invalidCols) > 0 { - return fmt.Errorf("invalid table columns: %s", strings.Join(invalidCols, ",")) + return warnings, fmt.Errorf("invalid table columns: %s", strings.Join(invalidCols, ",")) } - return nil + return warnings, nil } // WriteHeader writes the table header. diff --git a/internal/cmd/output/output_test.go b/internal/cmd/output/output_test.go index ef35feff..a5ddb6a2 100644 --- a/internal/cmd/output/output_test.go +++ b/internal/cmd/output/output_test.go @@ -51,11 +51,18 @@ func TestTableOutput(t *testing.T) { assert.Contains(t, to.fieldMapping, "leeroy jenkins") }) + t.Run("MarkFieldAsDeprecated", func(t *testing.T) { + to.MarkFieldAsDeprecated("name", "This field is deprecated") + + assert.Contains(t, to.deprecations, "name") + }) + t.Run("ValidateColumns", func(t *testing.T) { - err := to.ValidateColumns([]string{"non-existent", "NAME"}) + warnings, err := to.ValidateColumns([]string{"non-existent", "NAME"}) require.ErrorContains(t, err, "non-existent") assert.NotContains(t, err.Error(), "name") + assert.Contains(t, warnings, "This field is deprecated") }) t.Run("WriteHeader", func(t *testing.T) { diff --git a/internal/cmd/server/list.go b/internal/cmd/server/list.go index 9e3d6c9f..1acc324e 100644 --- a/internal/cmd/server/list.go +++ b/internal/cmd/server/list.go @@ -93,7 +93,6 @@ var ListCmd = &base.ListCmd[*hcloud.Server, schema.Server]{ return server.Datacenter.Name } return "-" - }). AddFieldFn("location", func(server *hcloud.Server) string { return server.Location.Name @@ -140,7 +139,8 @@ var ListCmd = &base.ListCmd[*hcloud.Server, schema.Server]{ return "-" } return server.PlacementGroup.Name - }) + }). + MarkFieldAsDeprecated("datacenter", "The datacenter column is deprecated. Use location column instead.") }, Schema: hcloud.SchemaFromServer,