-
Notifications
You must be signed in to change notification settings - Fork 284
/
Copy pathwshrpcmeta.go
116 lines (106 loc) · 3.48 KB
/
wshrpcmeta.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2025, 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
}