ops.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // The ops package provides a set of functions that can be used to
  2. // build a filter AST programatically using basic functions, versus
  3. // building a filter AST leveraging all structural components of the
  4. // tree.
  5. package ops
  6. import (
  7. "fmt"
  8. "reflect"
  9. "strings"
  10. "github.com/opencost/opencost/pkg/filter21/allocation"
  11. "github.com/opencost/opencost/pkg/filter21/asset"
  12. "github.com/opencost/opencost/pkg/filter21/ast"
  13. "github.com/opencost/opencost/pkg/util/typeutil"
  14. )
  15. // keyFieldType is used to extract field, key, and field type
  16. type keyFieldType interface {
  17. Field() string
  18. Key() string
  19. Type() string
  20. }
  21. // This is somewhat of a fancy solution, but allows us to "register" DefaultFieldByName funcs
  22. // funcs by Field type.
  23. var defaultFieldByType = map[string]any{
  24. // typeutil.TypeOf[cloud.CloudAggregationField](): cloud.DefaultFieldByName,
  25. typeutil.TypeOf[allocation.AllocationField](): allocation.DefaultFieldByName,
  26. typeutil.TypeOf[asset.AssetField](): asset.DefaultFieldByName,
  27. // typeutil.TypeOf[containerstats.ContainerStatsField](): containerstats.DefaultFieldByName,
  28. }
  29. // asField looks up a specific T field instance by name and returns the default
  30. // ast.Field value for that type.
  31. func asField[T ~string](field T) *ast.Field {
  32. lookup, ok := defaultFieldByType[typeutil.TypeOf[T]()]
  33. if !ok {
  34. return nil
  35. }
  36. defaultLookup, ok := lookup.(func(T) *ast.Field)
  37. if !ok {
  38. return nil
  39. }
  40. return defaultLookup(field)
  41. }
  42. // asFieldWithType allows for a field to be looked up by name and type.
  43. func asFieldWithType(field string, typ string) *ast.Field {
  44. lookup, ok := defaultFieldByType[typ]
  45. if !ok {
  46. return nil
  47. }
  48. // This is the sacrifice being made to allow a simple filter
  49. // builder style API. In the cases where we have keys, the typical
  50. // field type gets wrapped in a KeyedFieldType, which is a string
  51. // that holds all the parameterized data, but no way to get back from
  52. // string to T-instance.
  53. // Since we have the type name, we can use that to lookup the specific
  54. // func(T) *ast.Field function to be used.
  55. funcType := reflect.TypeOf(lookup)
  56. // Assert that the function has a single parameter (type T)
  57. if funcType.NumIn() != 1 {
  58. return nil
  59. }
  60. // Get a reference to the first parameter's type (T)
  61. inType := funcType.In(0)
  62. // Create a reflect.Value for the string field, then convert it to
  63. // the T type from the function's parameter list. (This has to be
  64. // done to ensure we're executing the call with the correct types)
  65. fieldParam := reflect.ValueOf(field).Convert(inType)
  66. // Create a reflect.Value for the lookup function
  67. callable := reflect.ValueOf(lookup)
  68. // Call the function with the fieldParam value, and get the result
  69. result := callable.Call([]reflect.Value{fieldParam})
  70. if len(result) == 0 {
  71. return nil
  72. }
  73. // Lastly, extract the value from the reflect.Value and ensure we can
  74. // cast it to *ast.Field
  75. resultValue := result[0].Interface()
  76. if f, ok := resultValue.(*ast.Field); ok {
  77. return f
  78. }
  79. return nil
  80. }
  81. // KeyedFieldType is a type alias for field is a special field type that can
  82. // be deconstructed into multiple components.
  83. type KeyedFieldType string
  84. func (k KeyedFieldType) Field() string {
  85. str := string(k)
  86. idx := strings.Index(str, "$")
  87. if idx == -1 {
  88. return ""
  89. }
  90. return str[0:idx]
  91. }
  92. func (k KeyedFieldType) Key() string {
  93. str := string(k)
  94. idx := strings.Index(str, "$")
  95. if idx == -1 {
  96. return ""
  97. }
  98. lastIndex := strings.LastIndex(str, "$")
  99. if lastIndex == -1 {
  100. return ""
  101. }
  102. return str[idx+1 : lastIndex]
  103. }
  104. func (k KeyedFieldType) Type() string {
  105. str := string(k)
  106. lastIndex := strings.LastIndex(str, "$")
  107. if lastIndex == -1 {
  108. return ""
  109. }
  110. return str[lastIndex+1:]
  111. }
  112. func WithKey[T ~string](field T, key string) KeyedFieldType {
  113. k := fmt.Sprintf("%s$%s$%s", field, key, typeutil.TypeOf[T]())
  114. return KeyedFieldType(k)
  115. }
  116. func toFieldAndKey[T ~string](field T) (*ast.Field, string) {
  117. var inner any = field
  118. if kft, ok := inner.(keyFieldType); ok {
  119. return asFieldWithType(kft.Field(), kft.Type()), kft.Key()
  120. }
  121. return asField(field), ""
  122. }
  123. func identifier[T ~string](field T) ast.Identifier {
  124. f, key := toFieldAndKey(field)
  125. return ast.Identifier{
  126. Field: f,
  127. Key: key,
  128. }
  129. }
  130. func And(node, next ast.FilterNode, others ...ast.FilterNode) ast.FilterNode {
  131. operands := append([]ast.FilterNode{node, next}, others...)
  132. return &ast.AndOp{
  133. Operands: operands,
  134. }
  135. }
  136. func Or(node, next ast.FilterNode, others ...ast.FilterNode) ast.FilterNode {
  137. operands := append([]ast.FilterNode{node, next}, others...)
  138. return &ast.OrOp{
  139. Operands: operands,
  140. }
  141. }
  142. func Not(node ast.FilterNode) ast.FilterNode {
  143. return &ast.NotOp{
  144. Operand: node,
  145. }
  146. }
  147. func Eq[T ~string](field T, value string) ast.FilterNode {
  148. return &ast.EqualOp{
  149. Left: identifier(field),
  150. Right: value,
  151. }
  152. }
  153. func NotEq[T ~string](field T, value string) ast.FilterNode {
  154. return Not(Eq(field, value))
  155. }
  156. func Contains[T ~string](field T, value string) ast.FilterNode {
  157. return &ast.ContainsOp{
  158. Left: identifier(field),
  159. Right: value,
  160. }
  161. }
  162. func NotContains[T ~string](field T, value string) ast.FilterNode {
  163. return Not(Contains(field, value))
  164. }
  165. func ContainsPrefix[T ~string](field T, value string) ast.FilterNode {
  166. return &ast.ContainsPrefixOp{
  167. Left: identifier(field),
  168. Right: value,
  169. }
  170. }
  171. func NotContainsPrefix[T ~string](field T, value string) ast.FilterNode {
  172. return Not(ContainsPrefix(field, value))
  173. }
  174. func ContainsSuffix[T ~string](field T, value string) ast.FilterNode {
  175. return &ast.ContainsSuffixOp{
  176. Left: identifier(field),
  177. Right: value,
  178. }
  179. }
  180. func NotContainsSuffix[T ~string](field T, value string) ast.FilterNode {
  181. return Not(ContainsSuffix(field, value))
  182. }