databricks-cli/libs/jsonschema/utils.go

186 lines
4.4 KiB
Go
Raw Permalink Normal View History

package jsonschema
import (
"errors"
"fmt"
"regexp"
"strconv"
)
// This error indicates an failure to parse a string as a particular JSON schema type.
type parseStringError struct {
// Expected JSON schema type for the value
ExpectedType Type
// The string value that failed to parse
Value string
}
func (e parseStringError) Error() string {
return fmt.Sprintf("%q is not a %s", e.Value, e.ExpectedType)
}
// function to check whether a float value represents an integer
func isIntegerValue(v float64) bool {
return v == float64(int64(v))
}
func toInteger(v any) (int64, error) {
switch typedVal := v.(type) {
// cast float to int
case float32:
if !isIntegerValue(float64(typedVal)) {
return 0, fmt.Errorf("expected integer value, got: %v", v)
}
return int64(typedVal), nil
case float64:
if !isIntegerValue(typedVal) {
return 0, fmt.Errorf("expected integer value, got: %v", v)
}
return int64(typedVal), nil
// pass through common integer cases
case int:
return int64(typedVal), nil
case int32:
return int64(typedVal), nil
case int64:
return typedVal, nil
default:
return 0, fmt.Errorf("cannot convert %#v to an integer", v)
}
}
func toString(v any, T Type) (string, error) {
switch T {
case BooleanType:
boolVal, ok := v.(bool)
if !ok {
return "", fmt.Errorf("expected bool, got: %#v", v)
}
return strconv.FormatBool(boolVal), nil
case StringType:
strVal, ok := v.(string)
if !ok {
return "", fmt.Errorf("expected string, got: %#v", v)
}
return strVal, nil
case NumberType:
floatVal, ok := v.(float64)
if !ok {
return "", fmt.Errorf("expected float, got: %#v", v)
}
return strconv.FormatFloat(floatVal, 'f', -1, 64), nil
case IntegerType:
intVal, err := toInteger(v)
if err != nil {
return "", err
}
return strconv.FormatInt(intVal, 10), nil
case ArrayType, ObjectType:
return "", fmt.Errorf("cannot format object of type %s as a string. Value of object: %#v", T, v)
default:
return "", fmt.Errorf("unknown json schema type: %q", T)
}
}
func toStringSlice(arr []any, T Type) ([]string, error) {
res := []string{}
for _, v := range arr {
s, err := toString(v, T)
if err != nil {
return nil, err
}
res = append(res, s)
}
return res, nil
}
func fromString(s string, T Type) (any, error) {
if T == StringType {
return s, nil
}
// Variables to store value and error from parsing
var v any
var err error
switch T {
case BooleanType:
v, err = strconv.ParseBool(s)
case NumberType:
v, err = strconv.ParseFloat(s, 32)
case IntegerType:
v, err = strconv.ParseInt(s, 10, 64)
case ArrayType, ObjectType:
return "", fmt.Errorf("cannot parse string as object of type %s. Value of string: %q", T, s)
default:
return "", fmt.Errorf("unknown json schema type: %q", T)
}
// Return more readable error incase of a syntax error
if errors.Is(err, strconv.ErrSyntax) {
return nil, parseStringError{
ExpectedType: T,
Value: s,
}
}
return v, err
}
// Error indicates a value entered by the user failed to match the pattern specified
// in the template schema.
type patternMatchError struct {
// The name of the property that failed to match the pattern
PropertyName string
// The value of the property that failed to match the pattern
PropertyValue any
// The regex pattern that the property value failed to match
Pattern string
// Failure message to display to the user, if specified in the template
// schema
FailureMessage string
}
func (e patternMatchError) Error() string {
// If custom user error message is defined, return error with the custom message
msg := e.FailureMessage
if msg == "" {
msg = fmt.Sprintf("Expected to match regex pattern: %s", e.Pattern)
}
return fmt.Sprintf("invalid value for %s: %q. %s", e.PropertyName, e.PropertyValue, msg)
}
func validatePatternMatch(name string, value any, propertySchema *Schema) error {
if propertySchema.Pattern == "" {
// Return early if no pattern is specified
return nil
}
// Expect type of value to be a string
stringValue, ok := value.(string)
if !ok {
return fmt.Errorf("invalid value for %s: %v. Expected a value of type string", name, value)
}
match, err := regexp.MatchString(propertySchema.Pattern, stringValue)
if err != nil {
return err
}
if match {
// successful match
return nil
}
return patternMatchError{
PropertyName: name,
PropertyValue: value,
Pattern: propertySchema.Pattern,
FailureMessage: propertySchema.PatternMatchFailureMessage,
}
}