github.com/kaptinlin/jsonschema@v0.4.6/enum.go (about)

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  )
     8  
     9  // valuesEqual checks if two values are equal, handling type conversions for numeric types
    10  func valuesEqual(a, b interface{}) bool {
    11  	// Try direct comparison first
    12  	if reflect.DeepEqual(a, b) {
    13  		return true
    14  	}
    15  
    16  	// Handle numeric type conversions
    17  	va := reflect.ValueOf(a)
    18  	vb := reflect.ValueOf(b)
    19  
    20  	// If both are numeric, convert to float64 for comparison
    21  	if isNumeric(va) && isNumeric(vb) {
    22  		fa, ok1 := toFloat64(a)
    23  		fb, ok2 := toFloat64(b)
    24  		if ok1 && ok2 {
    25  			return fa == fb
    26  		}
    27  	}
    28  
    29  	return false
    30  }
    31  
    32  // isNumeric checks if a reflect.Value represents a numeric type
    33  func isNumeric(v reflect.Value) bool {
    34  	switch v.Kind() {
    35  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
    36  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
    37  		reflect.Float32, reflect.Float64:
    38  		return true
    39  	case reflect.Invalid, reflect.Bool, reflect.Uintptr,
    40  		reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan,
    41  		reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr,
    42  		reflect.Slice, reflect.String, reflect.Struct, reflect.UnsafePointer:
    43  		return false
    44  	default:
    45  		return false
    46  	}
    47  }
    48  
    49  // toFloat64 converts various numeric types to float64
    50  func toFloat64(value interface{}) (float64, bool) {
    51  	switch v := value.(type) {
    52  	case int:
    53  		return float64(v), true
    54  	case int8:
    55  		return float64(v), true
    56  	case int16:
    57  		return float64(v), true
    58  	case int32:
    59  		return float64(v), true
    60  	case int64:
    61  		return float64(v), true
    62  	case uint:
    63  		return float64(v), true
    64  	case uint8:
    65  		return float64(v), true
    66  	case uint16:
    67  		return float64(v), true
    68  	case uint32:
    69  		return float64(v), true
    70  	case uint64:
    71  		return float64(v), true
    72  	case float32:
    73  		return float64(v), true
    74  	case float64:
    75  		return v, true
    76  	default:
    77  		return 0, false
    78  	}
    79  }
    80  
    81  // EvaluateEnum checks if the data's value matches one of the enumerated values specified in the schema.
    82  // According to the JSON Schema Draft 2020-12:
    83  //   - The value of the "enum" keyword must be an array.
    84  //   - This array should have at least one element, and all elements should be unique.
    85  //   - An instance validates successfully against this keyword if its value is equal to one of the elements in the array.
    86  //   - Elements in the array might be of any type, including null.
    87  //
    88  // This method ensures that the data instance conforms to the enumerated values defined in the schema.
    89  // If the instance does not match any of the enumerated values, it returns a EvaluationError detailing the allowed values.
    90  //
    91  // Reference: https://json-schema.org/draft/2020-12/json-schema-validation#name-enum
    92  func evaluateEnum(schema *Schema, instance interface{}) *EvaluationError {
    93  	if len(schema.Enum) == 0 {
    94  		return nil // No enum values, so no validation needed
    95  	}
    96  
    97  	allowed := make([]string, 0, len(schema.Enum))
    98  
    99  	for _, enumValue := range schema.Enum {
   100  		if valuesEqual(instance, enumValue) {
   101  			return nil // Match found.
   102  		}
   103  
   104  		allowed = append(allowed, fmt.Sprintf("%v", enumValue))
   105  	}
   106  
   107  	// No match found.
   108  	return NewEvaluationError("enum", "value_not_in_enum", "Value {received} should be one of the allowed values: {expected}", map[string]interface{}{
   109  		"expected": strings.Join(allowed, ", "),
   110  		"received": instance,
   111  	})
   112  }