mirror of https://github.com/jetkvm/kvm.git
fix: handle nullable properly
This commit is contained in:
parent
f8c2a95381
commit
3e2df4e651
|
@ -174,6 +174,11 @@ func extractMapType(t reflect.Type, schema *APISchema) *APIType {
|
||||||
|
|
||||||
// extractStructType handles struct types
|
// extractStructType handles struct types
|
||||||
func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
||||||
|
// Skip null.* structs as they are handled as optional properties
|
||||||
|
if strings.HasPrefix(t.String(), "null.") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
apiType := &APIType{
|
apiType := &APIType{
|
||||||
Name: t.String(),
|
Name: t.String(),
|
||||||
Kind: TypeKindStruct,
|
Kind: TypeKindStruct,
|
||||||
|
@ -186,6 +191,9 @@ func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract fields
|
// Extract fields
|
||||||
|
embeddedStructs := make([]string, 0)
|
||||||
|
regularFields := make([]APIField, 0)
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
field := t.Field(i)
|
field := t.Field(i)
|
||||||
|
|
||||||
|
@ -194,17 +202,36 @@ func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
apiField := buildAPIField(field)
|
// Check if this is an embedded struct (anonymous field)
|
||||||
apiType.Fields = append(apiType.Fields, apiField)
|
if field.Anonymous && field.Type.Kind() == reflect.Struct {
|
||||||
|
embeddedStructs = append(embeddedStructs, field.Type.String())
|
||||||
|
// Recursively extract nested struct types from embedded structs
|
||||||
|
if nestedType := extractAPIType(field.Type, schema); nestedType != nil {
|
||||||
|
if nestedType.Kind == TypeKindStruct {
|
||||||
|
schema.Types[nestedType.Name] = *nestedType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
apiField := buildAPIField(field)
|
||||||
|
regularFields = append(regularFields, apiField)
|
||||||
|
|
||||||
// Recursively extract nested struct types from field types
|
// Recursively extract nested struct types from field types
|
||||||
if nestedType := extractAPIType(field.Type, schema); nestedType != nil {
|
if nestedType := extractAPIType(field.Type, schema); nestedType != nil {
|
||||||
if nestedType.Kind == TypeKindStruct {
|
if nestedType.Kind == TypeKindStruct {
|
||||||
schema.Types[nestedType.Name] = *nestedType
|
schema.Types[nestedType.Name] = *nestedType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have exactly one embedded struct and no regular fields, mark it as an extension
|
||||||
|
if len(embeddedStructs) == 1 && len(regularFields) == 0 {
|
||||||
|
apiType.Kind = TypeKindExtension
|
||||||
|
apiType.Extends = embeddedStructs[0]
|
||||||
|
} else {
|
||||||
|
apiType.Fields = regularFields
|
||||||
|
}
|
||||||
|
|
||||||
return apiType
|
return apiType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +337,7 @@ func getAllReferencedStructs(schema *APISchema) []APIType {
|
||||||
|
|
||||||
// Also add all structs from the complete schema that might be referenced
|
// Also add all structs from the complete schema that might be referenced
|
||||||
for name, apiType := range schema.Types {
|
for name, apiType := range schema.Types {
|
||||||
if apiType.Kind == TypeKindStruct {
|
if apiType.Kind == TypeKindStruct || apiType.Kind == TypeKindExtension {
|
||||||
allStructs[name] = apiType
|
allStructs[name] = apiType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ const (
|
||||||
TypeKindArray TypeKind = "array"
|
TypeKindArray TypeKind = "array"
|
||||||
// TypeKindPointer represents a pointer type
|
// TypeKindPointer represents a pointer type
|
||||||
TypeKindPointer TypeKind = "pointer"
|
TypeKindPointer TypeKind = "pointer"
|
||||||
|
// TypeKindExtension represents a struct that extends another struct
|
||||||
|
TypeKindExtension TypeKind = "extension"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIType represents a type used in the JSON-RPC API
|
// APIType represents a type used in the JSON-RPC API
|
||||||
|
@ -26,6 +28,7 @@ type APIType struct {
|
||||||
Package string `json:"package"`
|
Package string `json:"package"`
|
||||||
Kind TypeKind `json:"kind"`
|
Kind TypeKind `json:"kind"`
|
||||||
Fields []APIField `json:"fields,omitempty"`
|
Fields []APIField `json:"fields,omitempty"`
|
||||||
|
Extends string `json:"extends,omitempty"`
|
||||||
IsPointer bool `json:"is_pointer"`
|
IsPointer bool `json:"is_pointer"`
|
||||||
IsSlice bool `json:"is_slice"`
|
IsSlice bool `json:"is_slice"`
|
||||||
IsMap bool `json:"is_map"`
|
IsMap bool `json:"is_map"`
|
||||||
|
|
|
@ -24,6 +24,7 @@ func generateTypeScriptTypings(schema *APISchema, searchPath string) string {
|
||||||
"sub": func(a, b int) int { return a - b },
|
"sub": func(a, b int) int { return a - b },
|
||||||
"pad": func(s string, width int) string { return padString(s, width) },
|
"pad": func(s string, width int) string { return padString(s, width) },
|
||||||
"padComment": func(fieldName, fieldType string) string { return padComment(fieldName, fieldType) },
|
"padComment": func(fieldName, fieldType string) string { return padComment(fieldName, fieldType) },
|
||||||
|
"isOptionalType": func(goType string) bool { return isOptionalType(goType) },
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the main template
|
// Parse the main template
|
||||||
|
@ -65,6 +66,11 @@ func padComment(fieldName, fieldType string) string {
|
||||||
return strings.Repeat(" ", targetColumn-declarationLength)
|
return strings.Repeat(" ", targetColumn-declarationLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isOptionalType determines if a Go type should be rendered as an optional TypeScript property
|
||||||
|
func isOptionalType(goType string) bool {
|
||||||
|
return goType == "null.String" || goType == "null.Bool" || goType == "null.Int"
|
||||||
|
}
|
||||||
|
|
||||||
// cleanTypeName cleans up Go type names for TypeScript
|
// cleanTypeName cleans up Go type names for TypeScript
|
||||||
func cleanTypeName(typeName string) string {
|
func cleanTypeName(typeName string) string {
|
||||||
// Remove package prefixes
|
// Remove package prefixes
|
||||||
|
@ -105,6 +111,12 @@ func parseTypeRecursively(goType string) string {
|
||||||
return "string"
|
return "string"
|
||||||
case "usbgadget.ByteSlice":
|
case "usbgadget.ByteSlice":
|
||||||
return "number[]"
|
return "number[]"
|
||||||
|
case "null.String":
|
||||||
|
return "string"
|
||||||
|
case "null.Bool":
|
||||||
|
return "boolean"
|
||||||
|
case "null.Int":
|
||||||
|
return "number"
|
||||||
case "interface {}":
|
case "interface {}":
|
||||||
return "any"
|
return "any"
|
||||||
case "time.Duration":
|
case "time.Duration":
|
||||||
|
@ -237,12 +249,15 @@ func hasParameters(handler APIHandler) bool {
|
||||||
// typescriptTemplate is the main template for generating TypeScript definitions
|
// typescriptTemplate is the main template for generating TypeScript definitions
|
||||||
const typescriptTemplate = `// Code generated by generate_typings.go. DO NOT EDIT.
|
const typescriptTemplate = `// Code generated by generate_typings.go. DO NOT EDIT.
|
||||||
{{range $struct := getAllStructs}}
|
{{range $struct := getAllStructs}}
|
||||||
|
{{if eq $struct.Kind "extension"}}
|
||||||
|
export interface {{cleanTypeName $struct.Name}} extends {{cleanTypeName $struct.Extends}} {
|
||||||
|
}
|
||||||
|
{{else}}
|
||||||
export interface {{cleanTypeName $struct.Name}} {
|
export interface {{cleanTypeName $struct.Name}} {
|
||||||
{{range $field := $struct.Fields}} {{$field.JSONName}}: {{goToTypeScriptType $field.Type}};{{padComment $field.JSONName (goToTypeScriptType $field.Type)}}// {{$field.Name}} {{$field.Type}}
|
{{range $field := $struct.Fields}} {{$field.JSONName}}{{if isOptionalType $field.Type}}?{{end}}: {{goToTypeScriptType $field.Type}};{{padComment $field.JSONName (goToTypeScriptType $field.Type)}}// {{$field.Name}} {{$field.Type}}
|
||||||
{{end}}}
|
{{end}}}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
// String aliases with constants
|
// String aliases with constants
|
||||||
{{range $alias := getStringAliasInfo}}
|
{{range $alias := getStringAliasInfo}}
|
||||||
export type {{$alias.Name}} = {{range $i, $const := $alias.Constants}}"{{$const}}"{{if lt $i (sub (len $alias.Constants) 1)}} | {{end}}{{end}};
|
export type {{$alias.Name}} = {{range $i, $const := $alias.Constants}}"{{$const}}"{{if lt $i (sub (len $alias.Constants) 1)}} | {{end}}{{end}};
|
||||||
|
@ -276,11 +291,6 @@ export interface JsonRpcErrorResponse {
|
||||||
|
|
||||||
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
||||||
|
|
||||||
// Handler method types (generated from actual handlers)
|
|
||||||
export type JsonRpcMethod =
|
|
||||||
{{range $i, $method := getSortedMethods}}{{if $i}} | {{else}} | {{end}}"{{$method}}"
|
|
||||||
{{end}};
|
|
||||||
|
|
||||||
// RPC Functions
|
// RPC Functions
|
||||||
export class JsonRpcClient {
|
export class JsonRpcClient {
|
||||||
constructor(private send: (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => void) {}
|
constructor(private send: (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => void) {}
|
||||||
|
|
Loading…
Reference in New Issue