Skip to content

Commit 11f2016

Browse files
committed
fix table formatter for nested structs
1 parent a1457b5 commit 11f2016

File tree

4 files changed

+37
-24
lines changed

4 files changed

+37
-24
lines changed

cli/cliui/output.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func TableFormat(out any, defaultColumns []string) OutputFormat {
106106
}
107107

108108
// Get the list of table column headers.
109-
headers, defaultSort, err := typeToTableHeaders(v.Type().Elem())
109+
headers, defaultSort, err := typeToTableHeaders(v.Type().Elem(), true)
110110
if err != nil {
111111
panic("parse table headers: " + err.Error())
112112
}

cli/cliui/table.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
7070
}
7171

7272
// Get the list of table column headers.
73-
headersRaw, defaultSort, err := typeToTableHeaders(v.Type().Elem())
73+
headersRaw, defaultSort, err := typeToTableHeaders(v.Type().Elem(), true)
7474
if err != nil {
7575
return "", xerrors.Errorf("get table headers recursively for type %q: %w", v.Type().Elem().String(), err)
7676
}
@@ -230,7 +230,10 @@ func isStructOrStructPointer(t reflect.Type) bool {
230230
// typeToTableHeaders converts a type to a slice of column names. If the given
231231
// type is invalid (not a struct or a pointer to a struct, has invalid table
232232
// tags, etc.), an error is returned.
233-
func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
233+
//
234+
// requireDefault is only needed for the root call. This is recursive, so nested
235+
// structs do not need the default sort name.
236+
func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string, error) {
234237
if !isStructOrStructPointer(t) {
235238
return nil, "", xerrors.Errorf("typeToTableHeaders called with a non-struct or a non-pointer-to-a-struct type")
236239
}
@@ -246,6 +249,12 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
246249
if err != nil {
247250
return nil, "", xerrors.Errorf("parse struct tags for field %q in type %q: %w", field.Name, t.String(), err)
248251
}
252+
253+
if name == "" && (recursive && skip) {
254+
return nil, "", xerrors.Errorf("a name is required for the field %q. "+
255+
"recursive_line will ensure this is never shown to the user, but is still needed", field.Name)
256+
}
257+
// If recurse and skip is set, the name is intentionally empty.
249258
if name == "" {
250259
continue
251260
}
@@ -262,7 +271,7 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
262271
return nil, "", xerrors.Errorf("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct", field.Name, t.String())
263272
}
264273

265-
childNames, _, err := typeToTableHeaders(fieldType)
274+
childNames, defaultSort, err := typeToTableHeaders(fieldType, false)
266275
if err != nil {
267276
return nil, "", xerrors.Errorf("get child field header names for field %q in type %q: %w", field.Name, fieldType.String(), err)
268277
}
@@ -273,13 +282,16 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
273282
}
274283
headers = append(headers, fullName)
275284
}
285+
if defaultSortName == "" {
286+
defaultSortName = defaultSort
287+
}
276288
continue
277289
}
278290

279291
headers = append(headers, name)
280292
}
281293

282-
if defaultSortName == "" {
294+
if defaultSortName == "" && requireDefault {
283295
return nil, "", xerrors.Errorf("no field marked as default_sort in type %q", t.String())
284296
}
285297

codersdk/users.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ type MinimalUser struct {
4747
// organizational memberships. Fetching that is more expensive, and not usually
4848
// required by the frontend.
4949
type ReducedUser struct {
50-
MinimalUser
51-
Name string `json:"name"`
52-
Email string `json:"email" validate:"required" table:"email" format:"email"`
53-
CreatedAt time.Time `json:"created_at" validate:"required" table:"created at" format:"date-time"`
54-
LastSeenAt time.Time `json:"last_seen_at" format:"date-time"`
50+
MinimalUser `table:"m,recursive_inline"`
51+
Name string `json:"name"`
52+
Email string `json:"email" validate:"required" table:"email" format:"email"`
53+
CreatedAt time.Time `json:"created_at" validate:"required" table:"created at" format:"date-time"`
54+
LastSeenAt time.Time `json:"last_seen_at" format:"date-time"`
5555

5656
Status UserStatus `json:"status" table:"status" enums:"active,suspended"`
5757
LoginType LoginType `json:"login_type"`
@@ -60,7 +60,7 @@ type ReducedUser struct {
6060

6161
// User represents a user in Coder.
6262
type User struct {
63-
ReducedUser
63+
ReducedUser `table:"r,recursive_inline"`
6464

6565
OrganizationIDs []uuid.UUID `json:"organization_ids" format:"uuid"`
6666
Roles []Role `json:"roles"`

site/src/api/typesGenerated.ts

Lines changed: 14 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)