mirror of
https://github.com/smallnest/rpcx.git
synced 2025-09-26 20:21:14 +08:00
216 lines
4.4 KiB
Go
216 lines
4.4 KiB
Go
package reflection
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"text/template"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/ChimeraCoder/gojson"
|
|
"github.com/smallnest/rpcx/log"
|
|
)
|
|
|
|
var (
|
|
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
|
typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()
|
|
)
|
|
|
|
// ServiceInfo service info.
|
|
type ServiceInfo struct {
|
|
Name string
|
|
PkgPath string
|
|
Methods []*MethodInfo
|
|
}
|
|
|
|
// MethodInfo method info
|
|
type MethodInfo struct {
|
|
Name string
|
|
ReqName string
|
|
Req string
|
|
ReplyName string
|
|
Reply string
|
|
}
|
|
|
|
var siTemplate = `package {{.PkgPath}}
|
|
|
|
type {{.Name}} struct{}
|
|
{{$name := .Name}}
|
|
{{range .Methods}}
|
|
{{.Req}}
|
|
{{.Reply}}
|
|
type (s *{{$name}}) {{.Name}}(ctx context.Context, arg *{{.ReqName}}, reply *{{.ReplyName}}) error {
|
|
return nil
|
|
}
|
|
{{end}}
|
|
`
|
|
|
|
func (si ServiceInfo) String() string {
|
|
tpl := template.Must(template.New("service").Parse(siTemplate))
|
|
var buf bytes.Buffer
|
|
_ = tpl.Execute(&buf, si)
|
|
return buf.String()
|
|
}
|
|
|
|
type Reflection struct {
|
|
Services map[string]*ServiceInfo
|
|
}
|
|
|
|
func New() *Reflection {
|
|
return &Reflection{
|
|
Services: make(map[string]*ServiceInfo),
|
|
}
|
|
}
|
|
|
|
func (r *Reflection) Register(name string, rcvr interface{}, metadata string) error {
|
|
si := &ServiceInfo{}
|
|
|
|
val := reflect.ValueOf(rcvr)
|
|
typ := reflect.TypeOf(rcvr)
|
|
vTyp := reflect.Indirect(val).Type()
|
|
si.Name = vTyp.Name()
|
|
pkg := vTyp.PkgPath()
|
|
if strings.Index(pkg, ".") > 0 {
|
|
pkg = pkg[strings.LastIndex(pkg, ".")+1:]
|
|
}
|
|
pkg = filepath.Base(pkg)
|
|
pkg = strings.ReplaceAll(pkg, "-", "_")
|
|
si.PkgPath = pkg
|
|
|
|
for m := 0; m < val.NumMethod(); m++ {
|
|
method := typ.Method(m)
|
|
mtype := method.Type
|
|
|
|
if method.PkgPath != "" {
|
|
continue
|
|
}
|
|
if mtype.NumIn() != 4 {
|
|
continue
|
|
}
|
|
// First arg must be context.Context
|
|
ctxType := mtype.In(1)
|
|
if !ctxType.Implements(typeOfContext) {
|
|
continue
|
|
}
|
|
|
|
// Second arg need not be a pointer.
|
|
argType := mtype.In(2)
|
|
if !isExportedOrBuiltinType(argType) {
|
|
continue
|
|
}
|
|
// Third arg must be a pointer.
|
|
replyType := mtype.In(3)
|
|
if replyType.Kind() != reflect.Ptr {
|
|
continue
|
|
}
|
|
// Reply type must be exported.
|
|
if !isExportedOrBuiltinType(replyType) {
|
|
continue
|
|
}
|
|
// Method needs one out.
|
|
if mtype.NumOut() != 1 {
|
|
continue
|
|
}
|
|
// The return type of the method must be error.
|
|
if returnType := mtype.Out(0); returnType != typeOfError {
|
|
continue
|
|
}
|
|
|
|
mi := &MethodInfo{}
|
|
mi.Name = method.Name
|
|
|
|
if argType.Kind() == reflect.Ptr {
|
|
argType = argType.Elem()
|
|
}
|
|
replyType = replyType.Elem()
|
|
|
|
mi.ReqName = argType.Name()
|
|
mi.Req = generateTypeDefination(mi.ReqName, si.PkgPath, generateJSON(argType))
|
|
mi.ReplyName = replyType.Name()
|
|
mi.Reply = generateTypeDefination(mi.ReplyName, si.PkgPath, generateJSON(replyType))
|
|
|
|
si.Methods = append(si.Methods, mi)
|
|
}
|
|
|
|
if len(si.Methods) > 0 {
|
|
r.Services[name] = si
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Reflection) Unregister(name string) error {
|
|
delete(r.Services, name)
|
|
return nil
|
|
}
|
|
|
|
func (r *Reflection) GetService(ctx context.Context, s string, reply *string) error {
|
|
si, ok := r.Services[s]
|
|
if !ok {
|
|
return fmt.Errorf("not found service %s", s)
|
|
}
|
|
*reply = si.String()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Reflection) GetServices(ctx context.Context, s string, reply *string) error {
|
|
var buf bytes.Buffer
|
|
|
|
pkg := `package `
|
|
|
|
for _, si := range r.Services {
|
|
if pkg == `package ` {
|
|
pkg = pkg + si.PkgPath + "\n\n"
|
|
}
|
|
buf.WriteString(strings.ReplaceAll(si.String(), pkg, ""))
|
|
}
|
|
|
|
if pkg != `package ` {
|
|
*reply = pkg + buf.String()
|
|
} else {
|
|
*reply = buf.String()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateTypeDefination(name, pkg string, jsonValue string) string {
|
|
jsonValue = strings.TrimSpace(jsonValue)
|
|
if jsonValue == "" || jsonValue == `""` {
|
|
return ""
|
|
}
|
|
r := strings.NewReader(jsonValue)
|
|
output, err := gojson.Generate(r, gojson.ParseJson, name, pkg, nil, false, true)
|
|
if err != nil {
|
|
log.Errorf("failed to generate json: %v", err)
|
|
return ""
|
|
}
|
|
rt := strings.ReplaceAll(string(output), "``", "")
|
|
return strings.ReplaceAll(rt, "package "+pkg+"\n\n", "")
|
|
}
|
|
|
|
func generateJSON(typ reflect.Type) string {
|
|
v := reflect.New(typ).Interface()
|
|
|
|
data, _ := json.Marshal(v)
|
|
return string(data)
|
|
}
|
|
|
|
func isExported(name string) bool {
|
|
rune, _ := utf8.DecodeRuneInString(name)
|
|
return unicode.IsUpper(rune)
|
|
}
|
|
|
|
func isExportedOrBuiltinType(t reflect.Type) bool {
|
|
for t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
return isExported(t.Name()) || t.PkgPath() == ""
|
|
}
|