reflect.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright (c) 2019 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package xreflect
  21. import (
  22. "fmt"
  23. "net/url"
  24. "reflect"
  25. "regexp"
  26. "runtime"
  27. "strings"
  28. "go.uber.org/dig"
  29. )
  30. // Match from beginning of the line until the first `vendor/` (non-greedy)
  31. var vendorRe = regexp.MustCompile("^.*?/vendor/")
  32. // ReturnTypes takes a func and returns a slice of string'd types.
  33. func ReturnTypes(t interface{}) []string {
  34. if reflect.TypeOf(t).Kind() != reflect.Func {
  35. // Invalid provide, will be logged as an error.
  36. return []string{}
  37. }
  38. rtypes := []string{}
  39. ft := reflect.ValueOf(t).Type()
  40. for i := 0; i < ft.NumOut(); i++ {
  41. t := ft.Out(i)
  42. traverseOuts(key{t: t}, func(s string) {
  43. rtypes = append(rtypes, s)
  44. })
  45. }
  46. return rtypes
  47. }
  48. type key struct {
  49. t reflect.Type
  50. name string
  51. }
  52. func (k *key) String() string {
  53. if k.name != "" {
  54. return fmt.Sprintf("%v:%s", k.t, k.name)
  55. }
  56. return k.t.String()
  57. }
  58. func traverseOuts(k key, f func(s string)) {
  59. // skip errors
  60. if isErr(k.t) {
  61. return
  62. }
  63. // call function on non-Out types
  64. if dig.IsOut(k.t) {
  65. // keep recursing down on field members in case they are ins
  66. for i := 0; i < k.t.NumField(); i++ {
  67. field := k.t.Field(i)
  68. ft := field.Type
  69. if field.PkgPath != "" {
  70. continue // skip private fields
  71. }
  72. // keep recursing to traverse all the embedded objects
  73. k := key{
  74. t: ft,
  75. name: field.Tag.Get("name"),
  76. }
  77. traverseOuts(k, f)
  78. }
  79. return
  80. }
  81. f(k.String())
  82. }
  83. // sanitize makes the function name suitable for logging display. It removes
  84. // url-encoded elements from the `dot.git` package names and shortens the
  85. // vendored paths.
  86. func sanitize(function string) string {
  87. // Use the stdlib to un-escape any package import paths which can happen
  88. // in the case of the "dot-git" postfix. Seems like a bug in stdlib =/
  89. if unescaped, err := url.QueryUnescape(function); err == nil {
  90. function = unescaped
  91. }
  92. // strip everything prior to the vendor
  93. return vendorRe.ReplaceAllString(function, "vendor/")
  94. }
  95. // Caller returns the formatted calling func name
  96. func Caller() string {
  97. return CallerStack(1, 0).CallerName()
  98. }
  99. // FuncName returns a funcs formatted name
  100. func FuncName(fn interface{}) string {
  101. fnV := reflect.ValueOf(fn)
  102. if fnV.Kind() != reflect.Func {
  103. return fmt.Sprint(fn)
  104. }
  105. function := runtime.FuncForPC(fnV.Pointer()).Name()
  106. return fmt.Sprintf("%s()", sanitize(function))
  107. }
  108. func isErr(t reflect.Type) bool {
  109. errInterface := reflect.TypeOf((*error)(nil)).Elem()
  110. return t.Implements(errInterface)
  111. }
  112. // Ascend the call stack until we leave the di production code. This allows us
  113. // to avoid hard-coding a frame skip, which makes this code work well even
  114. // when it's wrapped.
  115. func shouldIgnoreFrame(f Frame) bool {
  116. // Treat test files as leafs.
  117. if strings.Contains(f.File, "_test.go") {
  118. return false
  119. }
  120. // The unique, fully-qualified name for all functions begins with
  121. // "{{importPath}}.". We'll ignore di and its subpackages.
  122. s := strings.TrimPrefix(f.Function, "git.llsapp.com/zhenghe/pkg/di")
  123. if len(s) > 0 && s[0] == '.' || s[0] == '/' {
  124. // We want to match,
  125. // git.llsapp.com/zhenghe/pkg/di.Foo
  126. // git.llsapp.com/zhenghe/pkg/di/something.Foo
  127. // But not, git.llsapp.com/zhenghe/pkg/difoo
  128. return true
  129. }
  130. return false
  131. }