mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
117 lines
3.5 KiB
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
|
||
|
}
|