<commit_before>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package rdl

import (
	"encoding/json"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

//
// Validatable - if implemented, this can be called directly to validate. Otherwise, introspection is used.
//
type Validatable interface {
	Validate() error
}

// Validation is the result of a call to the validator. When sucessful, the Valid field is
// true, and when not, it is false, with other optional fields providing extra information
type Validation struct {
	Valid   bool        `json:"valid"`
	Type    string      `json:"type,omitempty"`
	Error   string      `json:"error,omitempty"`
	Value   interface{} `json:"value,omitempty"`
	Context string      `json:"context,omitempty"`
}

func (v Validation) String() string {
	data, _ := json.MarshalIndent(v, "", "    ")
	return string(data)
}

type validator struct {
	registry TypeRegistry
	schema   *Schema
}

// Validate tests the provided generic data against a type in the specified schema. If the typename is empty,
// an attempt to guess the type is made, otherwise the check is done against the single type.
func Validate(schema *Schema, typename string, data interface{}) Validation {
	checker := new(validator)
	checker.registry = NewTypeRegistry(schema)
	checker.schema = schema

	if schema.Types == nil {
		return checker.bad("top level", "Schema contains no types", data, "")
	}
	if typename == "" {
		//iterate over the types until we find the most (defined last) general match
		//But: not always useful: if structs are not "closed", they match on almost anything.
		typelist := schema.Types
		for i := len(typelist) - 1; i >= 0; i-- {
			t := typelist[i]
			tName, _, _ := TypeInfo(t)
			v := checker.validate(t, data, string(tName))
			if v.Error == "" {
				return v
			}
		}
		return checker.bad("top level", "Cannot determine type of data in schema", data, "")
	}
	context := typename
	typedef := checker.registry.FindType(TypeRef(typename))
	if typedef != nil {
		return checker.validate(typedef, data, context)
	}
	return checker.bad(context, "No such type", nil, "")
}

func (checker *validator) resolveAliases(typedef *Type, context string) *Type {
	for typedef.Variant == TypeVariantAliasTypeDef {
		typedef = checker.registry.FindType(typedef.AliasTypeDef.Type)
	}
	return typedef
}

func (checker *validator) validate(t *Type, data interface{}, context string) Validation {
	t = checker.resolveAliases(t, context)
	base := checker.registry.BaseType(t)
	switch base {
	case BaseTypeAny:
		return checker.good(t, data)
	case BaseTypeUnion:
		return checker.validateUnion(t.UnionTypeDef, data, context)
	case BaseTypeString:
		return checker.validatePotentialString(t, data, context)
	}
	switch d := data.(type) {
	case bool:
		if base == BaseTypeBool {
			return checker.validateBool(t, d, context)
		}
	case float64:
		//only float64 is set up here, because that is what encoding/json returns for all numbers
		//this would have to be extended to support other sources of data than json
		switch base {
		case BaseTypeFloat64, BaseTypeFloat32:
			return checker.validateFloat(t, d, context)
		case BaseTypeInt32, BaseTypeInt64, BaseTypeInt16, BaseTypeInt8:
			return checker.validateFloat(t, float64(d), context)
		}
	case string:
		switch base {
		case BaseTypeString:
			return checker.validateString(t, d, context)
		case BaseTypeUUID:
			return checker.validateUUID(t, d, context)
		case BaseTypeSymbol:
			return checker.validateSymbol(t, d, context)
		case BaseTypeTimestamp:
			return checker.validateTimestamp(t, d, context)
		case BaseTypeEnum:
			return checker.validateEnum(t, d, context)
		}
	case []interface{}:
		if base == BaseTypeArray {
			return checker.validateArray(t, d, context)
		}
	case map[string]interface{}:
		if base == BaseTypeStruct {
			return checker.validateStruct(t, d, context)
		} else if base == BaseTypeMap {
			return checker.validateMap(t, d, context)
		}
	case map[Symbol]interface{}:
		dd := make(map[string]interface{})
		for k, v := range d {
			dd[string(k)] = v
		}
		if base == BaseTypeStruct {
			return checker.validateStruct(t, dd, context)
		} else if base == BaseTypeMap {
			return checker.validateMap(t, dd, context)
		}
	default:
		v, ok := data.(Validatable)
		if ok {
			err := v.Validate()
			if err == nil {
				return checker.good(t, data)
			}
		}
	}
	return checker.typeMismatchVariant(context, data, t)
}

func (checker *validator) validatePotentialString(t *Type, data interface{}, context string) Validation {
	sdata, ok := data.(string)
	if !ok {
		tName, _, _ := TypeInfo(t)
		return checker.bad(context, "Not a string", sdata, tName)
	}
	return checker.validateString(t, sdata, context)
}

func (checker *validator) validateString(t *Type, data string, context string) Validation {
	switch t.Variant {
	case TypeVariantStringTypeDef:
		typedef := t.StringTypeDef
		if typedef.Values != nil {
			match := false
			for _, v := range typedef.Values {
				if v == data {
					match = true
					break
				}
			}
			if !match {
				return checker.bad(context, "Value mismatch in String type", data, typedef.Name)
			}
		}<commit_msg>Fixed bad string subtype validation problem.<commit_after>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package rdl

import (
	"encoding/json"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

//
// Validatable - if implemented, this can be called directly to validate. Otherwise, introspection is used.
//
type Validatable interface {
	Validate() error
}

// Validation is the result of a call to the validator. When sucessful, the Valid field is
// true, and when not, it is false, with other optional fields providing extra information
type Validation struct {
	Valid   bool        `json:"valid"`
	Type    string      `json:"type,omitempty"`
	Error   string      `json:"error,omitempty"`
	Value   interface{} `json:"value,omitempty"`
	Context string      `json:"context,omitempty"`
}

func (v Validation) String() string {
	data, _ := json.MarshalIndent(v, "", "    ")
	return string(data)
}

type validator struct {
	registry TypeRegistry
	schema   *Schema
}

// Validate tests the provided generic data against a type in the specified schema. If the typename is empty,
// an attempt to guess the type is made, otherwise the check is done against the single type.
func Validate(schema *Schema, typename string, data interface{}) Validation {
	checker := new(validator)
	checker.registry = NewTypeRegistry(schema)
	checker.schema = schema

	if schema.Types == nil {
		return checker.bad("top level", "Schema contains no types", data, "")
	}
	if typename == "" {
		//iterate over the types until we find the most (defined last) general match
		//But: not always useful: if structs are not "closed", they match on almost anything.
		typelist := schema.Types
		for i := len(typelist) - 1; i >= 0; i-- {
			t := typelist[i]
			tName, _, _ := TypeInfo(t)
			v := checker.validate(t, data, string(tName))
			if v.Error == "" {
				return v
			}
		}
		return checker.bad("top level", "Cannot determine type of data in schema", data, "")
	}
	context := typename
	typedef := checker.registry.FindType(TypeRef(typename))
	if typedef != nil {
		return checker.validate(typedef, data, context)
	}
	return checker.bad(context, "No such type", nil, "")
}

func (checker *validator) resolveAliases(typedef *Type, context string) *Type {
	for typedef.Variant == TypeVariantAliasTypeDef {
		typedef = checker.registry.FindType(typedef.AliasTypeDef.Type)
	}
	return typedef
}

func (checker *validator) validate(t *Type, data interface{}, context string) Validation {
	t = checker.resolveAliases(t, context)
	base := checker.registry.BaseType(t)
	switch base {
	case BaseTypeAny:
		return checker.good(t, data)
	case BaseTypeUnion:
		return checker.validateUnion(t.UnionTypeDef, data, context)
	case BaseTypeString:
		return checker.validateString(t, fmt.Sprint(data), context)
	}
	switch d := data.(type) {
	case bool:
		if base == BaseTypeBool {
			return checker.validateBool(t, d, context)
		}
	case float64:
		//only float64 is set up here, because that is what encoding/json returns for all numbers
		//this would have to be extended to support other sources of data than json
		switch base {
		case BaseTypeFloat64, BaseTypeFloat32:
			return checker.validateFloat(t, d, context)
		case BaseTypeInt32, BaseTypeInt64, BaseTypeInt16, BaseTypeInt8:
			return checker.validateFloat(t, float64(d), context)
		}
	case string:
		switch base {
		case BaseTypeString:
			return checker.validateString(t, d, context)
		case BaseTypeUUID:
			return checker.validateUUID(t, d, context)
		case BaseTypeSymbol:
			return checker.validateSymbol(t, d, context)
		case BaseTypeTimestamp:
			return checker.validateTimestamp(t, d, context)
		case BaseTypeEnum:
			return checker.validateEnum(t, d, context)
		}
	case []interface{}:
		if base == BaseTypeArray {
			return checker.validateArray(t, d, context)
		}
	case map[string]interface{}:
		if base == BaseTypeStruct {
			return checker.validateStruct(t, d, context)
		} else if base == BaseTypeMap {
			return checker.validateMap(t, d, context)
		}
	case map[Symbol]interface{}:
		dd := make(map[string]interface{})
		for k, v := range d {
			dd[string(k)] = v
		}
		if base == BaseTypeStruct {
			return checker.validateStruct(t, dd, context)
		} else if base == BaseTypeMap {
			return checker.validateMap(t, dd, context)
		}
	default:
		v, ok := data.(Validatable)
		if ok {
			err := v.Validate()
			if err == nil {
				return checker.good(t, data)
			}
		}
	}
	return checker.typeMismatchVariant(context, data, t)
}

func (checker *validator) validateString(t *Type, data string, context string) Validation {
	switch t.Variant {
	case TypeVariantStringTypeDef:
		typedef := t.StringTypeDef
		if typedef.Values != nil {
			match := false
			for _, v := range typedef.Values {
				if v == data {
					match = true
					break
				}
			}
			if !match {
				return checker.bad(context, "Value mismatch in String type", data, typedef.Name)
			}
		}
{'fraction_non_alphanumeric': 0.11127523387493846, 'fraction_numerical': 0.004923682914820286, 'mean_word_length': 3.85933014354067, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6092234', 'n_tokens_mistral': 3493, 'n_tokens_neox': 3162, 'n_words': 1047}