forked from ChainSafe/gossamer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
97 lines (85 loc) · 2.47 KB
/
service.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
// Copyright 2021 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only
package rpc
import (
"fmt"
"net/http"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
// Service struct to hold rpc service data
type Service struct {
rpcMethods []string // list of method names offered by rpc
}
// NewService create a new instance of Service
func NewService() *Service {
return &Service{
rpcMethods: []string{},
}
}
// Methods returns list of methods available via RPC call
func (s *Service) Methods() []string {
return s.rpcMethods
}
var (
// Precompute the reflect.Type of error and http.Request
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
)
// BuildMethodNames takes receiver interface and populates rpcMethods array with available
// method names
func (s *Service) BuildMethodNames(rcvr interface{}, name string) {
rcvrType := reflect.TypeOf(rcvr)
for i := 0; i < rcvrType.NumMethod(); i++ {
method := rcvrType.Method(i)
mtype := method.Type
// Method must be exported.
if method.PkgPath != "" {
continue
}
// Method needs four ins: receiver, *http.Request, *args, *reply.
if mtype.NumIn() != 4 {
continue
}
// First argument must be a pointer and must be http.Request.
reqType := mtype.In(1)
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
continue
}
// Second argument must be a pointer and must be exported.
args := mtype.In(2)
if args.Kind() != reflect.Ptr || !isExportedOrBuiltIn(args) {
continue
}
// Third argument must be a pointer and must be exported.
reply := mtype.In(3)
if reply.Kind() != reflect.Ptr || !isExportedOrBuiltIn(reply) {
continue
}
// Method needs one out: error.
if mtype.NumOut() != 1 {
continue
}
if returnType := mtype.Out(0); returnType != typeOfError {
continue
}
s.rpcMethods = append(s.rpcMethods,
fmt.Sprintf("%s_%s%s", name, strings.ToLower(string(method.Name[0])), method.Name[1:]))
}
}
// isExported returns true of a string is an exported (upper case) name.
func isExported(name string) bool {
r, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(r)
}
// isExportedOrBuiltIn returns true if a type is exported or a built in.
func isExportedOrBuiltIn(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}