waveterm/pkg/wshrpc/wshrpcmeta.go

117 lines
3.5 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wshrpc
import (
"context"
"fmt"
"log"
"reflect"
"strings"
)
type WshRpcMethodDecl struct {
Command string
CommandType string
MethodName string
CommandDataType reflect.Type
DefaultResponseDataType reflect.Type
}
var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
var wshRpcInterfaceRType = reflect.TypeOf((*WshRpcInterface)(nil)).Elem()
func getWshCommandType(method reflect.Method) string {
if method.Type.NumOut() == 1 {
outType := method.Type.Out(0)
if outType.Kind() == reflect.Chan {
return RpcType_ResponseStream
}
}
return RpcType_Call
}
func getWshMethodResponseType(commandType string, method reflect.Method) reflect.Type {
switch commandType {
case RpcType_ResponseStream:
if method.Type.NumOut() != 1 {
panic(fmt.Sprintf("method %q has invalid number of return values for response stream", method.Name))
}
outType := method.Type.Out(0)
if outType.Kind() != reflect.Chan {
panic(fmt.Sprintf("method %q has invalid return type %s for response stream", method.Name, outType))
}
elemType := outType.Elem()
if !strings.HasPrefix(elemType.Name(), "RespOrErrorUnion") {
panic(fmt.Sprintf("method %q has invalid return element type %s for response stream (should be RespOrErrorUnion)", method.Name, elemType))
}
respField, found := elemType.FieldByName("Response")
if !found {
panic(fmt.Sprintf("method %q has invalid return element type %s for response stream (missing Response field)", method.Name, elemType))
}
return respField.Type
case RpcType_Call:
if method.Type.NumOut() > 1 {
return method.Type.Out(0)
}
return nil
default:
panic(fmt.Sprintf("unsupported command type %q", commandType))
}
}
func generateWshCommandDecl(method reflect.Method) *WshRpcMethodDecl {
if method.Type.NumIn() == 0 || method.Type.In(0) != contextRType {
panic(fmt.Sprintf("method %q does not have context as first argument", method.Name))
}
cmdStr := method.Name
decl := &WshRpcMethodDecl{}
// remove Command suffix
if !strings.HasSuffix(cmdStr, "Command") {
panic(fmt.Sprintf("method %q does not have Command suffix", cmdStr))
}
cmdStr = cmdStr[:len(cmdStr)-len("Command")]
decl.Command = strings.ToLower(cmdStr)
decl.CommandType = getWshCommandType(method)
decl.MethodName = method.Name
var cdataType reflect.Type
if method.Type.NumIn() > 1 {
cdataType = method.Type.In(1)
}
decl.CommandDataType = cdataType
decl.DefaultResponseDataType = getWshMethodResponseType(decl.CommandType, method)
return decl
}
func MakeMethodMapForImpl(impl any, declMap map[string]*WshRpcMethodDecl) map[string]reflect.Method {
rtype := reflect.TypeOf(impl)
rtnMap := make(map[string]reflect.Method)
for midx := 0; midx < rtype.NumMethod(); midx++ {
method := rtype.Method(midx)
if !strings.HasSuffix(method.Name, "Command") {
continue
}
commandName := strings.ToLower(method.Name[:len(method.Name)-len("Command")])
decl := declMap[commandName]
if decl == nil {
log.Printf("WARNING: method %q does not match a command method", method.Name)
continue
}
rtnMap[commandName] = method
}
return rtnMap
}
func GenerateWshCommandDeclMap() map[string]*WshRpcMethodDecl {
rtype := wshRpcInterfaceRType
rtnMap := make(map[string]*WshRpcMethodDecl)
for midx := 0; midx < rtype.NumMethod(); midx++ {
method := rtype.Method(midx)
decl := generateWshCommandDecl(method)
rtnMap[decl.Command] = decl
}
return rtnMap
}