Skip to content

Commit aab400c

Browse files
authored
feat: Add support for json omitempty and embedded structs in apitypings (#1318)
* feat: Add support for json omitempty to apitypings * feat: Express embedded structs via extends in TypeScript * Handle unembedding via json field in apitypings * Add scripts/apitypings/main.go to Makefile
1 parent 13fc406 commit aab400c

File tree

4 files changed

+43
-20
lines changed

4 files changed

+43
-20
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ site/out/index.html: $(shell find ./site -not -path './site/node_modules/*' -typ
8383
# Restores GITKEEP files!
8484
git checkout HEAD site/out
8585

86-
site/src/api/typesGenerated.ts: $(shell find codersdk -type f -name '*.go')
86+
site/src/api/typesGenerated.ts: scripts/apitypings/main.go $(shell find codersdk -type f -name '*.go')
8787
go run scripts/apitypings/main.go > site/src/api/typesGenerated.ts
8888
cd site && yarn run format:types
8989

codersdk/pagination.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ type Pagination struct {
1414
// Offset for better performance. To use it as an alternative,
1515
// set AfterID to the last UUID returned by the previous
1616
// request.
17-
AfterID uuid.UUID `json:"after_id"`
17+
AfterID uuid.UUID `json:"after_id,omitempty"`
1818
// Limit sets the maximum number of users to be returned
1919
// in a single page. If the limit is <= 0, there is no limit
2020
// and all users are returned.
21-
Limit int `json:"limit"`
21+
Limit int `json:"limit,omitempty"`
2222
// Offset is used to indicate which page to return. An offset of 0
2323
// returns the first 'limit' number of users.
2424
// To get the next page, use offset=<limit>*<page_number>.
2525
// Offset is 0 indexed, so the first record sits at offset 0.
26-
Offset int `json:"offset"`
26+
Offset int `json:"offset,omitempty"`
2727
}
2828

2929
// asRequestOption returns a function that can be used in (*Client).request.

scripts/apitypings/main.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,31 @@ func (g *Generator) posLine(obj types.Object) string {
237237
func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, error) {
238238
var s strings.Builder
239239
_, _ = s.WriteString(g.posLine(obj))
240+
_, _ = s.WriteString(fmt.Sprintf("export interface %s ", obj.Name()))
240241

241-
_, _ = s.WriteString(fmt.Sprintf("export interface %s {\n", obj.Name()))
242+
// Handle named embedded structs in the codersdk package via extension.
243+
var extends []string
244+
extendedFields := make(map[int]bool)
245+
for i := 0; i < st.NumFields(); i++ {
246+
field := st.Field(i)
247+
tag := reflect.StructTag(st.Tag(i))
248+
// Adding a json struct tag causes the json package to consider
249+
// the field unembedded.
250+
if field.Embedded() && tag.Get("json") == "" && field.Pkg().Name() == "codersdk" {
251+
extendedFields[i] = true
252+
extends = append(extends, field.Name())
253+
}
254+
}
255+
if len(extends) > 0 {
256+
_, _ = s.WriteString(fmt.Sprintf("extends %s ", strings.Join(extends, ", ")))
257+
}
258+
259+
_, _ = s.WriteString("{\n")
242260
// For each field in the struct, we print 1 line of the typescript interface
243261
for i := 0; i < st.NumFields(); i++ {
262+
if extendedFields[i] {
263+
continue
264+
}
244265
field := st.Field(i)
245266
tag := reflect.StructTag(st.Tag(i))
246267

@@ -251,6 +272,10 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
251272
if jsonName == "" {
252273
jsonName = field.Name()
253274
}
275+
jsonOptional := false
276+
if len(arr) > 1 && arr[1] == "omitempty" {
277+
jsonOptional = true
278+
}
254279

255280
var tsType TypescriptType
256281
// If a `typescript:"string"` exists, we take this, and do not try to infer.
@@ -273,7 +298,7 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
273298
_, _ = s.WriteRune('\n')
274299
}
275300
optional := ""
276-
if tsType.Optional {
301+
if jsonOptional || tsType.Optional {
277302
optional = "?"
278303
}
279304
_, _ = s.WriteString(fmt.Sprintf("%sreadonly %s%s: %s\n", indent, jsonName, optional, tsType.ValueType))
@@ -322,7 +347,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
322347
return TypescriptType{
323348
ValueType: "any",
324349
AboveTypeLine: fmt.Sprintf("%s\n%s",
325-
indentedComment("Embedded struct, please fix by naming it"),
350+
indentedComment("Embedded anonymous struct, please fix by naming it"),
326351
indentedComment("eslint-disable-next-line @typescript-eslint/no-explicit-any"),
327352
),
328353
}, nil

site/src/api/typesGenerated.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export interface CreateWorkspaceBuildRequest {
9191
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
9292
readonly transition: string
9393
readonly dry_run: boolean
94-
readonly state: string
94+
readonly state?: string
9595
}
9696

9797
// From codersdk/organizations.go:52:6
@@ -149,9 +149,9 @@ export interface OrganizationMember {
149149

150150
// From codersdk/pagination.go:11:6
151151
export interface Pagination {
152-
readonly after_id: string
153-
readonly limit: number
154-
readonly offset: number
152+
readonly after_id?: string
153+
readonly limit?: number
154+
readonly offset?: number
155155
}
156156

157157
// From codersdk/parameters.go:26:6
@@ -185,7 +185,7 @@ export interface ProvisionerJob {
185185
readonly created_at: string
186186
readonly started_at?: string
187187
readonly completed_at?: string
188-
readonly error: string
188+
readonly error?: string
189189
readonly status: ProvisionerJobStatus
190190
readonly worker_id?: string
191191
}
@@ -264,9 +264,8 @@ export interface TemplateVersionParameterSchema {
264264
}
265265

266266
// From codersdk/templates.go:74:6
267-
export interface TemplateVersionsByTemplateRequest {
267+
export interface TemplateVersionsByTemplateRequest extends Pagination {
268268
readonly template_id: string
269-
readonly Pagination: Pagination
270269
}
271270

272271
// From codersdk/templates.go:28:6
@@ -323,10 +322,9 @@ export interface UserRoles {
323322
}
324323

325324
// From codersdk/users.go:23:6
326-
export interface UsersRequest {
325+
export interface UsersRequest extends Pagination {
327326
readonly search: string
328327
readonly status: string
329-
readonly Pagination: Pagination
330328
}
331329

332330
// From codersdk/workspaces.go:18:6
@@ -355,12 +353,12 @@ export interface WorkspaceAgent {
355353
readonly status: WorkspaceAgentStatus
356354
readonly name: string
357355
readonly resource_id: string
358-
readonly instance_id: string
356+
readonly instance_id?: string
359357
readonly architecture: string
360358
readonly environment_variables: Record<string, string>
361359
readonly operating_system: string
362-
readonly startup_script: string
363-
readonly directory: string
360+
readonly startup_script?: string
361+
readonly directory?: string
364362
}
365363

366364
// From codersdk/workspaceagents.go:47:6
@@ -415,7 +413,7 @@ export interface WorkspaceResource {
415413
readonly workspace_transition: string
416414
readonly type: string
417415
readonly name: string
418-
readonly agents: WorkspaceAgent[]
416+
readonly agents?: WorkspaceAgent[]
419417
}
420418

421419
// From codersdk/parameters.go:16:6

0 commit comments

Comments
 (0)