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
|
||||
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{
|
||||
Name: t.String(),
|
||||
Kind: TypeKindStruct,
|
||||
|
@ -186,6 +191,9 @@ func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
|||
}
|
||||
|
||||
// Extract fields
|
||||
embeddedStructs := make([]string, 0)
|
||||
regularFields := make([]APIField, 0)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
|
@ -194,8 +202,18 @@ func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
|||
continue
|
||||
}
|
||||
|
||||
// Check if this is an embedded struct (anonymous field)
|
||||
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)
|
||||
apiType.Fields = append(apiType.Fields, apiField)
|
||||
regularFields = append(regularFields, apiField)
|
||||
|
||||
// Recursively extract nested struct types from field types
|
||||
if nestedType := extractAPIType(field.Type, schema); nestedType != nil {
|
||||
|
@ -204,6 +222,15 @@ func extractStructType(t reflect.Type, schema *APISchema) *APIType {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -310,7 +337,7 @@ func getAllReferencedStructs(schema *APISchema) []APIType {
|
|||
|
||||
// Also add all structs from the complete schema that might be referenced
|
||||
for name, apiType := range schema.Types {
|
||||
if apiType.Kind == TypeKindStruct {
|
||||
if apiType.Kind == TypeKindStruct || apiType.Kind == TypeKindExtension {
|
||||
allStructs[name] = apiType
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ const (
|
|||
TypeKindArray TypeKind = "array"
|
||||
// TypeKindPointer represents a pointer type
|
||||
TypeKindPointer TypeKind = "pointer"
|
||||
// TypeKindExtension represents a struct that extends another struct
|
||||
TypeKindExtension TypeKind = "extension"
|
||||
)
|
||||
|
||||
// APIType represents a type used in the JSON-RPC API
|
||||
|
@ -26,6 +28,7 @@ type APIType struct {
|
|||
Package string `json:"package"`
|
||||
Kind TypeKind `json:"kind"`
|
||||
Fields []APIField `json:"fields,omitempty"`
|
||||
Extends string `json:"extends,omitempty"`
|
||||
IsPointer bool `json:"is_pointer"`
|
||||
IsSlice bool `json:"is_slice"`
|
||||
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 },
|
||||
"pad": func(s string, width int) string { return padString(s, width) },
|
||||
"padComment": func(fieldName, fieldType string) string { return padComment(fieldName, fieldType) },
|
||||
"isOptionalType": func(goType string) bool { return isOptionalType(goType) },
|
||||
}
|
||||
|
||||
// Parse the main template
|
||||
|
@ -65,6 +66,11 @@ func padComment(fieldName, fieldType string) string {
|
|||
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
|
||||
func cleanTypeName(typeName string) string {
|
||||
// Remove package prefixes
|
||||
|
@ -105,6 +111,12 @@ func parseTypeRecursively(goType string) string {
|
|||
return "string"
|
||||
case "usbgadget.ByteSlice":
|
||||
return "number[]"
|
||||
case "null.String":
|
||||
return "string"
|
||||
case "null.Bool":
|
||||
return "boolean"
|
||||
case "null.Int":
|
||||
return "number"
|
||||
case "interface {}":
|
||||
return "any"
|
||||
case "time.Duration":
|
||||
|
@ -237,12 +249,15 @@ func hasParameters(handler APIHandler) bool {
|
|||
// typescriptTemplate is the main template for generating TypeScript definitions
|
||||
const typescriptTemplate = `// Code generated by generate_typings.go. DO NOT EDIT.
|
||||
{{range $struct := getAllStructs}}
|
||||
{{if eq $struct.Kind "extension"}}
|
||||
export interface {{cleanTypeName $struct.Name}} extends {{cleanTypeName $struct.Extends}} {
|
||||
}
|
||||
{{else}}
|
||||
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}}
|
||||
// String aliases with constants
|
||||
{{range $alias := getStringAliasInfo}}
|
||||
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;
|
||||
|
||||
// Handler method types (generated from actual handlers)
|
||||
export type JsonRpcMethod =
|
||||
{{range $i, $method := getSortedMethods}}{{if $i}} | {{else}} | {{end}}"{{$method}}"
|
||||
{{end}};
|
||||
|
||||
// RPC Functions
|
||||
export class JsonRpcClient {
|
||||
constructor(private send: (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => void) {}
|
||||
|
|
Loading…
Reference in New Issue