fix: generate typescript types for healthcheck pkg (#8846)

This commit is contained in:
Colin Adler
2023-08-03 16:20:46 -05:00
committed by GitHub
parent 72780c8031
commit 8ee500c59d
21 changed files with 333 additions and 204 deletions
+15 -15
View File
@@ -11248,6 +11248,9 @@ const docTemplate = `{
"$ref": "#/definitions/derp.ServerInfoMessage"
},
"round_trip_ping": {
"type": "string"
},
"round_trip_ping_ms": {
"type": "integer"
},
"stun": {
@@ -11316,7 +11319,9 @@ const docTemplate = `{
"enabled": {
"type": "boolean"
},
"error": {}
"error": {
"type": "string"
}
}
},
"healthcheck.DatabaseReport": {
@@ -11329,6 +11334,9 @@ const docTemplate = `{
"type": "boolean"
},
"latency": {
"type": "string"
},
"latency_ms": {
"type": "integer"
},
"reachable": {
@@ -11373,20 +11381,6 @@ const docTemplate = `{
}
},
"healthcheck.WebsocketReport": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"healthy": {
"type": "boolean"
},
"response": {
"$ref": "#/definitions/healthcheck.WebsocketResponse"
}
}
},
"healthcheck.WebsocketResponse": {
"type": "object",
"properties": {
"body": {
@@ -11394,6 +11388,12 @@ const docTemplate = `{
},
"code": {
"type": "integer"
},
"error": {
"type": "string"
},
"healthy": {
"type": "boolean"
}
}
},
+15 -15
View File
@@ -10232,6 +10232,9 @@
"$ref": "#/definitions/derp.ServerInfoMessage"
},
"round_trip_ping": {
"type": "string"
},
"round_trip_ping_ms": {
"type": "integer"
},
"stun": {
@@ -10300,7 +10303,9 @@
"enabled": {
"type": "boolean"
},
"error": {}
"error": {
"type": "string"
}
}
},
"healthcheck.DatabaseReport": {
@@ -10313,6 +10318,9 @@
"type": "boolean"
},
"latency": {
"type": "string"
},
"latency_ms": {
"type": "integer"
},
"reachable": {
@@ -10357,20 +10365,6 @@
}
},
"healthcheck.WebsocketReport": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"healthy": {
"type": "boolean"
},
"response": {
"$ref": "#/definitions/healthcheck.WebsocketResponse"
}
}
},
"healthcheck.WebsocketResponse": {
"type": "object",
"properties": {
"body": {
@@ -10378,6 +10372,12 @@
},
"code": {
"type": "integer"
},
"error": {
"type": "string"
},
"healthy": {
"type": "boolean"
}
}
},
+1
View File
@@ -12,6 +12,7 @@ import (
"github.com/coder/coder/coderd/util/ptr"
)
// @typescript-generate AccessURLReport
type AccessURLReport struct {
AccessURL string `json:"access_url"`
Healthy bool `json:"healthy"`
+10 -6
View File
@@ -10,11 +10,13 @@ import (
"github.com/coder/coder/coderd/database"
)
// @typescript-generate DatabaseReport
type DatabaseReport struct {
Healthy bool `json:"healthy"`
Reachable bool `json:"reachable"`
Latency time.Duration `json:"latency"`
Error *string `json:"error"`
Healthy bool `json:"healthy"`
Reachable bool `json:"reachable"`
Latency string `json:"latency"`
LatencyMs int `json:"latency_ms"`
Error *string `json:"error"`
}
type DatabaseReportOptions struct {
@@ -39,10 +41,12 @@ func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) {
slices.Sort(pings)
// Take the median ping.
r.Latency = pings[pingCount/2]
latency := pings[pingCount/2]
r.Latency = latency.String()
r.LatencyMs = int(latency.Milliseconds())
// Somewhat arbitrary, but if the latency is over 15ms, we consider it
// unhealthy.
if r.Latency < 15*time.Millisecond {
if latency < 15*time.Millisecond {
r.Healthy = true
}
r.Reachable = true
+4 -2
View File
@@ -35,7 +35,8 @@ func TestDatabase(t *testing.T) {
assert.True(t, report.Healthy)
assert.True(t, report.Reachable)
assert.Equal(t, ping, report.Latency)
assert.Equal(t, ping.String(), report.Latency)
assert.Equal(t, int(ping.Milliseconds()), report.LatencyMs)
assert.Nil(t, report.Error)
})
@@ -81,7 +82,8 @@ func TestDatabase(t *testing.T) {
assert.True(t, report.Healthy)
assert.True(t, report.Reachable)
assert.Equal(t, time.Millisecond, report.Latency)
assert.Equal(t, time.Millisecond.String(), report.Latency)
assert.Equal(t, 1, report.LatencyMs)
assert.Nil(t, report.Error)
})
}
+14 -6
View File
@@ -24,6 +24,7 @@ import (
"github.com/coder/coder/coderd/util/ptr"
)
// @typescript-generate DERPReport
type DERPReport struct {
Healthy bool `json:"healthy"`
@@ -36,6 +37,7 @@ type DERPReport struct {
Error *string `json:"error"`
}
// @typescript-generate DERPRegionReport
type DERPRegionReport struct {
mu sync.Mutex
Healthy bool `json:"healthy"`
@@ -44,6 +46,8 @@ type DERPRegionReport struct {
NodeReports []*DERPNodeReport `json:"node_reports"`
Error *string `json:"error"`
}
// @typescript-generate DERPNodeReport
type DERPNodeReport struct {
mu sync.Mutex
clientCounter int
@@ -53,7 +57,8 @@ type DERPNodeReport struct {
ServerInfo derp.ServerInfoMessage `json:"node_info"`
CanExchangeMessages bool `json:"can_exchange_messages"`
RoundTripPing time.Duration `json:"round_trip_ping"`
RoundTripPing string `json:"round_trip_ping"`
RoundTripPingMs int `json:"round_trip_ping_ms"`
UsesWebsocket bool `json:"uses_websocket"`
ClientLogs [][]string `json:"client_logs"`
ClientErrs [][]string `json:"client_errs"`
@@ -62,10 +67,11 @@ type DERPNodeReport struct {
STUN DERPStunReport `json:"stun"`
}
// @typescript-generate DERPStunReport
type DERPStunReport struct {
Enabled bool
CanSTUN bool
Error error
Error *string
}
type DERPReportOptions struct {
@@ -251,7 +257,9 @@ func (r *DERPNodeReport) doExchangeMessage(ctx context.Context) {
r.mu.Lock()
r.CanExchangeMessages = true
r.RoundTripPing = time.Since(*t)
rtt := time.Since(*t)
r.RoundTripPing = rtt.String()
r.RoundTripPingMs = int(rtt.Milliseconds())
r.mu.Unlock()
cancel()
@@ -301,20 +309,20 @@ func (r *DERPNodeReport) doSTUNTest(ctx context.Context) {
addr, port, err := r.stunAddr(ctx)
if err != nil {
r.STUN.Error = xerrors.Errorf("get stun addr: %w", err)
r.STUN.Error = convertError(xerrors.Errorf("get stun addr: %w", err))
return
}
// We only create a prober to call ProbeUDP manually.
p, err := prober.DERP(prober.New(), "", time.Second, time.Second, time.Second)
if err != nil {
r.STUN.Error = xerrors.Errorf("create prober: %w", err)
r.STUN.Error = convertError(xerrors.Errorf("create prober: %w", err))
return
}
err = p.ProbeUDP(addr, port)(ctx)
if err != nil {
r.STUN.Error = xerrors.Errorf("probe stun: %w", err)
r.STUN.Error = convertError(xerrors.Errorf("probe stun: %w", err))
return
}
+7 -7
View File
@@ -67,7 +67,7 @@ func TestDERP(t *testing.T) {
for _, node := range region.NodeReports {
assert.True(t, node.Healthy)
assert.True(t, node.CanExchangeMessages)
assert.Positive(t, node.RoundTripPing)
assert.NotEmpty(t, node.RoundTripPing)
assert.Len(t, node.ClientLogs, 2)
assert.Len(t, node.ClientLogs[0], 1)
assert.Len(t, node.ClientErrs[0], 0)
@@ -76,7 +76,7 @@ func TestDERP(t *testing.T) {
assert.False(t, node.STUN.Enabled)
assert.False(t, node.STUN.CanSTUN)
assert.NoError(t, node.STUN.Error)
assert.Nil(t, node.STUN.Error)
}
}
})
@@ -111,7 +111,7 @@ func TestDERP(t *testing.T) {
for _, node := range region.NodeReports {
assert.True(t, node.Healthy)
assert.True(t, node.CanExchangeMessages)
assert.Positive(t, node.RoundTripPing)
assert.NotEmpty(t, node.RoundTripPing)
assert.Len(t, node.ClientLogs, 2)
assert.Len(t, node.ClientLogs[0], 1)
assert.Len(t, node.ClientErrs[0], 0)
@@ -120,7 +120,7 @@ func TestDERP(t *testing.T) {
assert.True(t, node.STUN.Enabled)
assert.True(t, node.STUN.CanSTUN)
assert.NoError(t, node.STUN.Error)
assert.Nil(t, node.STUN.Error)
}
}
})
@@ -174,7 +174,7 @@ func TestDERP(t *testing.T) {
for _, node := range region.NodeReports {
assert.False(t, node.Healthy)
assert.True(t, node.CanExchangeMessages)
assert.Positive(t, node.RoundTripPing)
assert.NotEmpty(t, node.RoundTripPing)
assert.Len(t, node.ClientLogs, 2)
assert.Len(t, node.ClientLogs[0], 3)
assert.Len(t, node.ClientLogs[1], 3)
@@ -185,7 +185,7 @@ func TestDERP(t *testing.T) {
assert.False(t, node.STUN.Enabled)
assert.False(t, node.STUN.CanSTUN)
assert.NoError(t, node.STUN.Error)
assert.Nil(t, node.STUN.Error)
}
}
})
@@ -227,7 +227,7 @@ func TestDERP(t *testing.T) {
assert.True(t, node.STUN.Enabled)
assert.True(t, node.STUN.CanSTUN)
assert.NoError(t, node.STUN.Error)
assert.Nil(t, node.STUN.Error)
}
}
})
+1
View File
@@ -29,6 +29,7 @@ type Checker interface {
Database(ctx context.Context, opts *DatabaseReportOptions) DatabaseReport
}
// @typescript-generate Report
type Report struct {
// Time is the time the report was generated at.
Time time.Time `json:"time"`
+10 -15
View File
@@ -2,6 +2,7 @@ package healthcheck
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
@@ -10,8 +11,6 @@ import (
"golang.org/x/xerrors"
"nhooyr.io/websocket"
"github.com/coder/coder/coderd/httpapi"
)
type WebsocketReportOptions struct {
@@ -20,15 +19,12 @@ type WebsocketReportOptions struct {
HTTPClient *http.Client
}
// @typescript-generate WebsocketReport
type WebsocketReport struct {
Healthy bool `json:"healthy"`
Response WebsocketResponse `json:"response"`
Error *string `json:"error"`
}
type WebsocketResponse struct {
Body string `json:"body"`
Code int `json:"code"`
Healthy bool `json:"healthy"`
Body string `json:"body"`
Code int `json:"code"`
Error *string `json:"error"`
}
func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) {
@@ -60,10 +56,8 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions)
}
}
r.Response = WebsocketResponse{
Body: body,
Code: res.StatusCode,
}
r.Body = body
r.Code = res.StatusCode
}
if err != nil {
r.Error = convertError(xerrors.Errorf("websocket dial: %w", err))
@@ -115,7 +109,8 @@ func (s *WebsocketEchoServer) ServeHTTP(rw http.ResponseWriter, r *http.Request)
ctx := r.Context()
c, err := websocket.Accept(rw, r, &websocket.AcceptOptions{})
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, "unable to accept: "+err.Error())
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte(fmt.Sprint("unable to accept:", err)))
return
}
defer c.Close(websocket.StatusGoingAway, "goodbye")
+2 -2
View File
@@ -63,7 +63,7 @@ func TestWebsocket(t *testing.T) {
})
require.NotNil(t, wsReport.Error)
assert.Equal(t, wsReport.Response.Body, "test error")
assert.Equal(t, wsReport.Response.Code, http.StatusBadRequest)
assert.Equal(t, wsReport.Body, "test error")
assert.Equal(t, wsReport.Code, http.StatusBadRequest)
})
}
+11 -10
View File
@@ -51,7 +51,8 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"database": {
"error": "string",
"healthy": true,
"latency": 0,
"latency": "string",
"latency_ms": 0,
"reachable": true
},
"derp": {
@@ -118,11 +119,12 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -179,11 +181,12 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -218,12 +221,10 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"healthy": true,
"time": "string",
"websocket": {
"body": "string",
"code": 0,
"error": "string",
"healthy": true,
"response": {
"body": "string",
"code": 0
}
"healthy": true
}
}
```
+45 -54
View File
@@ -6603,11 +6603,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -6624,7 +6625,8 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| `healthy` | boolean | false | | |
| `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | |
| `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | |
| `round_trip_ping` | integer | false | | |
| `round_trip_ping` | string | false | | |
| `round_trip_ping_ms` | integer | false | | |
| `stun` | [healthcheck.DERPStunReport](#healthcheckderpstunreport) | false | | |
| `uses_websocket` | boolean | false | | |
@@ -6659,11 +6661,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -6770,11 +6773,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -6831,11 +6835,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -6886,7 +6891,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
{
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
}
```
@@ -6896,7 +6901,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| --------- | ------- | -------- | ------------ | ----------- |
| `canSTUN` | boolean | false | | |
| `enabled` | boolean | false | | |
| `error` | any | false | | |
| `error` | string | false | | |
## healthcheck.DatabaseReport
@@ -6904,19 +6909,21 @@ If the schedule is empty, the user will be updated to use the default schedule.|
{
"error": "string",
"healthy": true,
"latency": 0,
"latency": "string",
"latency_ms": 0,
"reachable": true
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ----------- | ------- | -------- | ------------ | ----------- |
| `error` | string | false | | |
| `healthy` | boolean | false | | |
| `latency` | integer | false | | |
| `reachable` | boolean | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------ | ------- | -------- | ------------ | ----------- |
| `error` | string | false | | |
| `healthy` | boolean | false | | |
| `latency` | string | false | | |
| `latency_ms` | integer | false | | |
| `reachable` | boolean | false | | |
## healthcheck.Report
@@ -6934,7 +6941,8 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"database": {
"error": "string",
"healthy": true,
"latency": 0,
"latency": "string",
"latency_ms": 0,
"reachable": true
},
"derp": {
@@ -7001,11 +7009,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -7062,11 +7071,12 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"tokenBucketBytesBurst": 0,
"tokenBucketBytesPerSecond": 0
},
"round_trip_ping": 0,
"round_trip_ping": "string",
"round_trip_ping_ms": 0,
"stun": {
"canSTUN": true,
"enabled": true,
"error": null
"error": "string"
},
"uses_websocket": true
}
@@ -7101,12 +7111,10 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"healthy": true,
"time": "string",
"websocket": {
"body": "string",
"code": 0,
"error": "string",
"healthy": true,
"response": {
"body": "string",
"code": 0
}
"healthy": true
}
}
```
@@ -7126,40 +7134,23 @@ If the schedule is empty, the user will be updated to use the default schedule.|
## healthcheck.WebsocketReport
```json
{
"error": "string",
"healthy": true,
"response": {
"body": "string",
"code": 0
}
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ---------- | -------------------------------------------------------------- | -------- | ------------ | ----------- |
| `error` | string | false | | |
| `healthy` | boolean | false | | |
| `response` | [healthcheck.WebsocketResponse](#healthcheckwebsocketresponse) | false | | |
## healthcheck.WebsocketResponse
```json
{
"body": "string",
"code": 0
"code": 0,
"error": "string",
"healthy": true
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------ | ------- | -------- | ------------ | ----------- |
| `body` | string | false | | |
| `code` | integer | false | | |
| Name | Type | Required | Restrictions | Description |
| --------- | ------- | -------- | ------------ | ----------- |
| `body` | string | false | | |
| `code` | integer | false | | |
| `error` | string | false | | |
| `healthy` | boolean | false | | |
## netcheck.Report
+1 -1
View File
@@ -356,7 +356,7 @@ require (
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/text v0.11.0
golang.org/x/time v0.3.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde // indirect
+76 -33
View File
@@ -15,6 +15,8 @@ import (
"text/template"
"github.com/fatih/structtag"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"golang.org/x/tools/go/packages"
"golang.org/x/xerrors"
@@ -23,21 +25,25 @@ import (
"github.com/coder/coder/coderd/util/slice"
)
const (
baseDir = "./codersdk"
indent = " "
var (
baseDirs = [...]string{"./codersdk", "./coderd/healthcheck"}
indent = " "
)
func main() {
ctx := context.Background()
log := slog.Make(sloghuman.Sink(os.Stderr))
output, err := Generate(baseDir)
if err != nil {
log.Fatal(ctx, err.Error())
}
_, _ = fmt.Print("// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.\n\n")
for _, baseDir := range baseDirs {
_, _ = fmt.Printf("// The code below is generated from %s.\n\n", strings.TrimPrefix(baseDir, "./"))
output, err := Generate(baseDir)
if err != nil {
log.Fatal(ctx, err.Error())
}
// Just cat the output to a file to capture it
_, _ = fmt.Println(output)
// Just cat the output to a file to capture it
_, _ = fmt.Print(output, "\n\n")
}
}
func Generate(directory string) (string, error) {
@@ -63,11 +69,6 @@ type TypescriptTypes struct {
// String just combines all the codeblocks.
func (t TypescriptTypes) String() string {
var s strings.Builder
const prelude = `
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
`
_, _ = s.WriteString(prelude)
sortedTypes := make([]string, 0, len(t.Types))
sortedEnums := make([]string, 0, len(t.Enums))
@@ -176,6 +177,7 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
Enums: make(map[string]types.Object),
EnumConsts: make(map[string][]*types.Const),
IgnoredTypes: make(map[string]struct{}),
AllowedTypes: make(map[string]struct{}),
}
// Look for comments that indicate to ignore a type for typescript generation.
@@ -196,6 +198,24 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
}
}
// This allows opt-in generation, instead of opt-out.
allowRegex := regexp.MustCompile("@typescript-generate[:]?(?P<allowed_types>.*)")
for _, file := range g.pkg.Syntax {
for _, comment := range file.Comments {
for _, line := range comment.List {
text := line.Text
matches := allowRegex.FindStringSubmatch(text)
allowed := allowRegex.SubexpIndex("allowed_types")
if len(matches) >= allowed && matches[allowed] != "" {
arr := strings.Split(matches[allowed], ",")
for _, s := range arr {
m.AllowedTypes[strings.TrimSpace(s)] = struct{}{}
}
}
}
}
}
for _, n := range g.pkg.Types.Scope().Names() {
obj := g.pkg.Types.Scope().Lookup(n)
err := g.generateOne(m, obj)
@@ -260,6 +280,15 @@ type Maps struct {
Enums map[string]types.Object
EnumConsts map[string][]*types.Const
IgnoredTypes map[string]struct{}
AllowedTypes map[string]struct{}
}
// objName prepends the package name of a type if it is outside of codersdk.
func objName(obj types.Object) string {
if pkgName := obj.Pkg().Name(); pkgName != "codersdk" {
return cases.Title(language.English).String(pkgName) + obj.Name()
}
return obj.Name()
}
func (g *Generator) generateOne(m *Maps, obj types.Object) error {
@@ -273,6 +302,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
return nil
}
// If we have allowed types, only allow those to be generated.
if _, ok := m.AllowedTypes[obj.Name()]; len(m.AllowedTypes) > 0 && !ok {
return nil
}
objName := objName(obj)
switch obj := obj.(type) {
// All named types are type declarations
case *types.TypeName:
@@ -286,13 +322,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
// Structs are obvious.
codeBlock, err := g.buildStruct(obj, underNamed)
if err != nil {
return xerrors.Errorf("generate %q: %w", obj.Name(), err)
return xerrors.Errorf("generate %q: %w", objName, err)
}
m.Structs[obj.Name()] = codeBlock
m.Structs[objName] = codeBlock
case *types.Basic:
// type <Name> string
// These are enums. Store to expand later.
m.Enums[obj.Name()] = obj
m.Enums[objName] = obj
case *types.Map, *types.Array, *types.Slice:
// Declared maps that are not structs are still valid codersdk objects.
// Handle them custom by calling 'typescriptType' directly instead of
@@ -301,7 +337,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
// These are **NOT** enums, as a map in Go would never be used for an enum.
ts, err := g.typescriptType(obj.Type().Underlying())
if err != nil {
return xerrors.Errorf("(map) generate %q: %w", obj.Name(), err)
return xerrors.Errorf("(map) generate %q: %w", objName, err)
}
var str strings.Builder
@@ -311,8 +347,8 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
_, _ = str.WriteRune('\n')
}
// Use similar output syntax to enums.
_, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", obj.Name(), ts.ValueType))
m.Structs[obj.Name()] = str.String()
_, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", objName, ts.ValueType))
m.Structs[objName] = str.String()
case *types.Interface:
// Interfaces are used as generics. Non-generic interfaces are
// not supported.
@@ -330,9 +366,9 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
block, err := g.buildUnion(obj, union)
if err != nil {
return xerrors.Errorf("generate union %q: %w", obj.Name(), err)
return xerrors.Errorf("generate union %q: %w", objName, err)
}
m.Generics[obj.Name()] = block
m.Generics[objName] = block
}
case *types.Signature:
// Ignore named functions.
@@ -353,7 +389,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
case *types.Func:
// Noop
default:
_, _ = fmt.Println(obj.Name())
_, _ = fmt.Println(objName)
}
return nil
}
@@ -361,7 +397,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
func (g *Generator) posLine(obj types.Object) string {
file := g.pkg.Fset.File(obj.Pos())
// Do not use filepath, as that changes behavior based on OS
return fmt.Sprintf("// From %s\n", path.Join("codersdk", filepath.Base(file.Name())))
return fmt.Sprintf("// From %s\n", path.Join(obj.Pkg().Name(), filepath.Base(file.Name())))
}
// buildStruct just prints the typescript def for a type.
@@ -387,7 +423,7 @@ func (g *Generator) buildUnion(obj types.Object, st *types.Union) (string, error
allTypes = slice.Unique(allTypes)
_, _ = s.WriteString(fmt.Sprintf("export type %s = %s\n", obj.Name(), strings.Join(allTypes, " | ")))
_, _ = s.WriteString(fmt.Sprintf("export type %s = %s\n", objName(obj), strings.Join(allTypes, " | ")))
return s.String(), nil
}
@@ -421,7 +457,7 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
}
state.PosLine = g.posLine(obj)
state.Name = obj.Name()
state.Name = objName(obj)
// Handle named embedded structs in the codersdk package via extension.
var extends []string
@@ -453,6 +489,10 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
panic("invalid struct tags on type " + obj.String())
}
if !field.Exported() {
continue
}
// Use the json name if present
jsonTag, err := tags.Get("json")
var (
@@ -729,6 +769,8 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
case "time.Time":
// We really should come up with a standard for time.
return TypescriptType{ValueType: "string"}, nil
case "time.Duration":
return TypescriptType{ValueType: "number"}, nil
case "database/sql.NullTime":
return TypescriptType{ValueType: "string", Optional: true}, nil
case "github.com/coder/coder/codersdk.NullTime":
@@ -744,12 +786,13 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
}
// Then see if the type is defined elsewhere. If it is, we can just
// put the name as it will be defined in the typescript codeblock
// put the objName as it will be defined in the typescript codeblock
// we generate.
name := n.Obj().Name()
objName := objName(n.Obj())
genericName := ""
genericTypes := make(map[string]string)
if obj := g.pkg.Types.Scope().Lookup(name); obj != nil {
pkgName := n.Obj().Pkg().Name()
if obj := g.pkg.Types.Scope().Lookup(n.Obj().Name()); g.pkg.Name == pkgName && obj != nil {
// Sweet! Using other typescript types as fields. This could be an
// enum or another struct
if args := n.TypeArgs(); args != nil && args.Len() > 0 {
@@ -758,7 +801,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
for i := 0; i < args.Len(); i++ {
genType, err := g.typescriptType(args.At(i))
if err != nil {
return TypescriptType{}, xerrors.Errorf("generic field %q<%q>: %w", name, args.At(i).String(), err)
return TypescriptType{}, xerrors.Errorf("generic field %q<%q>: %w", objName, args.At(i).String(), err)
}
if param, ok := args.At(i).(*types.TypeParam); ok {
@@ -773,13 +816,13 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
genericConstraints = append(genericConstraints, genType.ValueType)
}
genericName = name + fmt.Sprintf("<%s>", strings.Join(genericNames, ", "))
name += fmt.Sprintf("<%s>", strings.Join(genericConstraints, ", "))
genericName = objName + fmt.Sprintf("<%s>", strings.Join(genericNames, ", "))
objName += fmt.Sprintf("<%s>", strings.Join(genericConstraints, ", "))
}
return TypescriptType{
GenericTypes: genericTypes,
GenericValue: genericName,
ValueType: name,
ValueType: objName,
}, nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
package enums
package codersdk
type (
Enum string
-2
View File
@@ -1,5 +1,3 @@
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
// From codersdk/enums.go
export type Enums = Enum[]
+1 -1
View File
@@ -1,4 +1,4 @@
package genericmap
package codersdk
type Foo struct {
Bar string `json:"bar"`
-2
View File
@@ -1,5 +1,3 @@
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
// From codersdk/genericmap.go
export interface Buzz {
readonly foo: Foo
+17 -17
View File
@@ -1,4 +1,4 @@
package generics
package codersdk
import "time"
@@ -10,26 +10,26 @@ type Custom interface {
string | bool | int | time.Duration | []string | *int
}
// StaticGeneric has all generic fields defined in the field
type StaticGeneric struct {
Static GenericFields[string, int, time.Duration, string] `json:"static"`
// Static has all generic fields defined in the field
type Static struct {
Static Fields[string, int, time.Duration, string] `json:"static"`
}
// DynamicGeneric can has some dynamic fields
type DynamicGeneric[A any, S Single] struct {
Dynamic GenericFields[bool, A, string, S] `json:"dynamic"`
Comparable bool `json:"comparable"`
// Dynamic has some dynamic fields.
type Dynamic[A any, S Single] struct {
Dynamic Fields[bool, A, string, S] `json:"dynamic"`
Comparable bool `json:"comparable"`
}
type ComplexGeneric[C comparable, S Single, T Custom] struct {
Dynamic GenericFields[C, bool, string, S] `json:"dynamic"`
Order GenericFieldsDiffOrder[C, string, S, T] `json:"order"`
Comparable C `json:"comparable"`
Single S `json:"single"`
Static StaticGeneric `json:"static"`
type Complex[C comparable, S Single, T Custom] struct {
Dynamic Fields[C, bool, string, S] `json:"dynamic"`
Order FieldsDiffOrder[C, string, S, T] `json:"order"`
Comparable C `json:"comparable"`
Single S `json:"single"`
Static Static `json:"static"`
}
type GenericFields[C comparable, A any, T Custom, S Single] struct {
type Fields[C comparable, A any, T Custom, S Single] struct {
Comparable C `json:"comparable"`
Any A `json:"any"`
@@ -38,6 +38,6 @@ type GenericFields[C comparable, A any, T Custom, S Single] struct {
SingleConstraint S `json:"single_constraint"`
}
type GenericFieldsDiffOrder[A any, C comparable, S Single, T Custom] struct {
GenericFields[C, A, T, S]
type FieldsDiffOrder[A any, C comparable, S Single, T Custom] struct {
Fields Fields[C, A, T, S]
}
+11 -13
View File
@@ -1,22 +1,20 @@
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
// From codersdk/generics.go
export interface ComplexGeneric<C extends comparable, S extends Single, T extends Custom> {
readonly dynamic: GenericFields<C, boolean, string, S>
readonly order: GenericFieldsDiffOrder<C, string, S, T>
export interface Complex<C extends comparable, S extends Single, T extends Custom> {
readonly dynamic: Fields<C, boolean, string, S>
readonly order: FieldsDiffOrder<C, string, S, T>
readonly comparable: C
readonly single: S
readonly static: StaticGeneric
readonly static: Static
}
// From codersdk/generics.go
export interface DynamicGeneric<A extends any, S extends Single> {
readonly dynamic: GenericFields<boolean, A, string, S>
export interface Dynamic<A extends any, S extends Single> {
readonly dynamic: Fields<boolean, A, string, S>
readonly comparable: boolean
}
// From codersdk/generics.go
export interface GenericFields<C extends comparable, A extends any, T extends Custom, S extends Single> {
export interface Fields<C extends comparable, A extends any, T extends Custom, S extends Single> {
readonly comparable: C
readonly any: A
readonly custom: T
@@ -25,13 +23,13 @@ export interface GenericFields<C extends comparable, A extends any, T extends Cu
}
// From codersdk/generics.go
export interface GenericFieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
readonly GenericFields: GenericFields<C, A, T, S>
export interface FieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
readonly Fields: Fields<C, A, T, S>
}
// From codersdk/generics.go
export interface StaticGeneric {
readonly static: GenericFields<string, number, number, string>
export interface Static {
readonly static: Fields<string, number, number, string>
}
// From codersdk/generics.go
+91 -2
View File
@@ -1,5 +1,7 @@
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
// The code below is generated from codersdk.
// From codersdk/templates.go
export interface ACLAvailable {
readonly users: User[]
@@ -235,7 +237,6 @@ export interface CreateTestAuditLogRequest {
// From codersdk/apikey.go
export interface CreateTokenRequest {
// This is likely an enum in an external package ("time.Duration")
readonly lifetime: number
readonly scope: APIKeyScope
readonly token_name: string
@@ -1075,7 +1076,6 @@ export interface TemplateVersionsByTemplateRequest extends Pagination {
// From codersdk/apikey.go
export interface TokenConfig {
// This is likely an enum in an external package ("time.Duration")
readonly max_token_lifetime: number
}
@@ -1928,3 +1928,92 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [
// From codersdk/workspaceproxy.go
export type RegionTypes = Region | WorkspaceProxy
// The code below is generated from coderd/healthcheck.
// From healthcheck/accessurl.go
export interface HealthcheckAccessURLReport {
readonly access_url: string
readonly healthy: boolean
readonly reachable: boolean
readonly status_code: number
readonly healthz_response: string
readonly error?: string
}
// From healthcheck/derp.go
export interface HealthcheckDERPNodeReport {
readonly healthy: boolean
// Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly node?: any
// Named type "tailscale.com/derp.ServerInfoMessage" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly node_info: any
readonly can_exchange_messages: boolean
readonly round_trip_ping: string
readonly round_trip_ping_ms: number
readonly uses_websocket: boolean
readonly client_logs: string[][]
readonly client_errs: string[][]
readonly error?: string
readonly stun: HealthcheckDERPStunReport
}
// From healthcheck/derp.go
export interface HealthcheckDERPRegionReport {
readonly healthy: boolean
// Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly region?: any
readonly node_reports: HealthcheckDERPNodeReport[]
readonly error?: string
}
// From healthcheck/derp.go
export interface HealthcheckDERPReport {
readonly healthy: boolean
readonly regions: Record<number, HealthcheckDERPRegionReport>
// Named type "tailscale.com/net/netcheck.Report" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly netcheck?: any
readonly netcheck_err?: string
readonly netcheck_logs: string[]
readonly error?: string
}
// From healthcheck/derp.go
export interface HealthcheckDERPStunReport {
readonly Enabled: boolean
readonly CanSTUN: boolean
readonly Error?: string
}
// From healthcheck/database.go
export interface HealthcheckDatabaseReport {
readonly healthy: boolean
readonly reachable: boolean
readonly latency: string
readonly latency_ms: number
readonly error?: string
}
// From healthcheck/healthcheck.go
export interface HealthcheckReport {
readonly time: string
readonly healthy: boolean
readonly failing_sections: string[]
readonly derp: HealthcheckDERPReport
readonly access_url: HealthcheckAccessURLReport
readonly websocket: HealthcheckWebsocketReport
readonly database: HealthcheckDatabaseReport
readonly coder_version: string
}
// From healthcheck/websocket.go
export interface HealthcheckWebsocketReport {
readonly healthy: boolean
readonly body: string
readonly code: number
readonly error?: string
}