mirror of https://github.com/jetkvm/kvm.git
434 lines
11 KiB
Go
434 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/jetkvm/kvm"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// NewAPISchema creates a new API schema from the JSON-RPC handlers
|
|
func NewAPISchema(searchPath string) *APISchema {
|
|
schema := &APISchema{
|
|
Handlers: make(map[string]APIHandler),
|
|
Types: make(map[string]APIType),
|
|
}
|
|
|
|
handlers := kvm.GetJSONRPCHandlers()
|
|
log.Info().Int("count", len(handlers)).Msg("Processing JSON-RPC handlers")
|
|
|
|
for name, handler := range handlers {
|
|
log.Debug().Str("handler", name).Msg("Building API handler")
|
|
apiHandler := buildAPIHandler(name, handler, schema)
|
|
schema.Handlers[name] = apiHandler
|
|
}
|
|
|
|
schema.HandlerCount = len(schema.Handlers)
|
|
schema.TypeCount = len(schema.Types)
|
|
|
|
log.Info().
|
|
Int("handlers", schema.HandlerCount).
|
|
Int("types", schema.TypeCount).
|
|
Msg("API schema created successfully")
|
|
|
|
return schema
|
|
}
|
|
|
|
// buildAPIHandler constructs an APIHandler from a JSON-RPC handler
|
|
func buildAPIHandler(name string, handler kvm.JSONRPCHandler, schema *APISchema) APIHandler {
|
|
apiHandler := APIHandler{
|
|
Name: name,
|
|
FunctionType: handler.Type.String(),
|
|
ParameterNames: handler.Params,
|
|
Parameters: make([]APIParameter, 0, handler.Type.NumIn()),
|
|
ReturnValues: make([]APIReturnValue, 0, handler.Type.NumOut()),
|
|
}
|
|
|
|
// Process parameters
|
|
for i := 0; i < handler.Type.NumIn(); i++ {
|
|
paramType := handler.Type.In(i)
|
|
paramName := getParameterName(i, handler.Params)
|
|
|
|
apiParam := APIParameter{
|
|
Name: paramName,
|
|
Type: paramType.String(),
|
|
}
|
|
|
|
if apiType := extractAPIType(paramType, schema); apiType != nil {
|
|
apiParam.APIType = apiType
|
|
schema.Types[apiType.Name] = *apiType
|
|
}
|
|
|
|
apiHandler.Parameters = append(apiHandler.Parameters, apiParam)
|
|
}
|
|
|
|
// Process return values
|
|
for i := 0; i < handler.Type.NumOut(); i++ {
|
|
returnType := handler.Type.Out(i)
|
|
|
|
apiReturn := APIReturnValue{
|
|
Index: i,
|
|
Type: returnType.String(),
|
|
}
|
|
|
|
if apiType := extractAPIType(returnType, schema); apiType != nil {
|
|
apiReturn.APIType = apiType
|
|
schema.Types[apiType.Name] = *apiType
|
|
}
|
|
|
|
apiHandler.ReturnValues = append(apiHandler.ReturnValues, apiReturn)
|
|
}
|
|
|
|
return apiHandler
|
|
}
|
|
|
|
// getParameterName safely retrieves a parameter name by index
|
|
func getParameterName(index int, paramNames []string) string {
|
|
if index < len(paramNames) {
|
|
return paramNames[index]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// extractAPIType extracts API type information from a reflect.Type
|
|
// It recursively finds and adds nested struct types to the schema
|
|
func extractAPIType(t reflect.Type, schema *APISchema) *APIType {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
|
|
switch t.Kind() {
|
|
case reflect.Ptr:
|
|
return extractPointerType(t, schema)
|
|
case reflect.Slice:
|
|
return extractSliceType(t, schema)
|
|
case reflect.Array:
|
|
return extractArrayType(t, schema)
|
|
case reflect.Map:
|
|
return extractMapType(t, schema)
|
|
case reflect.Struct:
|
|
return extractStructType(t, schema)
|
|
case reflect.Interface:
|
|
return extractInterfaceType(t)
|
|
default:
|
|
return extractBasicType(t)
|
|
}
|
|
}
|
|
|
|
// extractPointerType handles pointer types
|
|
func extractPointerType(t reflect.Type, schema *APISchema) *APIType {
|
|
elemType := extractAPIType(t.Elem(), schema)
|
|
if elemType == nil {
|
|
return nil
|
|
}
|
|
|
|
elemType.IsPointer = true
|
|
elemType.Name = "*" + elemType.Name
|
|
return elemType
|
|
}
|
|
|
|
// extractSliceType handles slice types
|
|
func extractSliceType(t reflect.Type, schema *APISchema) *APIType {
|
|
elemType := extractAPIType(t.Elem(), schema)
|
|
if elemType == nil {
|
|
return nil
|
|
}
|
|
|
|
elemType.IsSlice = true
|
|
elemType.SliceType = elemType.Name
|
|
elemType.Name = "[]" + elemType.Name
|
|
return elemType
|
|
}
|
|
|
|
// extractArrayType handles array types
|
|
func extractArrayType(t reflect.Type, schema *APISchema) *APIType {
|
|
elemType := extractAPIType(t.Elem(), schema)
|
|
if elemType == nil {
|
|
return nil
|
|
}
|
|
|
|
elemType.Name = fmt.Sprintf("[%d]%s", t.Len(), elemType.Name)
|
|
return elemType
|
|
}
|
|
|
|
// extractMapType handles map types
|
|
func extractMapType(t reflect.Type, schema *APISchema) *APIType {
|
|
keyType := extractAPIType(t.Key(), schema)
|
|
valueType := extractAPIType(t.Elem(), schema)
|
|
|
|
if keyType == nil || valueType == nil {
|
|
return nil
|
|
}
|
|
|
|
return &APIType{
|
|
Name: fmt.Sprintf("map[%s]%s", keyType.Name, valueType.Name),
|
|
Kind: TypeKindMap,
|
|
IsMap: true,
|
|
MapKeyType: keyType.Name,
|
|
MapValueType: valueType.Name,
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
Fields: make([]APIField, 0, t.NumField()),
|
|
}
|
|
|
|
// Extract package name
|
|
if pkgPath := t.PkgPath(); pkgPath != "" {
|
|
apiType.Package = extractPackageName(pkgPath)
|
|
}
|
|
|
|
// Extract fields
|
|
embeddedStructs := make([]string, 0)
|
|
regularFields := make([]APIField, 0)
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
field := t.Field(i)
|
|
|
|
// Skip unexported fields
|
|
if !field.IsExported() {
|
|
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)
|
|
regularFields = append(regularFields, apiField)
|
|
|
|
// Recursively extract nested struct types from field types
|
|
if nestedType := extractAPIType(field.Type, schema); nestedType != nil {
|
|
if nestedType.Kind == TypeKindStruct {
|
|
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
|
|
}
|
|
|
|
// extractInterfaceType handles interface types
|
|
func extractInterfaceType(t reflect.Type) *APIType {
|
|
return &APIType{
|
|
Name: t.String(),
|
|
Kind: TypeKindInterface,
|
|
}
|
|
}
|
|
|
|
// extractBasicType handles basic Go types
|
|
func extractBasicType(t reflect.Type) *APIType {
|
|
if isBasicType(t.String()) {
|
|
return &APIType{
|
|
Name: t.String(),
|
|
Kind: TypeKindBasic,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// buildAPIField constructs an APIField from a reflect.StructField
|
|
func buildAPIField(field reflect.StructField) APIField {
|
|
apiField := APIField{
|
|
Name: field.Name,
|
|
JSONName: field.Name, // Default to field name
|
|
Type: field.Type.String(),
|
|
IsExported: field.IsExported(),
|
|
Tag: string(field.Tag),
|
|
}
|
|
|
|
// Parse JSON tag
|
|
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
|
if jsonName := parseJSONTag(jsonTag); jsonName != "" {
|
|
apiField.JSONName = jsonName
|
|
}
|
|
}
|
|
|
|
return apiField
|
|
}
|
|
|
|
// parseJSONTag extracts the JSON field name from a JSON tag
|
|
func parseJSONTag(jsonTag string) string {
|
|
parts := strings.Split(jsonTag, ",")
|
|
if len(parts) > 0 && parts[0] != "" && parts[0] != "-" {
|
|
return parts[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// extractPackageName extracts the package name from a package path
|
|
func extractPackageName(pkgPath string) string {
|
|
parts := strings.Split(pkgPath, "/")
|
|
if len(parts) > 0 {
|
|
return parts[len(parts)-1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetStructTypes returns all struct types from the schema
|
|
func (s *APISchema) GetStructTypes() []APIType {
|
|
structs := make([]APIType, 0)
|
|
for _, apiType := range s.Types {
|
|
if apiType.Kind == TypeKindStruct {
|
|
structs = append(structs, apiType)
|
|
}
|
|
}
|
|
return structs
|
|
}
|
|
|
|
// getSortedHandlers returns handlers sorted by name
|
|
func getSortedHandlers(schema *APISchema) []APIHandler {
|
|
var handlers []APIHandler
|
|
for _, handler := range schema.Handlers {
|
|
handlers = append(handlers, handler)
|
|
}
|
|
sort.Slice(handlers, func(i, j int) bool {
|
|
return handlers[i].Name < handlers[j].Name
|
|
})
|
|
return handlers
|
|
}
|
|
|
|
// getSortedMethods returns method names sorted alphabetically
|
|
func getSortedMethods(schema *APISchema) []string {
|
|
var methods []string
|
|
for name := range schema.Handlers {
|
|
methods = append(methods, name)
|
|
}
|
|
sort.Strings(methods)
|
|
return methods
|
|
}
|
|
|
|
// getAllReferencedStructs recursively finds all structs referenced in the API
|
|
func getAllReferencedStructs(schema *APISchema) []APIType {
|
|
// Start with all structs found in handlers
|
|
allStructs := make(map[string]APIType)
|
|
|
|
// Add all structs from handlers
|
|
for _, apiType := range schema.GetStructTypes() {
|
|
allStructs[apiType.Name] = apiType
|
|
}
|
|
|
|
// Also add all structs from the complete schema that might be referenced
|
|
for name, apiType := range schema.Types {
|
|
if apiType.Kind == TypeKindStruct || apiType.Kind == TypeKindExtension {
|
|
allStructs[name] = apiType
|
|
}
|
|
}
|
|
|
|
// Recursively find all referenced structs
|
|
changed := true
|
|
for changed {
|
|
changed = false
|
|
for _, apiType := range allStructs {
|
|
referencedStructs := findReferencedStructs(apiType, schema)
|
|
for _, refStruct := range referencedStructs {
|
|
if _, exists := allStructs[refStruct.Name]; !exists {
|
|
allStructs[refStruct.Name] = refStruct
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert map to slice
|
|
var result []APIType
|
|
for _, apiType := range allStructs {
|
|
result = append(result, apiType)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// findReferencedStructs finds structs referenced in a given API type
|
|
func findReferencedStructs(apiType APIType, schema *APISchema) []APIType {
|
|
var referenced []APIType
|
|
|
|
for _, field := range apiType.Fields {
|
|
if isStructType(field.Type) {
|
|
structName := extractStructName(field.Type)
|
|
if structType, exists := schema.Types[structName]; exists {
|
|
referenced = append(referenced, structType)
|
|
}
|
|
}
|
|
}
|
|
|
|
return referenced
|
|
}
|
|
|
|
// isStructType checks if a type string represents a struct
|
|
func isStructType(typeStr string) bool {
|
|
// Check if it's a custom type (not basic Go types)
|
|
return !isBasicType(typeStr) &&
|
|
!strings.HasPrefix(typeStr, "[]") &&
|
|
!strings.HasPrefix(typeStr, "map[") &&
|
|
!strings.HasPrefix(typeStr, "*") &&
|
|
!strings.HasPrefix(typeStr, "[")
|
|
}
|
|
|
|
// extractStructName extracts the struct name from a type string
|
|
func extractStructName(typeStr string) string {
|
|
// Remove array/slice prefixes
|
|
if strings.HasPrefix(typeStr, "[]") {
|
|
return typeStr[2:]
|
|
}
|
|
if strings.HasPrefix(typeStr, "*") {
|
|
return typeStr[1:]
|
|
}
|
|
return typeStr
|
|
}
|
|
|
|
// isBasicType checks if a type is a basic Go type
|
|
func isBasicType(typeName string) bool {
|
|
basicTypes := map[string]bool{
|
|
"bool": true,
|
|
"string": true,
|
|
"int": true,
|
|
"int8": true,
|
|
"int16": true,
|
|
"int32": true,
|
|
"int64": true,
|
|
"uint": true,
|
|
"uint8": true,
|
|
"uint16": true,
|
|
"uint32": true,
|
|
"uint64": true,
|
|
"uintptr": true,
|
|
"float32": true,
|
|
"float64": true,
|
|
"complex64": true,
|
|
"complex128": true,
|
|
"byte": true,
|
|
"rune": true,
|
|
"error": true,
|
|
}
|
|
|
|
return basicTypes[typeName]
|
|
}
|