-
Notifications
You must be signed in to change notification settings - Fork 14
/
table_validate.go
193 lines (163 loc) · 6.66 KB
/
table_validate.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package plugin
import (
"fmt"
"strings"
"github.com/stevenle/topsort"
"github.com/turbot/go-kit/helpers"
"github.com/turbot/steampipe-plugin-sdk/v4/grpc/proto"
)
func (t *Table) validate(name string, requiredColumns []*Column) []string {
var validationErrors []string
// does table have a name set?
if t.Name == "" {
validationErrors = append(validationErrors, fmt.Sprintf("table with key '%s' in plugin table map does not have a name property set", name))
}
// verify all required columns exist
validationErrors = t.validateRequiredColumns(requiredColumns)
// validated list and get config
// NOTE: this also sets key column require and operators to default value if not specified
validationErrors = append(validationErrors, t.validateListAndGetConfig()...)
// verify hydrate dependencies are valid
// the map entries are strings - ensure they correspond to actual functions
validationErrors = append(validationErrors, t.validateHydrateDependencies()...)
validationErrors = append(validationErrors, t.DefaultRetryConfig.Validate(t)...)
validationErrors = append(validationErrors, t.DefaultIgnoreConfig.Validate(t)...)
for _, h := range t.hydrateConfigMap {
validationErrors = append(validationErrors, h.Validate(t)...)
}
return validationErrors
}
func (t *Table) validateRequiredColumns(requiredColumns []*Column) []string {
var validationErrors []string
if len(requiredColumns) > 0 {
for _, requiredColumn := range requiredColumns {
// get column def from this t
c := t.getColumn(requiredColumn.Name)
if c == nil {
validationErrors = append(validationErrors, fmt.Sprintf("table '%s' does not implement required column '%s'", t.Name, requiredColumn.Name))
} else if c.Type != requiredColumn.Type {
validationErrors = append(validationErrors, fmt.Sprintf("table '%s' required column '%s' should be type '%s' but is type '%s'", t.Name, requiredColumn.Name, columnTypeToString(requiredColumn.Type), columnTypeToString(c.Type)))
}
}
}
return validationErrors
}
func columnTypeToString(columnType proto.ColumnType) string {
switch columnType {
case proto.ColumnType_BOOL:
return "ColumnType_BOOL"
case proto.ColumnType_INT:
return "ColumnType_INT"
case proto.ColumnType_DOUBLE:
return "ColumnType_DOUBLE"
case proto.ColumnType_STRING:
return "ColumnType_STRING"
case proto.ColumnType_JSON:
return "ColumnType_BOOL"
case proto.ColumnType_DATETIME:
return "ColumnType_DATETIME"
case proto.ColumnType_IPADDR:
return "ColumnType_IPADDR"
case proto.ColumnType_CIDR:
return "ColumnType_CIDR"
case proto.ColumnType_INET:
return "ColumnType_INET"
case proto.ColumnType_TIMESTAMP:
return "ColumnType_TIMESTAMP"
case proto.ColumnType_LTREE:
return "ColumnType_LTREE"
default:
return fmt.Sprintf("Unknown column type: %v", columnType)
}
}
// validate list and get config
// NOTE: this initialises key column properties to their defaults
func (t *Table) validateListAndGetConfig() []string {
var validationErrors []string
// either get or list must be defined
if t.List == nil && t.Get == nil {
validationErrors = append(validationErrors, fmt.Sprintf("table '%s' does not have either GetConfig or ListConfig - one of these must be provided", t.Name))
}
if t.Get != nil {
validationErrors = append(validationErrors, t.Get.Validate(t)...)
}
if t.List != nil {
validationErrors = append(validationErrors, t.List.Validate(t)...)
}
// verify any key columns defined for GET only use '=' operators
// also set key column require and operators to default value if not specified
validationErrors = append(validationErrors, t.validateKeyColumns()...)
return validationErrors
}
func (t *Table) validateHydrateDependencies() []string {
// only 1 of HydrateDependencies and HydrateConfig) may be set
if len(t.HydrateDependencies) != 0 && len(t.HydrateConfig) != 0 {
return []string{fmt.Sprintf("table '%s' defines both HydrateDependencies and HydrateConfig", t.Name)}
}
var validationErrors []string
if cyclicDependencyError := t.detectCyclicHydrateDependencies(); cyclicDependencyError != "" {
validationErrors = append(validationErrors, cyclicDependencyError)
}
return validationErrors
}
func (t *Table) detectCyclicHydrateDependencies() string {
var dependencyGraph = topsort.NewGraph()
dependencyGraph.AddNode("root")
updateDependencyGraph := func(hydrateFunc HydrateFunc, hydrateDepends []HydrateFunc) {
name := helpers.GetFunctionName(hydrateFunc)
if !dependencyGraph.ContainsNode(name) {
dependencyGraph.AddNode(name)
}
dependencyGraph.AddEdge("root", name)
for _, dep := range hydrateDepends {
depName := helpers.GetFunctionName(dep)
if !dependencyGraph.ContainsNode(depName) {
dependencyGraph.AddNode(depName)
}
dependencyGraph.AddEdge(name, depName)
}
}
for _, hydrateConfig := range t.hydrateConfigMap {
updateDependencyGraph(hydrateConfig.Func, hydrateConfig.Depends)
}
if _, err := dependencyGraph.TopSort("root"); err != nil {
return strings.Replace(err.Error(), "Cycle error", "Hydration dependencies contains cycle: ", 1)
}
return ""
}
// validate key columns
// also set key column require and operators to default value if not specified
func (t *Table) validateKeyColumns() []string {
// get key columns should only have equals operators
var getValidationErrors []string
var listValidationErrors []string
if t.Get != nil && len(t.Get.KeyColumns) > 0 {
getValidationErrors = t.Get.KeyColumns.Validate()
if !t.Get.KeyColumns.AllEquals() {
getValidationErrors = append(getValidationErrors, fmt.Sprintf("table '%s' Get key columns must only use '=' operators", t.Name))
}
// ensure all key columns actually exist
getValidationErrors = append(getValidationErrors, t.ValidateColumnsExist(t.Get.KeyColumns)...)
if len(getValidationErrors) > 0 {
getValidationErrors = append([]string{fmt.Sprintf("table '%s' has an invalid Get config:", t.Name)}, helpers.TabifyStringSlice(getValidationErrors, " - ")...)
}
}
if t.List != nil && len(t.List.KeyColumns) > 0 {
listValidationErrors = t.List.KeyColumns.Validate()
if len(listValidationErrors) > 0 {
listValidationErrors = append([]string{fmt.Sprintf("table '%s' has an invalid List config:", t.Name)}, helpers.TabifyStringSlice(listValidationErrors, " - ")...)
}
// ensure all key columns actually exist
listValidationErrors = append(listValidationErrors, t.ValidateColumnsExist(t.List.KeyColumns)...)
}
return append(getValidationErrors, listValidationErrors...)
}
func (t *Table) ValidateColumnsExist(keyColumns KeyColumnSlice) []string {
var res []string
for _, c := range keyColumns {
if t.getColumn(c.Name) == nil {
res = append(res, fmt.Sprintf("key column '%s' does not exist in table '%s'", c.Name, t.Name))
}
}
return res
}