-
Notifications
You must be signed in to change notification settings - Fork 0
/
parameterValidator.go
156 lines (139 loc) · 4.95 KB
/
parameterValidator.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
package parameterValidator
import (
_ "embed" // Required to embed the validator JS code
"fmt"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
"github.com/exasol/extension-manager/pkg/extensionAPI"
)
//go:generate npm ci
//go:generate npm run build
//go:embed parameterValidator.js
var dependencyValidatorJs string
type ParameterDefinition struct {
Id string
Name string
RawDefinition map[string]interface{}
}
func ConvertDefinitions(rawDefinitions []interface{}) ([]ParameterDefinition, error) {
definitions := make([]ParameterDefinition, 0, len(rawDefinitions))
for _, d := range rawDefinitions {
if def, ok := d.(map[string]interface{}); ok {
convertedDef, err := convertDefinition(def)
if err != nil {
return nil, err
}
definitions = append(definitions, convertedDef)
} else {
return nil, fmt.Errorf("unexpected type %T of definition: %v, expected map[string]interface{}", d, d)
}
}
return definitions, nil
}
func convertDefinition(rawDefinition map[string]interface{}) (ParameterDefinition, error) {
id, name, err := extractValues(rawDefinition)
if err != nil {
return ParameterDefinition{}, err
}
return ParameterDefinition{Id: id, Name: name, RawDefinition: rawDefinition}, nil
}
func extractValues(def map[string]interface{}) (id, name string, err error) {
id, err = extractStringValue(def, "id")
if err != nil {
return "", "", err
}
name, err = extractStringValue(def, "name")
if err != nil {
return "", "", err
}
return id, name, nil
}
func extractStringValue(def map[string]interface{}, key string) (string, error) {
if _, ok := def[key]; !ok {
return "", fmt.Errorf("entry %q missing in parameter definition %v", key, def)
}
if value, ok := def[key].(string); ok {
return value, nil
}
return "", fmt.Errorf("unexpected type of key %q in parameter definition: %T, expected string", key, def[key])
}
type ValidationResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type Validator struct {
validate func(definition interface{}, value string) ValidationResult
}
// New creates a new reusable validator.
/* [impl -> dsn~reuse-parameter-validation-rules~1] */
/* [impl -> dsn~parameter-validation-rules-simple~1]. */
func New() (*Validator, error) {
vm := newJavaScriptVm()
globalJsObj := vm.NewObject()
err := vm.Set("global", globalJsObj)
if err != nil {
return nil, err
}
_, err = vm.RunString(dependencyValidatorJs)
if err != nil {
return nil, fmt.Errorf("failed to load validateParameter script. Cause: %w", err)
}
function := globalJsObj.Get("validateParameter")
//nolint:exhaustruct // Omitting values by intention for deserialization
validator := Validator{}
err = vm.ExportTo(function, &validator.validate)
if err != nil {
return nil, err
}
return &validator, nil
}
// ValidateParameters validates parameter values against the parameter definition and returns a list of failed validations.
// If all parameters are valid, this returns an empty slice.
/* [impl -> dsn~validate-parameters~1] */
/* [impl -> dsn~parameter-definitions~1]. */
func (v *Validator) ValidateParameters(definitions []ParameterDefinition, params extensionAPI.ParameterValues) (failedValidations []ValidationResult, err error) {
result := make([]ValidationResult, 0)
for _, def := range definitions {
name, id, validationResult, err := v.validateParameter(def, params)
if err != nil {
return nil, err
}
if !validationResult.Success {
result = append(result, ValidationResult{Success: false, Message: fmt.Sprintf("Failed to validate parameter '%s' (%s): %s", name, id, validationResult.Message)})
}
}
return result, nil
}
func (v *Validator) validateParameter(def ParameterDefinition, params extensionAPI.ParameterValues) (paramName, paramId string, validationResult *ValidationResult, validationError error) {
paramValue := findParamValue(params, def.Id)
result, err := v.ValidateParameter(def, paramValue)
if err != nil {
return "", "", nil, fmt.Errorf("failed to validate parameter value %q with id %q using definition %v", paramValue, def.Id, def.RawDefinition)
}
return def.Name, def.Id, result, nil
}
func findParamValue(params extensionAPI.ParameterValues, id string) string {
if param, found := params.Find(id); found {
return param.Value
}
return ""
}
// ValidateParameters uses the given parameter definition to validate a single value.
func (v *Validator) ValidateParameter(def ParameterDefinition, value string) (validationResult *ValidationResult, errorResult error) {
defer func() {
if err := recover(); err != nil {
errorResult = fmt.Errorf("failed to validate parameter value %q using definition %v: %v", value, def, err)
}
}()
result := v.validate(def.RawDefinition, value)
return &result, nil
}
func newJavaScriptVm() *goja.Runtime {
vm := goja.New()
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
registry := new(require.Registry)
registry.Enable(vm)
console.Enable(vm)
return vm
}