-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpika_aip_filter_proto.go
157 lines (137 loc) · 5.04 KB
/
pika_aip_filter_proto.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// SPDX-FileCopyrightText: Copyright (c) 2023-2024, Ctrl IQ, Inc. All rights reserved
// SPDX-License-Identifier: Apache-2.0
package pika
import (
"strings"
"github.com/iancoleman/strcase"
"go.ciq.dev/pika/parser"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
var (
protoKindToLexer = map[protoreflect.Kind]int{
protoreflect.StringKind: parser.FilterLexerSTRING,
protoreflect.FloatKind: parser.FilterLexerNUM_FLOAT,
protoreflect.Int32Kind: parser.FilterLexerNUM_INT,
protoreflect.Int64Kind: parser.FilterLexerNUM_INT,
protoreflect.Uint32Kind: parser.FilterLexerNUM_UINT,
protoreflect.Uint64Kind: parser.FilterLexerNUM_UINT,
}
protoMessageKindToLexer = map[string]int{
"google.protobuf.StringValue": parser.FilterLexerSTRING,
"google.protobuf.Duration": parser.FilterLexerDURATION,
"google.protobuf.Timestamp": parser.FilterLexerTIMESTAMP,
"google.protobuf.DoubleValue": parser.FilterLexerNUM_FLOAT,
"google.protobuf.FloatValue": parser.FilterLexerNUM_FLOAT,
"google.protobuf.Int32Value": parser.FilterLexerNUM_INT,
"google.protobuf.Int64Value": parser.FilterLexerNUM_INT,
"google.protobuf.UInt32Value": parser.FilterLexerNUM_UINT,
"google.protobuf.UInt64Value": parser.FilterLexerNUM_UINT,
}
)
type ProtoReflectOptions struct {
// Exclude is a list of field names to exclude from the filter
// Uses proto name always, not JSON name
Exclude []string
// ColumnName is a function that returns the column name for a given field
// name. If not provided, the field name is used.
ColumnName func(string) string
}
func protoReflect(m proto.Message, opts ProtoReflectOptions) AIPFilterOptions {
res := AIPFilterOptions{
Identifiers: map[string]AIPFilterIdentifier{},
AcceptableIdentifiers: []string{},
}
fields := m.ProtoReflect().Descriptor().Fields()
for i := 0; i < fields.Len(); i++ {
// Get field from message
fd := fields.Get(i)
// Get name, use JSON name if available
name := string(fd.Name())
// Skip if excluded
if contains(opts.Exclude, name) {
continue
}
if fd.JSONName() != "" {
name = fd.JSONName()
}
// Now let's configure the identifiers map with acceptable value and
// potential aliases for enum
ident := AIPFilterIdentifier{
ValueAliases: map[any]any{},
AcceptedTypes: []int{},
AcceptedValues: []any{},
IsRepeated: false,
}
// Check if repeated
if fd.Cardinality() == protoreflect.Repeated {
ident.IsRepeated = true
}
// Check and add type
// If type is message and not a supported wrapper type, skip
switch fd.Kind() {
case protoreflect.StringKind,
protoreflect.FloatKind,
protoreflect.Int32Kind,
protoreflect.Int64Kind,
protoreflect.Uint32Kind,
protoreflect.Uint64Kind:
ident.AcceptedTypes = append(ident.AcceptedTypes, protoKindToLexer[fd.Kind()])
case protoreflect.BoolKind:
ident.AcceptedTypes = append(ident.AcceptedTypes, parser.FilterLexerTRUE, parser.FilterLexerFALSE)
case protoreflect.MessageKind:
// Bool wrappers need two types
if fd.Message().FullName() == "google.protobuf.BoolValue" {
ident.AcceptedTypes = append(ident.AcceptedTypes, parser.FilterLexerTRUE, parser.FilterLexerFALSE, parser.FilterLexerNULL)
} else {
// Check if it's a supported wrapper type
if lexer, ok := protoMessageKindToLexer[string(fd.Message().FullName())]; ok {
ident.AcceptedTypes = append(ident.AcceptedTypes, lexer)
// All wrappers can be nil
ident.AcceptedTypes = append(ident.AcceptedTypes, parser.FilterLexerNULL)
} else {
// Skip this field
continue
}
}
case protoreflect.EnumKind:
// Add all enum values as aliases
for i := 0; i < fd.Enum().Values().Len(); i++ {
enumName := string(fd.Enum().Values().Get(i).Name())
enumReverseSplit := strings.Split(enumName, "_")
for i := 0; i < len(enumReverseSplit)/2; i++ {
j := len(enumReverseSplit) - i - 1
enumReverseSplit[i], enumReverseSplit[j] = enumReverseSplit[j], enumReverseSplit[i]
}
lastValue := enumReverseSplit[0]
// Both the name and the last value are acceptable
// Only add last value if it's not the same as the name
num := int64(fd.Enum().Values().Get(i).Number())
ident.ValueAliases[strings.ToLower(enumName)] = num
if lastValue != enumName {
ident.ValueAliases[strings.ToLower(lastValue)] = num
}
// Add num as acceptable value
ident.AcceptedValues = append(ident.AcceptedValues, num)
}
// Add the enum value as a type
ident.AcceptedTypes = append(ident.AcceptedTypes, parser.FilterLexerNUM_INT)
}
// Add column name
if opts.ColumnName != nil {
ident.ColumnName = opts.ColumnName(name)
} else {
ident.ColumnName = strcase.ToSnake(name)
}
// Add to identifiers
res.Identifiers[name] = ident
res.AcceptableIdentifiers = append(res.AcceptableIdentifiers, name)
}
return res
}
func ProtoReflect(m proto.Message) AIPFilterOptions {
return protoReflect(m, ProtoReflectOptions{})
}
func ProtoReflectWithOpts(m proto.Message, opts ProtoReflectOptions) AIPFilterOptions {
return protoReflect(m, opts)
}