mirror of https://github.com/databricks/cli.git
Add schema and config validation to jsonschema package (#740)
## Changes At a high level this PR adds new schema validation and moves functionality that should be present in the jsonschema package, but resides in the template package today, to the jsonschema package. This includes for example schema validation, schema instance validation, to / from string conversion methods etc. The list below outlines all the pieces that have been moved over, and the new validation bits added. This PR: 1. Adds casting default value of schema properties to integers to the jsonschema.Load method. 2. Adds validation for default value types for schema properties, checking they are consistant with the type defined. 3. Introduces the LoadInstance and ValidateInstance methods to the json schema package. These methods can be used to read and validate JSON documents against the schema. 4. Replaces validation done for template inputs to use the newly defined JSON schema validation functions. 5. Moves to/from string and isInteger utility methods to the json schema package. ## Tests Existing and new unit tests.
This commit is contained in:
parent
10e0836749
commit
1a7bf4e4f1
|
@ -0,0 +1,91 @@
|
||||||
|
package jsonschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load a JSON document and validate it against the JSON schema. Instance here
|
||||||
|
// refers to a JSON document. see: https://json-schema.org/draft/2020-12/json-schema-core.html#name-instance
|
||||||
|
func (s *Schema) LoadInstance(path string) (map[string]any, error) {
|
||||||
|
instance := make(map[string]any)
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &instance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default JSON unmarshaler parses untyped number values as float64.
|
||||||
|
// We convert integer properties from float64 to int64 here.
|
||||||
|
for name, v := range instance {
|
||||||
|
propertySchema, ok := s.Properties[name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if propertySchema.Type != IntegerType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
integerValue, err := toInteger(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse property %s: %w", name, err)
|
||||||
|
}
|
||||||
|
instance[name] = integerValue
|
||||||
|
}
|
||||||
|
return instance, s.ValidateInstance(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schema) ValidateInstance(instance map[string]any) error {
|
||||||
|
if err := s.validateAdditionalProperties(instance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.validateRequired(instance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.validateTypes(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If additional properties is set to false, this function validates instance only
|
||||||
|
// contains properties defined in the schema.
|
||||||
|
func (s *Schema) validateAdditionalProperties(instance map[string]any) error {
|
||||||
|
// Note: AdditionalProperties has the type any.
|
||||||
|
if s.AdditionalProperties != false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for k := range instance {
|
||||||
|
_, ok := s.Properties[k]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("property %s is not defined in the schema", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function validates that all require properties in the schema have values
|
||||||
|
// in the instance.
|
||||||
|
func (s *Schema) validateRequired(instance map[string]any) error {
|
||||||
|
for _, name := range s.Required {
|
||||||
|
if _, ok := instance[name]; !ok {
|
||||||
|
return fmt.Errorf("no value provided for required property %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the types of all input properties values match their types defined in the schema
|
||||||
|
func (s *Schema) validateTypes(instance map[string]any) error {
|
||||||
|
for k, v := range instance {
|
||||||
|
fieldInfo, ok := s.Properties[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := validateType(v, fieldInfo.Type)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect type for property %s: %w", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package jsonschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateInstanceAdditionalPropertiesPermitted(t *testing.T) {
|
||||||
|
instance := map[string]any{
|
||||||
|
"int_val": 1,
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
"an_additional_property": "abc",
|
||||||
|
}
|
||||||
|
|
||||||
|
schema, err := Load("./testdata/instance-validate/test-schema.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = schema.validateAdditionalProperties(instance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = schema.ValidateInstance(instance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateInstanceAdditionalPropertiesForbidden(t *testing.T) {
|
||||||
|
instance := map[string]any{
|
||||||
|
"int_val": 1,
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
"an_additional_property": "abc",
|
||||||
|
}
|
||||||
|
|
||||||
|
schema, err := Load("./testdata/instance-validate/test-schema-no-additional-properties.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = schema.validateAdditionalProperties(instance)
|
||||||
|
assert.EqualError(t, err, "property an_additional_property is not defined in the schema")
|
||||||
|
|
||||||
|
err = schema.ValidateInstance(instance)
|
||||||
|
assert.EqualError(t, err, "property an_additional_property is not defined in the schema")
|
||||||
|
|
||||||
|
instanceWOAdditionalProperties := map[string]any{
|
||||||
|
"int_val": 1,
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = schema.validateAdditionalProperties(instanceWOAdditionalProperties)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = schema.ValidateInstance(instanceWOAdditionalProperties)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateInstanceTypes(t *testing.T) {
|
||||||
|
schema, err := Load("./testdata/instance-validate/test-schema.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validInstance := map[string]any{
|
||||||
|
"int_val": 1,
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = schema.validateTypes(validInstance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = schema.ValidateInstance(validInstance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
invalidInstance := map[string]any{
|
||||||
|
"int_val": "abc",
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = schema.validateTypes(invalidInstance)
|
||||||
|
assert.EqualError(t, err, "incorrect type for property int_val: expected type integer, but value is \"abc\"")
|
||||||
|
|
||||||
|
err = schema.ValidateInstance(invalidInstance)
|
||||||
|
assert.EqualError(t, err, "incorrect type for property int_val: expected type integer, but value is \"abc\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateInstanceRequired(t *testing.T) {
|
||||||
|
schema, err := Load("./testdata/instance-validate/test-schema-some-fields-required.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validInstance := map[string]any{
|
||||||
|
"int_val": 1,
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
}
|
||||||
|
err = schema.validateRequired(validInstance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = schema.ValidateInstance(validInstance)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
invalidInstance := map[string]any{
|
||||||
|
"string_val": "abc",
|
||||||
|
"float_val": 1.0,
|
||||||
|
"bool_val": false,
|
||||||
|
}
|
||||||
|
err = schema.validateRequired(invalidInstance)
|
||||||
|
assert.EqualError(t, err, "no value provided for required property int_val")
|
||||||
|
err = schema.ValidateInstance(invalidInstance)
|
||||||
|
assert.EqualError(t, err, "no value provided for required property int_val")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadInstance(t *testing.T) {
|
||||||
|
schema, err := Load("./testdata/instance-validate/test-schema.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Expect the instance to be loaded successfully.
|
||||||
|
instance, err := schema.LoadInstance("./testdata/instance-load/valid-instance.json")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]any{
|
||||||
|
"bool_val": false,
|
||||||
|
"int_val": int64(1),
|
||||||
|
"string_val": "abc",
|
||||||
|
"float_val": 2.0,
|
||||||
|
}, instance)
|
||||||
|
|
||||||
|
// Expect instance validation against the schema to fail.
|
||||||
|
_, err = schema.LoadInstance("./testdata/instance-load/invalid-type-instance.json")
|
||||||
|
assert.EqualError(t, err, "incorrect type for property string_val: expected type string, but value is 123")
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (schema *Schema) validate() error {
|
func (schema *Schema) validate() error {
|
||||||
|
// Validate property types are all valid JSON schema types.
|
||||||
for _, v := range schema.Properties {
|
for _, v := range schema.Properties {
|
||||||
switch v.Type {
|
switch v.Type {
|
||||||
case NumberType, BooleanType, StringType, IntegerType:
|
case NumberType, BooleanType, StringType, IntegerType:
|
||||||
|
@ -72,6 +73,17 @@ func (schema *Schema) validate() error {
|
||||||
return fmt.Errorf("type %s is not a recognized json schema type", v.Type)
|
return fmt.Errorf("type %s is not a recognized json schema type", v.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate default property values are consistent with types.
|
||||||
|
for name, property := range schema.Properties {
|
||||||
|
if property.Default == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := validateType(property.Default, property.Type); err != nil {
|
||||||
|
return fmt.Errorf("type validation for default value of property %s failed: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,5 +97,25 @@ func Load(path string) (*Schema, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the default values of top-level properties to integers.
|
||||||
|
// This is required because the default JSON unmarshaler parses numbers
|
||||||
|
// as floats when the Golang field it's being loaded to is untyped.
|
||||||
|
//
|
||||||
|
// NOTE: properties can be recursively defined in a schema, but the current
|
||||||
|
// use-cases only uses the first layer of properties so we skip converting
|
||||||
|
// any recursive properties.
|
||||||
|
for name, property := range schema.Properties {
|
||||||
|
if property.Type != IntegerType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if property.Default != nil {
|
||||||
|
property.Default, err = toInteger(property.Default)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse default value for property %s: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return schema, schema.validate()
|
return schema, schema.validate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJsonSchemaValidate(t *testing.T) {
|
func TestSchemaValidateTypeNames(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
toSchema := func(s string) *Schema {
|
toSchema := func(s string) *Schema {
|
||||||
return &Schema{
|
return &Schema{
|
||||||
|
@ -42,3 +42,40 @@ func TestJsonSchemaValidate(t *testing.T) {
|
||||||
err = toSchema("foobar").validate()
|
err = toSchema("foobar").validate()
|
||||||
assert.EqualError(t, err, "type foobar is not a recognized json schema type")
|
assert.EqualError(t, err, "type foobar is not a recognized json schema type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSchemaLoadIntegers(t *testing.T) {
|
||||||
|
schema, err := Load("./testdata/schema-load-int/schema-valid.json")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), schema.Properties["abc"].Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemaLoadIntegersWithInvalidDefault(t *testing.T) {
|
||||||
|
_, err := Load("./testdata/schema-load-int/schema-invalid-default.json")
|
||||||
|
assert.EqualError(t, err, "failed to parse default value for property abc: expected integer value, got: 1.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemaValidateDefaultType(t *testing.T) {
|
||||||
|
invalidSchema := &Schema{
|
||||||
|
Properties: map[string]*Schema{
|
||||||
|
"foo": {
|
||||||
|
Type: "number",
|
||||||
|
Default: "abc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := invalidSchema.validate()
|
||||||
|
assert.EqualError(t, err, "type validation for default value of property foo failed: expected type float, but value is \"abc\"")
|
||||||
|
|
||||||
|
validSchema := &Schema{
|
||||||
|
Properties: map[string]*Schema{
|
||||||
|
"foo": {
|
||||||
|
Type: "boolean",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validSchema.validate()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"int_val": 1,
|
||||||
|
"bool_val": false,
|
||||||
|
"string_val": 123,
|
||||||
|
"float_val": 3.0
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"int_val": 1,
|
||||||
|
"bool_val": false,
|
||||||
|
"string_val": "abc",
|
||||||
|
"float_val": 2.0
|
||||||
|
}
|
19
libs/jsonschema/testdata/instance-validate/test-schema-no-additional-properties.json
vendored
Normal file
19
libs/jsonschema/testdata/instance-validate/test-schema-no-additional-properties.json
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"int_val": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 123
|
||||||
|
},
|
||||||
|
"float_val": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bool_val": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"string_val": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "abc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
19
libs/jsonschema/testdata/instance-validate/test-schema-some-fields-required.json
vendored
Normal file
19
libs/jsonschema/testdata/instance-validate/test-schema-some-fields-required.json
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"int_val": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 123
|
||||||
|
},
|
||||||
|
"float_val": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bool_val": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"string_val": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "abc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["int_val", "float_val", "bool_val"]
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"int_val": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 123
|
||||||
|
},
|
||||||
|
"float_val": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bool_val": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"string_val": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"abc": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"abc": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
package template
|
package jsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// function to check whether a float value represents an integer
|
// function to check whether a float value represents an integer
|
||||||
|
@ -40,41 +38,41 @@ func toInteger(v any) (int64, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toString(v any, T jsonschema.Type) (string, error) {
|
func ToString(v any, T Type) (string, error) {
|
||||||
switch T {
|
switch T {
|
||||||
case jsonschema.BooleanType:
|
case BooleanType:
|
||||||
boolVal, ok := v.(bool)
|
boolVal, ok := v.(bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("expected bool, got: %#v", v)
|
return "", fmt.Errorf("expected bool, got: %#v", v)
|
||||||
}
|
}
|
||||||
return strconv.FormatBool(boolVal), nil
|
return strconv.FormatBool(boolVal), nil
|
||||||
case jsonschema.StringType:
|
case StringType:
|
||||||
strVal, ok := v.(string)
|
strVal, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("expected string, got: %#v", v)
|
return "", fmt.Errorf("expected string, got: %#v", v)
|
||||||
}
|
}
|
||||||
return strVal, nil
|
return strVal, nil
|
||||||
case jsonschema.NumberType:
|
case NumberType:
|
||||||
floatVal, ok := v.(float64)
|
floatVal, ok := v.(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("expected float, got: %#v", v)
|
return "", fmt.Errorf("expected float, got: %#v", v)
|
||||||
}
|
}
|
||||||
return strconv.FormatFloat(floatVal, 'f', -1, 64), nil
|
return strconv.FormatFloat(floatVal, 'f', -1, 64), nil
|
||||||
case jsonschema.IntegerType:
|
case IntegerType:
|
||||||
intVal, err := toInteger(v)
|
intVal, err := toInteger(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return strconv.FormatInt(intVal, 10), nil
|
return strconv.FormatInt(intVal, 10), nil
|
||||||
case jsonschema.ArrayType, jsonschema.ObjectType:
|
case ArrayType, ObjectType:
|
||||||
return "", fmt.Errorf("cannot format object of type %s as a string. Value of object: %#v", T, v)
|
return "", fmt.Errorf("cannot format object of type %s as a string. Value of object: %#v", T, v)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unknown json schema type: %q", T)
|
return "", fmt.Errorf("unknown json schema type: %q", T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromString(s string, T jsonschema.Type) (any, error) {
|
func FromString(s string, T Type) (any, error) {
|
||||||
if T == jsonschema.StringType {
|
if T == StringType {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,13 +81,13 @@ func fromString(s string, T jsonschema.Type) (any, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch T {
|
switch T {
|
||||||
case jsonschema.BooleanType:
|
case BooleanType:
|
||||||
v, err = strconv.ParseBool(s)
|
v, err = strconv.ParseBool(s)
|
||||||
case jsonschema.NumberType:
|
case NumberType:
|
||||||
v, err = strconv.ParseFloat(s, 32)
|
v, err = strconv.ParseFloat(s, 32)
|
||||||
case jsonschema.IntegerType:
|
case IntegerType:
|
||||||
v, err = strconv.ParseInt(s, 10, 64)
|
v, err = strconv.ParseInt(s, 10, 64)
|
||||||
case jsonschema.ArrayType, jsonschema.ObjectType:
|
case ArrayType, ObjectType:
|
||||||
return "", fmt.Errorf("cannot parse string as object of type %s. Value of string: %q", T, s)
|
return "", fmt.Errorf("cannot parse string as object of type %s. Value of string: %q", T, s)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unknown json schema type: %q", T)
|
return "", fmt.Errorf("unknown json schema type: %q", T)
|
|
@ -1,10 +1,9 @@
|
||||||
package template
|
package jsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,72 +49,72 @@ func TestTemplateToInteger(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateToString(t *testing.T) {
|
func TestTemplateToString(t *testing.T) {
|
||||||
s, err := toString(true, jsonschema.BooleanType)
|
s, err := ToString(true, BooleanType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "true", s)
|
assert.Equal(t, "true", s)
|
||||||
|
|
||||||
s, err = toString("abc", jsonschema.StringType)
|
s, err = ToString("abc", StringType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "abc", s)
|
assert.Equal(t, "abc", s)
|
||||||
|
|
||||||
s, err = toString(1.1, jsonschema.NumberType)
|
s, err = ToString(1.1, NumberType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "1.1", s)
|
assert.Equal(t, "1.1", s)
|
||||||
|
|
||||||
s, err = toString(2, jsonschema.IntegerType)
|
s, err = ToString(2, IntegerType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "2", s)
|
assert.Equal(t, "2", s)
|
||||||
|
|
||||||
_, err = toString([]string{}, jsonschema.ArrayType)
|
_, err = ToString([]string{}, ArrayType)
|
||||||
assert.EqualError(t, err, "cannot format object of type array as a string. Value of object: []string{}")
|
assert.EqualError(t, err, "cannot format object of type array as a string. Value of object: []string{}")
|
||||||
|
|
||||||
_, err = toString("true", jsonschema.BooleanType)
|
_, err = ToString("true", BooleanType)
|
||||||
assert.EqualError(t, err, "expected bool, got: \"true\"")
|
assert.EqualError(t, err, "expected bool, got: \"true\"")
|
||||||
|
|
||||||
_, err = toString(123, jsonschema.StringType)
|
_, err = ToString(123, StringType)
|
||||||
assert.EqualError(t, err, "expected string, got: 123")
|
assert.EqualError(t, err, "expected string, got: 123")
|
||||||
|
|
||||||
_, err = toString(false, jsonschema.NumberType)
|
_, err = ToString(false, NumberType)
|
||||||
assert.EqualError(t, err, "expected float, got: false")
|
assert.EqualError(t, err, "expected float, got: false")
|
||||||
|
|
||||||
_, err = toString("abc", jsonschema.IntegerType)
|
_, err = ToString("abc", IntegerType)
|
||||||
assert.EqualError(t, err, "cannot convert \"abc\" to an integer")
|
assert.EqualError(t, err, "cannot convert \"abc\" to an integer")
|
||||||
|
|
||||||
_, err = toString("abc", "foobar")
|
_, err = ToString("abc", "foobar")
|
||||||
assert.EqualError(t, err, "unknown json schema type: \"foobar\"")
|
assert.EqualError(t, err, "unknown json schema type: \"foobar\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateFromString(t *testing.T) {
|
func TestTemplateFromString(t *testing.T) {
|
||||||
v, err := fromString("true", jsonschema.BooleanType)
|
v, err := FromString("true", BooleanType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, true, v)
|
assert.Equal(t, true, v)
|
||||||
|
|
||||||
v, err = fromString("abc", jsonschema.StringType)
|
v, err = FromString("abc", StringType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "abc", v)
|
assert.Equal(t, "abc", v)
|
||||||
|
|
||||||
v, err = fromString("1.1", jsonschema.NumberType)
|
v, err = FromString("1.1", NumberType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// Floating point conversions are not perfect
|
// Floating point conversions are not perfect
|
||||||
assert.True(t, (v.(float64)-1.1) < 0.000001)
|
assert.True(t, (v.(float64)-1.1) < 0.000001)
|
||||||
|
|
||||||
v, err = fromString("12345", jsonschema.IntegerType)
|
v, err = FromString("12345", IntegerType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(12345), v)
|
assert.Equal(t, int64(12345), v)
|
||||||
|
|
||||||
v, err = fromString("123", jsonschema.NumberType)
|
v, err = FromString("123", NumberType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, float64(123), v)
|
assert.Equal(t, float64(123), v)
|
||||||
|
|
||||||
_, err = fromString("qrt", jsonschema.ArrayType)
|
_, err = FromString("qrt", ArrayType)
|
||||||
assert.EqualError(t, err, "cannot parse string as object of type array. Value of string: \"qrt\"")
|
assert.EqualError(t, err, "cannot parse string as object of type array. Value of string: \"qrt\"")
|
||||||
|
|
||||||
_, err = fromString("abc", jsonschema.IntegerType)
|
_, err = FromString("abc", IntegerType)
|
||||||
assert.EqualError(t, err, "could not parse \"abc\" as a integer: strconv.ParseInt: parsing \"abc\": invalid syntax")
|
assert.EqualError(t, err, "could not parse \"abc\" as a integer: strconv.ParseInt: parsing \"abc\": invalid syntax")
|
||||||
|
|
||||||
_, err = fromString("1.0", jsonschema.IntegerType)
|
_, err = FromString("1.0", IntegerType)
|
||||||
assert.EqualError(t, err, "could not parse \"1.0\" as a integer: strconv.ParseInt: parsing \"1.0\": invalid syntax")
|
assert.EqualError(t, err, "could not parse \"1.0\" as a integer: strconv.ParseInt: parsing \"1.0\": invalid syntax")
|
||||||
|
|
||||||
_, err = fromString("1.0", "foobar")
|
_, err = FromString("1.0", "foobar")
|
||||||
assert.EqualError(t, err, "unknown json schema type: \"foobar\"")
|
assert.EqualError(t, err, "unknown json schema type: \"foobar\"")
|
||||||
}
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
package template
|
package jsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type validator func(v any) error
|
type validateTypeFunc func(v any) error
|
||||||
|
|
||||||
func validateType(v any, fieldType jsonschema.Type) error {
|
func validateType(v any, fieldType Type) error {
|
||||||
validateFunc, ok := validators[fieldType]
|
validateFunc, ok := validateTypeFuncs[fieldType]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,9 +48,9 @@ func validateInteger(v any) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var validators map[jsonschema.Type]validator = map[jsonschema.Type]validator{
|
var validateTypeFuncs map[Type]validateTypeFunc = map[Type]validateTypeFunc{
|
||||||
jsonschema.StringType: validateString,
|
StringType: validateString,
|
||||||
jsonschema.BooleanType: validateBoolean,
|
BooleanType: validateBoolean,
|
||||||
jsonschema.IntegerType: validateInteger,
|
IntegerType: validateInteger,
|
||||||
jsonschema.NumberType: validateNumber,
|
NumberType: validateNumber,
|
||||||
}
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package template
|
package jsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,53 +76,53 @@ func TestValidatorInt(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateValidateType(t *testing.T) {
|
func TestTemplateValidateType(t *testing.T) {
|
||||||
// assert validation passing
|
// assert validation passing
|
||||||
err := validateType(int(0), jsonschema.IntegerType)
|
err := validateType(int(0), IntegerType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = validateType(int32(1), jsonschema.IntegerType)
|
err = validateType(int32(1), IntegerType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = validateType(int64(1), jsonschema.IntegerType)
|
err = validateType(int64(1), IntegerType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = validateType(float32(1.1), jsonschema.NumberType)
|
err = validateType(float32(1.1), NumberType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = validateType(float64(1.2), jsonschema.NumberType)
|
err = validateType(float64(1.2), NumberType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = validateType(false, jsonschema.BooleanType)
|
err = validateType(false, BooleanType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = validateType("abc", jsonschema.StringType)
|
err = validateType("abc", StringType)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// assert validation failing for integers
|
// assert validation failing for integers
|
||||||
err = validateType(float64(1.2), jsonschema.IntegerType)
|
err = validateType(float64(1.2), IntegerType)
|
||||||
assert.ErrorContains(t, err, "expected type integer, but value is 1.2")
|
assert.ErrorContains(t, err, "expected type integer, but value is 1.2")
|
||||||
err = validateType(true, jsonschema.IntegerType)
|
err = validateType(true, IntegerType)
|
||||||
assert.ErrorContains(t, err, "expected type integer, but value is true")
|
assert.ErrorContains(t, err, "expected type integer, but value is true")
|
||||||
err = validateType("abc", jsonschema.IntegerType)
|
err = validateType("abc", IntegerType)
|
||||||
assert.ErrorContains(t, err, "expected type integer, but value is \"abc\"")
|
assert.ErrorContains(t, err, "expected type integer, but value is \"abc\"")
|
||||||
|
|
||||||
// assert validation failing for floats
|
// assert validation failing for floats
|
||||||
err = validateType(true, jsonschema.NumberType)
|
err = validateType(true, NumberType)
|
||||||
assert.ErrorContains(t, err, "expected type float, but value is true")
|
assert.ErrorContains(t, err, "expected type float, but value is true")
|
||||||
err = validateType("abc", jsonschema.NumberType)
|
err = validateType("abc", NumberType)
|
||||||
assert.ErrorContains(t, err, "expected type float, but value is \"abc\"")
|
assert.ErrorContains(t, err, "expected type float, but value is \"abc\"")
|
||||||
err = validateType(int(1), jsonschema.NumberType)
|
err = validateType(int(1), NumberType)
|
||||||
assert.ErrorContains(t, err, "expected type float, but value is 1")
|
assert.ErrorContains(t, err, "expected type float, but value is 1")
|
||||||
|
|
||||||
// assert validation failing for boolean
|
// assert validation failing for boolean
|
||||||
err = validateType(int(1), jsonschema.BooleanType)
|
err = validateType(int(1), BooleanType)
|
||||||
assert.ErrorContains(t, err, "expected type boolean, but value is 1")
|
assert.ErrorContains(t, err, "expected type boolean, but value is 1")
|
||||||
err = validateType(float64(1), jsonschema.BooleanType)
|
err = validateType(float64(1), BooleanType)
|
||||||
assert.ErrorContains(t, err, "expected type boolean, but value is 1")
|
assert.ErrorContains(t, err, "expected type boolean, but value is 1")
|
||||||
err = validateType("abc", jsonschema.BooleanType)
|
err = validateType("abc", BooleanType)
|
||||||
assert.ErrorContains(t, err, "expected type boolean, but value is \"abc\"")
|
assert.ErrorContains(t, err, "expected type boolean, but value is \"abc\"")
|
||||||
|
|
||||||
// assert validation failing for string
|
// assert validation failing for string
|
||||||
err = validateType(int(1), jsonschema.StringType)
|
err = validateType(int(1), StringType)
|
||||||
assert.ErrorContains(t, err, "expected type string, but value is 1")
|
assert.ErrorContains(t, err, "expected type string, but value is 1")
|
||||||
err = validateType(float64(1), jsonschema.StringType)
|
err = validateType(float64(1), StringType)
|
||||||
assert.ErrorContains(t, err, "expected type string, but value is 1")
|
assert.ErrorContains(t, err, "expected type string, but value is 1")
|
||||||
err = validateType(false, jsonschema.StringType)
|
err = validateType(false, StringType)
|
||||||
assert.ErrorContains(t, err, "expected type string, but value is false")
|
assert.ErrorContains(t, err, "expected type string, but value is false")
|
||||||
}
|
}
|
|
@ -2,12 +2,11 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/cmdio"
|
"github.com/databricks/cli/libs/cmdio"
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
"github.com/databricks/cli/libs/jsonschema"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
@ -26,6 +25,9 @@ func newConfig(ctx context.Context, schemaPath string) (*config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not allow template input variables that are not defined in the schema.
|
||||||
|
schema.AdditionalProperties = false
|
||||||
|
|
||||||
// Return config
|
// Return config
|
||||||
return &config{
|
return &config{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -45,32 +47,10 @@ func validateSchema(schema *jsonschema.Schema) error {
|
||||||
|
|
||||||
// Reads json file at path and assigns values from the file
|
// Reads json file at path and assigns values from the file
|
||||||
func (c *config) assignValuesFromFile(path string) error {
|
func (c *config) assignValuesFromFile(path string) error {
|
||||||
// Read the config file
|
// Load the config file.
|
||||||
configFromFile := make(map[string]any, 0)
|
configFromFile, err := c.schema.LoadInstance(path)
|
||||||
b, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to load config from file %s: %w", path, err)
|
||||||
}
|
|
||||||
err = json.Unmarshal(b, &configFromFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast any integer properties, from float to integer. Required because
|
|
||||||
// the json unmarshaller treats all json numbers as floating point
|
|
||||||
for name, floatVal := range configFromFile {
|
|
||||||
property, ok := c.schema.Properties[name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%s is not defined as an input parameter for the template", name)
|
|
||||||
}
|
|
||||||
if property.Type != jsonschema.IntegerType {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v, err := toInteger(floatVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to cast value %v of property %s from file %s to an integer: %w", floatVal, name, path, err)
|
|
||||||
}
|
|
||||||
configFromFile[name] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write configs from the file to the input map, not overwriting any existing
|
// Write configs from the file to the input map, not overwriting any existing
|
||||||
|
@ -91,26 +71,11 @@ func (c *config) assignDefaultValues() error {
|
||||||
if _, ok := c.values[name]; ok {
|
if _, ok := c.values[name]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// No default value defined for the property
|
// No default value defined for the property
|
||||||
if property.Default == nil {
|
if property.Default == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
c.values[name] = property.Default
|
||||||
// Assign default value if property is not an integer
|
|
||||||
if property.Type != jsonschema.IntegerType {
|
|
||||||
c.values[name] = property.Default
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast default value to int before assigning to an integer configuration.
|
|
||||||
// Required because untyped field Default will read all numbers as floats
|
|
||||||
// during unmarshalling
|
|
||||||
v, err := toInteger(property.Default)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to cast default value %v of property %s to an integer: %w", property.Default, name, err)
|
|
||||||
}
|
|
||||||
c.values[name] = v
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -130,7 +95,7 @@ func (c *config) promptForValues() error {
|
||||||
var defaultVal string
|
var defaultVal string
|
||||||
var err error
|
var err error
|
||||||
if property.Default != nil {
|
if property.Default != nil {
|
||||||
defaultVal, err = toString(property.Default, property.Type)
|
defaultVal, err = jsonschema.ToString(property.Default, property.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,7 +108,7 @@ func (c *config) promptForValues() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert user input string back to a value
|
// Convert user input string back to a value
|
||||||
c.values[name], err = fromString(userInput, property.Type)
|
c.values[name], err = jsonschema.FromString(userInput, property.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -163,42 +128,10 @@ func (c *config) promptOrAssignDefaultValues() error {
|
||||||
// Validates the configuration. If passes, the configuration is ready to be used
|
// Validates the configuration. If passes, the configuration is ready to be used
|
||||||
// to initialize the template.
|
// to initialize the template.
|
||||||
func (c *config) validate() error {
|
func (c *config) validate() error {
|
||||||
validateFns := []func() error{
|
// All properties in the JSON schema should have a value defined.
|
||||||
c.validateValuesDefined,
|
c.schema.Required = maps.Keys(c.schema.Properties)
|
||||||
c.validateValuesType,
|
if err := c.schema.ValidateInstance(c.values); err != nil {
|
||||||
}
|
return fmt.Errorf("validation for template input parameters failed. %w", err)
|
||||||
|
|
||||||
for _, fn := range validateFns {
|
|
||||||
err := fn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates all input properties have a user defined value assigned to them
|
|
||||||
func (c *config) validateValuesDefined() error {
|
|
||||||
for k := range c.schema.Properties {
|
|
||||||
if _, ok := c.values[k]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("no value has been assigned to input parameter %s", k)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates the types of all input properties values match their types defined in the schema
|
|
||||||
func (c *config) validateValuesType() error {
|
|
||||||
for k, v := range c.values {
|
|
||||||
fieldInfo, ok := c.schema.Properties[k]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%s is not defined as an input parameter for the template", k)
|
|
||||||
}
|
|
||||||
err := validateType(v, fieldInfo.Type)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("incorrect type for %s. %w", k, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/jsonschema"
|
"github.com/databricks/cli/libs/jsonschema"
|
||||||
|
@ -9,36 +9,14 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testSchema(t *testing.T) *jsonschema.Schema {
|
func testConfig(t *testing.T) *config {
|
||||||
schemaJson := `{
|
c, err := newConfig(context.Background(), "./testdata/config-test-schema/test-schema.json")
|
||||||
"properties": {
|
|
||||||
"int_val": {
|
|
||||||
"type": "integer",
|
|
||||||
"default": 123
|
|
||||||
},
|
|
||||||
"float_val": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"bool_val": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"string_val": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "abc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
var jsonSchema jsonschema.Schema
|
|
||||||
err := json.Unmarshal([]byte(schemaJson), &jsonSchema)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return &jsonSchema
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigAssignValuesFromFile(t *testing.T) {
|
func TestTemplateConfigAssignValuesFromFile(t *testing.T) {
|
||||||
c := config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
|
||||||
values: make(map[string]any),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
|
err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -49,32 +27,17 @@ func TestTemplateConfigAssignValuesFromFile(t *testing.T) {
|
||||||
assert.Equal(t, "hello", c.values["string_val"])
|
assert.Equal(t, "hello", c.values["string_val"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigAssignValuesFromFileForUnknownField(t *testing.T) {
|
|
||||||
c := config{
|
|
||||||
schema: testSchema(t),
|
|
||||||
values: make(map[string]any),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.assignValuesFromFile("./testdata/config-assign-from-file-unknown-property/config.json")
|
|
||||||
assert.EqualError(t, err, "unknown_prop is not defined as an input parameter for the template")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateConfigAssignValuesFromFileForInvalidIntegerValue(t *testing.T) {
|
func TestTemplateConfigAssignValuesFromFileForInvalidIntegerValue(t *testing.T) {
|
||||||
c := config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
|
||||||
values: make(map[string]any),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.assignValuesFromFile("./testdata/config-assign-from-file-invalid-int/config.json")
|
err := c.assignValuesFromFile("./testdata/config-assign-from-file-invalid-int/config.json")
|
||||||
assert.EqualError(t, err, "failed to cast value abc of property int_val from file ./testdata/config-assign-from-file-invalid-int/config.json to an integer: cannot convert \"abc\" to an integer")
|
assert.EqualError(t, err, "failed to load config from file ./testdata/config-assign-from-file-invalid-int/config.json: failed to parse property int_val: cannot convert \"abc\" to an integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *testing.T) {
|
func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *testing.T) {
|
||||||
c := config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
c.values = map[string]any{
|
||||||
values: map[string]any{
|
"string_val": "this-is-not-overwritten",
|
||||||
"string_val": "this-is-not-overwritten",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
|
err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
|
||||||
|
@ -87,10 +50,7 @@ func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *te
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigAssignDefaultValues(t *testing.T) {
|
func TestTemplateConfigAssignDefaultValues(t *testing.T) {
|
||||||
c := config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
|
||||||
values: make(map[string]any),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.assignDefaultValues()
|
err := c.assignDefaultValues()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -101,65 +61,55 @@ func TestTemplateConfigAssignDefaultValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigValidateValuesDefined(t *testing.T) {
|
func TestTemplateConfigValidateValuesDefined(t *testing.T) {
|
||||||
c := config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
c.values = map[string]any{
|
||||||
values: map[string]any{
|
"int_val": 1,
|
||||||
"int_val": 1,
|
"float_val": 1.0,
|
||||||
"float_val": 1.0,
|
"bool_val": false,
|
||||||
"bool_val": false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.validateValuesDefined()
|
err := c.validate()
|
||||||
assert.EqualError(t, err, "no value has been assigned to input parameter string_val")
|
assert.EqualError(t, err, "validation for template input parameters failed. no value provided for required property string_val")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigValidateTypeForValidConfig(t *testing.T) {
|
func TestTemplateConfigValidateTypeForValidConfig(t *testing.T) {
|
||||||
c := &config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
c.values = map[string]any{
|
||||||
values: map[string]any{
|
"int_val": 1,
|
||||||
"int_val": 1,
|
"float_val": 1.1,
|
||||||
"float_val": 1.1,
|
"bool_val": true,
|
||||||
"bool_val": true,
|
"string_val": "abcd",
|
||||||
"string_val": "abcd",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.validateValuesType()
|
err := c.validate()
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = c.validate()
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigValidateTypeForUnknownField(t *testing.T) {
|
func TestTemplateConfigValidateTypeForUnknownField(t *testing.T) {
|
||||||
c := &config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
c.values = map[string]any{
|
||||||
values: map[string]any{
|
"unknown_prop": 1,
|
||||||
"unknown_prop": 1,
|
"int_val": 1,
|
||||||
},
|
"float_val": 1.1,
|
||||||
|
"bool_val": true,
|
||||||
|
"string_val": "abcd",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.validateValuesType()
|
err := c.validate()
|
||||||
assert.EqualError(t, err, "unknown_prop is not defined as an input parameter for the template")
|
assert.EqualError(t, err, "validation for template input parameters failed. property unknown_prop is not defined in the schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateConfigValidateTypeForInvalidType(t *testing.T) {
|
func TestTemplateConfigValidateTypeForInvalidType(t *testing.T) {
|
||||||
c := &config{
|
c := testConfig(t)
|
||||||
schema: testSchema(t),
|
c.values = map[string]any{
|
||||||
values: map[string]any{
|
"int_val": "this-should-be-an-int",
|
||||||
"int_val": "this-should-be-an-int",
|
"float_val": 1.1,
|
||||||
"float_val": 1.1,
|
"bool_val": true,
|
||||||
"bool_val": true,
|
"string_val": "abcd",
|
||||||
"string_val": "abcd",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.validateValuesType()
|
err := c.validate()
|
||||||
assert.EqualError(t, err, `incorrect type for int_val. expected type integer, but value is "this-should-be-an-int"`)
|
assert.EqualError(t, err, "validation for template input parameters failed. incorrect type for property int_val: expected type integer, but value is \"this-should-be-an-int\"")
|
||||||
|
|
||||||
err = c.validate()
|
|
||||||
assert.EqualError(t, err, `incorrect type for int_val. expected type integer, but value is "this-should-be-an-int"`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateValidateSchema(t *testing.T) {
|
func TestTemplateValidateSchema(t *testing.T) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"int_val": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 123
|
||||||
|
},
|
||||||
|
"float_val": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bool_val": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"string_val": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue