package helper
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"time"
)
func AnyToFloat(v any) (float64, error) {
switch v := v.(type) {
case string:
return float64(len(v)), nil
case time.Time:
return float64(v.Unix()), nil
case int:
return float64(v), nil
case int8:
return float64(v), nil
case int16:
return float64(v), nil
case int32:
return float64(v), nil
case int64:
return float64(v), nil
case uint:
return float64(v), nil
case uint8:
return float64(v), nil
case uint16:
return float64(v), nil
case uint32:
return float64(v), nil
case uint64:
return float64(v), nil
case float32:
return float64(v), nil
case float64:
return v, nil
default:
rv := reflect.ValueOf(v)
switch rv.Type().Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
return float64(rv.Len()), nil
default:
return 0, fmt.Errorf("unsupported type for value: %T", v)
}
}
}
func AnyToString(v any) (string, error) {
switch v := v.(type) {
case string:
return v, nil
case bool:
return fmt.Sprintf("%t", v), nil
case time.Time:
return fmt.Sprintf("%d", v.Unix()), nil
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%d", v), nil
case float32, float64:
return fmt.Sprintf("%f", v), nil
default:
return "", fmt.Errorf("unsupported type for value: %T", v)
}
}
func AnyToArrayOfString(v any) ([]string, error) {
if v, ok := v.([]string); ok {
return v, nil
}
rv := reflect.ValueOf(v)
switch rv.Type().Kind() {
case reflect.Array, reflect.Slice:
values := []string{}
rv := reflect.ValueOf(v)
for i := 0; i < rv.Len(); i++ {
avs, err := AnyToString(rv.Index(i).Interface())
if err != nil {
return nil, fmt.Errorf("error converting array value to string: %T", v)
}
values = append(values, avs)
}
return values, nil
case reflect.Map:
keys := []string{}
for _, mk := range reflect.ValueOf(v).MapKeys() {
mks, err := AnyToString(mk.Interface())
if err != nil {
return nil, fmt.Errorf("error converting map key to string: %T", v)
}
keys = append(keys, mks)
}
return keys, nil
default:
return nil, fmt.Errorf("unsupported type for value: %T", v)
}
}
func AnyToType(in any, expected reflect.Type) (out any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error converting interface to type %v: %v", expected, r)
}
}()
switch expected.Kind() {
case reflect.String:
if v, ok := in.(string); ok {
return v, nil
}
case reflect.Bool:
if v, ok := in.(bool); ok {
return v, nil
} else if v, ok := in.(string); ok {
switch v {
// Case on and off are for form values.
case "on":
return any(true), nil
case "off":
return any(false), nil
default:
b, err := strconv.ParseBool(v)
if err != nil {
return nil, fmt.Errorf("error parsing string to bool: %v", err)
}
return any(b), nil
}
}
case reflect.Int:
if v, ok := in.(int); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(int(v)), nil
} else if v, ok := in.(string); ok {
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to int64: %v", err)
}
return any(int(i)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Int8:
if v, ok := in.(int8); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(int8(v)), nil
} else if v, ok := in.(string); ok {
i, err := strconv.ParseInt(v, 10, 8)
if err != nil {
return nil, fmt.Errorf("error parsing string to int8: %v", err)
}
return any(int8(i)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Int16:
if v, ok := in.(int16); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(int16(v)), nil
} else if v, ok := in.(string); ok {
i, err := strconv.ParseInt(v, 10, 16)
if err != nil {
return nil, fmt.Errorf("error parsing string to int16: %v", err)
}
return any(int16(i)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Int32:
if v, ok := in.(int32); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(int32(v)), nil
} else if v, ok := in.(string); ok {
i, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return nil, fmt.Errorf("error parsing string to int32: %v", err)
}
return any(int32(i)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Int64:
if v, ok := in.(int64); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(int64(v)), nil
} else if v, ok := in.(string); ok {
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to int64: %v", err)
}
return any(int64(i)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Uint:
if v, ok := in.(uint); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(uint(v)), nil
} else if v, ok := in.(string); ok {
u, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to uint64: %v", err)
}
return any(uint(u)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Uint8:
if v, ok := in.(uint8); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(uint8(v)), nil
} else if v, ok := in.(string); ok {
u, err := strconv.ParseUint(v, 10, 8)
if err != nil {
return nil, fmt.Errorf("error parsing string to uint8: %v", err)
}
return any(uint8(u)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Uint16:
if v, ok := in.(uint16); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(uint16(v)), nil
} else if v, ok := in.(string); ok {
u, err := strconv.ParseUint(v, 10, 16)
if err != nil {
return nil, fmt.Errorf("error parsing string to uint16: %v", err)
}
return any(uint16(u)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Uint32:
if v, ok := in.(uint32); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(uint32(v)), nil
} else if v, ok := in.(string); ok {
u, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return nil, fmt.Errorf("error parsing string to uint32: %v", err)
}
return any(uint32(u)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Uint64:
if v, ok := in.(uint64); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(uint64(v)), nil
} else if v, ok := in.(string); ok {
u, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to uint64: %v", err)
}
return u, nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Float32:
if v, ok := in.(float32); ok {
return v, nil
} else if v, ok := in.(float64); ok {
return any(float32(v)), nil
} else if v, ok := in.(string); ok {
f, err := strconv.ParseFloat(v, 32)
if err != nil {
return nil, fmt.Errorf("error parsing string to float32: %v", err)
}
return any(float32(f)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Float64:
if v, ok := in.(float64); ok {
return v, nil
} else if v, ok := in.(string); ok {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to float64: %v", err)
}
return any(float64(f)), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Struct:
if v, ok := in.(time.Time); ok {
return v, nil
} else if v, ok := in.(float64); ok {
date, err := UnixStringToTime(strconv.Itoa(int(v)))
if err != nil {
return nil, fmt.Errorf("error parsing float to time.Time: %v", err)
}
return date, nil
} else if v, ok := in.(string); ok {
structTempt := reflect.New(expected).Interface()
if err := json.Unmarshal([]byte(v), structTempt); err != nil {
date, err := UnixStringToTime(v)
if err != nil {
date, err = ISO8601StringToTime(v)
if err != nil {
return nil, fmt.Errorf("error parsing string to struct: %v", err)
}
}
return date, nil
}
return reflect.ValueOf(structTempt).Elem().Interface(), nil
} else if jsonValueMap, ok := in.(map[string]any); ok {
structTemp := reflect.New(expected).Interface()
err = MapJsonMapToStruct(jsonValueMap, structTemp)
if err != nil {
return nil, fmt.Errorf("error parsing map to struct: %v", err)
}
return reflect.ValueOf(structTemp).Elem().Interface(), nil
} else if reflect.TypeOf(in).ConvertibleTo(expected) {
return reflect.ValueOf(in).Convert(expected).Interface(), nil
}
case reflect.Interface:
return in, nil
default:
return nil, fmt.Errorf("unsupported type %T", expected)
}
return nil, fmt.Errorf("unsupported type %T", expected)
}
package helper
import (
"fmt"
"reflect"
)
func ArrayToArrayOfAny(v any) ([]any, error) {
aany := []any{}
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Array && rv.Type().Kind() != reflect.Slice {
return nil, fmt.Errorf("invalid type %v, has to be array or slice", rv.Type().Kind())
}
for i := 0; i < rv.Len(); i++ {
aany = append(aany, rv.Index(i).Interface())
}
return aany, nil
}
func ArrayToArrayOfType(a []any, expectedValue reflect.Type) (reflect.Value, error) {
targetArray := reflect.MakeSlice(reflect.SliceOf(expectedValue), len(a), len(a))
for i, item := range a {
itemConverted, err := AnyToType(item, expectedValue)
if err != nil {
return reflect.Value{}, fmt.Errorf("error converting item at index %d: %v", i, err)
}
targetArray.Index(i).Set(reflect.ValueOf(itemConverted))
}
return targetArray, nil
}
package helper
import (
"fmt"
"reflect"
)
// Checks if the given value is a string.
func IsString(in any) bool {
return reflect.TypeOf(in).Kind() == reflect.String
}
// Checks if the given value is a struct.
func IsStruct(in any) bool {
return reflect.TypeOf(in).Kind() == reflect.Struct
}
// Checks if the given value is an array or slice.
func IsArray(in any) bool {
return reflect.TypeOf(in).Kind() == reflect.Array || reflect.TypeOf(in).Kind() == reflect.Slice
}
// Checks if the given value is an array or slice of structs.
func IsArrayOfStruct(in any) bool {
return IsArray(in) && reflect.TypeOf(in).Elem().Kind() == reflect.Struct
}
// Checks if the given value is an array of maps.
func IsArrayOfMap(in any) bool {
return IsArray(in) && reflect.TypeOf(in).Elem().Kind() == reflect.Map
}
// Checks if the given value is a pointer to a struct.
func CheckValidPointerToStruct(in any) error {
value := reflect.ValueOf(in)
if value.Kind() != reflect.Ptr {
return fmt.Errorf("value has to be of kind pointer, was %T", value)
}
if value.Elem().Kind() != reflect.Struct {
return fmt.Errorf("value has to be of kind struct, was %T", value)
}
return nil
}
package helper
import (
"fmt"
"reflect"
"strings"
)
func ConditionValueToT[T comparable](v T, condition string) (T, error) {
rt := reflect.TypeOf(v)
out, err := AnyToType(condition, rt)
if err != nil {
return v, fmt.Errorf("error converting condition value: %v", err)
}
return out.(T), nil
}
func ConditionValueToArrayOfAny(condition string, expected reflect.Type) ([]any, error) {
conditionList := strings.Split(condition, ",")
if len(conditionList) == 0 || (len(conditionList) == 1 && len(strings.TrimSpace(conditionList[0])) == 0) {
return []any{}, fmt.Errorf("empty condition list %s value", condition)
}
values := []any{}
for _, c := range conditionList {
ct, err := AnyToType(any(c), expected)
if err != nil {
return nil, fmt.Errorf("error converting map key to string: %v", err)
}
values = append(values, any(ct))
}
return values, nil
}
package helper
import (
"fmt"
"reflect"
)
func GetValidMap(in any) (map[string]any, error) {
v, ok := in.(map[string]any)
if ok {
return v, nil
}
return nil, fmt.Errorf("error getting valid map from json")
}
func UnmapStructToJsonMap(structInput any, jsonMapToUpdate *map[string]any) error {
err := CheckValidPointerToStruct(structInput)
if err != nil {
return err
}
structFull := reflect.ValueOf(structInput).Elem()
for i := 0; i < structFull.Type().NumField(); i++ {
field := structFull.Field(i)
fieldType := structFull.Type().Field(i)
fieldKey := fieldType.Name
jsonKey := fieldType.Tag.Get("json")
if len(jsonKey) > 0 {
fieldKey = jsonKey
}
(*jsonMapToUpdate)[fieldKey] = field.Interface()
}
return nil
}
func JsonMapToMapKV(m map[string]any, expectedKey reflect.Type, expectedValue reflect.Type) (reflect.Value, error) {
targetMapValue := reflect.MakeMap(reflect.MapOf(expectedKey, expectedValue))
for key, value := range m {
valueConverted, err := AnyToType(value, expectedValue)
if err != nil {
return reflect.Value{}, fmt.Errorf("error converting value for key %s: %v", key, err)
}
keyConverted, err := AnyToType(key, expectedKey)
if err != nil {
return reflect.Value{}, fmt.Errorf("error converting key %s: %v", key, err)
}
targetMapValue.SetMapIndex(reflect.ValueOf(keyConverted), reflect.ValueOf(valueConverted))
}
return targetMapValue, nil
}
func MapJsonMapToStruct(jsonMapInput map[string]any, structToUpdate any) error {
err := CheckValidPointerToStruct(structToUpdate)
if err != nil {
return err
}
structFull := reflect.ValueOf(structToUpdate).Elem()
for i := 0; i < structFull.Type().NumField(); i++ {
field := structFull.Field(i)
fieldType := structFull.Type().Field(i)
fieldKey := fieldType.Name
jsonKey := fieldType.Tag.Get("json")
if len(jsonKey) > 0 {
fieldKey = jsonKey
}
if jsonValue, ok := jsonMapInput[fieldKey]; ok {
err := SetStructValueByJson(field, jsonValue)
if err != nil {
return fmt.Errorf("could not set field %v (json key: %v) of %v: %v", fieldType.Name, jsonKey, reflect.TypeOf(structToUpdate), err.Error())
}
}
}
return nil
}
func SetStructValueByJson(fv reflect.Value, jsonValue any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error setting struct value: %v", r)
}
}()
if fv.IsValid() && fv.CanSet() {
switch fv.Kind() {
case reflect.String:
var newString string = ""
b, err := AnyToType(jsonValue, reflect.TypeOf(newString))
if err != nil {
return fmt.Errorf("error converting value to string: %v", err)
}
newString = b.(string)
fv.SetString(newString)
case reflect.Bool:
var newBool bool = true
b, err := AnyToType(jsonValue, reflect.TypeOf(newBool))
if err != nil {
return fmt.Errorf("error converting value to bool: %v", err)
}
newBool = b.(bool)
fv.SetBool(newBool)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var newInt int64 = 0
i, err := AnyToType(jsonValue, reflect.TypeOf(newInt))
if err != nil {
return fmt.Errorf("error converting value to int: %v", err)
}
newInt = i.(int64)
if fv.OverflowInt(newInt) {
return fmt.Errorf("cannot set overflowing int")
}
fv.SetInt(newInt)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var newUint uint64 = 0
i, err := AnyToType(jsonValue, reflect.TypeOf(newUint))
if err != nil {
return fmt.Errorf("error converting value to uint: %v", err)
}
newUint = i.(uint64)
if fv.OverflowUint(newUint) {
return fmt.Errorf("cannot set overflowing uint")
}
fv.SetUint(newUint)
case reflect.Float32, reflect.Float64:
var newFloat float64 = 0
i, err := AnyToType(jsonValue, reflect.TypeOf(newFloat))
if err != nil {
return fmt.Errorf("error converting value to float: %v", err)
}
newFloat = i.(float64)
if fv.OverflowFloat(newFloat) {
return fmt.Errorf("cannot set overflowing float")
}
fv.SetFloat(newFloat)
case reflect.Struct:
date, err := AnyToType(jsonValue, fv.Type())
if err != nil {
return err
}
fv.Set(reflect.ValueOf(date))
case reflect.Map:
var mapReflect reflect.Value
if v, ok := jsonValue.(map[string]any); ok {
mapReflect, err = JsonMapToMapKV(v, fv.Type().Key(), fv.Type().Elem())
if err != nil {
return fmt.Errorf("error converting json map to mapKV: %v", err)
}
} else {
mapReflect = reflect.ValueOf(jsonValue)
}
if mapReflect.Type().ConvertibleTo(fv.Type()) {
fv.Set(mapReflect.Convert(fv.Type()))
return nil
} else {
return fmt.Errorf("json map %T is not convertible to type %v", jsonValue, fv.Type())
}
case reflect.Array, reflect.Slice:
if !IsArray(jsonValue) {
return fmt.Errorf("input value has to be of type %v or %v, was %v", reflect.Array, reflect.Slice, reflect.ValueOf(jsonValue).Kind())
}
switch t := reflect.TypeOf(fv.Interface()).Elem().Kind(); t {
case reflect.Struct:
if a, ok := jsonValue.([]any); ok {
underlying := fv.Type().Elem()
typedArray := reflect.New(reflect.SliceOf(underlying)).Elem()
for _, v := range a {
if m, ok := v.(map[string]any); ok {
structTempt := reflect.New(underlying).Interface()
err := MapJsonMapToStruct(m, structTempt)
if err != nil {
return err
}
typedArray = reflect.Append(typedArray, reflect.ValueOf(structTempt).Elem())
} else {
return fmt.Errorf("input value inside array has to be of type map[string]any, was %v", reflect.TypeOf(v))
}
}
fv.Set(typedArray)
} else {
return fmt.Errorf("input value has to be of type []any, was %v", reflect.TypeOf(jsonValue))
}
default:
if v, ok := jsonValue.([]any); ok {
typedArray, err := ArrayToArrayOfType(v, fv.Type().Elem())
if err != nil {
return err
}
fv.Set(typedArray)
} else {
fv.Set(reflect.ValueOf(jsonValue))
}
}
default:
return fmt.Errorf("invalid field type: %v", reflect.TypeOf(jsonValue).Elem().Kind())
}
}
return nil
}
package helper
import (
"fmt"
"reflect"
)
func MapKeysToArrayOfAny(v any) ([]any, error) {
aany := []any{}
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Map {
return nil, fmt.Errorf("invalid type %v, has to be map", rv.Type().Kind())
}
for _, mk := range rv.MapKeys() {
aany = append(aany, mk.Interface())
}
return aany, nil
}
package helper
import (
"fmt"
"regexp"
"strconv"
"time"
)
// UnixStringToTime converts a Unix timestamp string to a time.Time object.
// It checks if the string is a valid Unix timestamp and parses it.
func UnixStringToTime(in string) (time.Time, error) {
// Unix seconds date string
match, _ := regexp.MatchString("^[0-9]{1,}$", in)
if !match {
return time.Time{}, fmt.Errorf("invalid unix time: %v", in)
}
seconds, err := strconv.ParseInt(in, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("error parsing unix string to time: %v", err)
}
return time.Unix(seconds, 0).UTC(), nil
}
// ISO8601StringToTime converts an ISO8601 date string to a time.Time object.
// It checks the format of the string and parses it accordingly.
// It supports various formats including local time, UTC time, with and without microseconds.
func ISO8601StringToTime(in string) (time.Time, error) {
layout := ""
// Iso8601 date string in local time (yyyy-MM-ddTHH:mm:ss.mmmuuu)
match, _ := regexp.MatchString("^[-:.T0-9]{26}$", in)
if match {
layout = "2006-01-02T15:04:05.000000"
}
// Iso8601 date string in UTC time (yyyy-MM-ddTHH:mm:ss.mmmuuuZ)
match, _ = regexp.MatchString("^[-:.T0-9]{26}Z$", in)
if match {
layout = "2006-01-02T15:04:05.000000Z"
}
// Iso8601 date string in local time without microseconds (yyyy-MM-ddTHH:mm:ss.mmm)
match, _ = regexp.MatchString("^[-:.T0-9]{23}$", in)
if match {
layout = "2006-01-02T15:04:05.000"
}
// Iso8601 date string in UTC time without microseconds (yyyy-MM-ddTHH:mm:ss.mmmZ)
match, _ = regexp.MatchString("^[-:.T0-9]{23}Z$", in)
if match {
layout = "2006-01-02T15:04:05.000Z"
}
date, err := time.Parse(layout, in)
if err != nil {
return time.Time{}, fmt.Errorf("error parsing iso8601 string to time: %v", err)
}
return date, nil
}
package helper
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
func UnmarshalRequestToJsonMap(request *http.Request) (map[string]any, error) {
if request == nil {
return nil, fmt.Errorf("request is nil")
}
bodyBytes, err := io.ReadAll(request.Body)
if err != nil {
return nil, fmt.Errorf("error reading request body: %v", err)
}
defer request.Body.Close()
return UnmarshalJsonToJsonMap(bodyBytes)
}
func UnmapRequestToJsonMap(request *http.Request) (map[string]any, error) {
if request == nil {
return nil, fmt.Errorf("request is nil")
}
err := request.ParseForm()
if err != nil {
return nil, fmt.Errorf("error parsing form: %v", err)
}
return UnmapUrlValuesToJsonMap(request.Form)
}
func UnmarshalJsonToJsonMap(jsonInput []byte) (map[string]any, error) {
mapOut := map[string]any{}
err := json.Unmarshal(jsonInput, &mapOut)
if err != nil {
return nil, fmt.Errorf("error unmarshaling: %v", err)
}
return mapOut, nil
}
func UnmapUrlValuesToJsonMap(values url.Values) (map[string]any, error) {
mapOut := map[string]any{}
for k := range values {
if len(values[k]) > 1 {
arrayOut := []any{}
for _, v := range values[k] {
var unmarshalled any
err := json.Unmarshal([]byte(v), &unmarshalled)
if err == nil {
arrayOut = append(arrayOut, unmarshalled)
} else {
arrayOut = append(arrayOut, v)
}
}
mapOut[k] = arrayOut
} else {
value := values.Get(k)
var unmarshalled any
err := json.Unmarshal([]byte(value), &unmarshalled)
if err == nil {
mapOut[k] = unmarshalled
} else {
mapOut[k] = value
}
}
}
return mapOut, nil
}
package model
import (
"fmt"
"strings"
)
// ConditionType is the type for all available condition types.
type ConditionType string
// Available condition types.
const (
NONE ConditionType = "-"
EQUAL ConditionType = "equ"
NOT_EQUAL ConditionType = "neq"
MIN_VALUE ConditionType = "min"
MAX_VALUE ConditionType = "max"
CONTAINS ConditionType = "con"
NOT_CONTAINS ConditionType = "nco"
FROM ConditionType = "frm"
NOT_FROM ConditionType = "nfr"
REGX ConditionType = "rex"
FUNC ConditionType = "fun"
)
var ValidConditionTypes = map[ConditionType]int{
NONE: 0,
EQUAL: 1,
NOT_EQUAL: 2,
MIN_VALUE: 3,
MAX_VALUE: 4,
CONTAINS: 5,
NOT_CONTAINS: 6,
FROM: 7,
NOT_FROM: 8,
REGX: 9,
}
// LookupConditionType checks our validConditionType map for the scanned condition type.
// If not found, an error is returned.
func LookupConditionType(conType ConditionType) error {
if _, ok := ValidConditionTypes[conType]; ok {
return nil
}
return fmt.Errorf("expected a valid condition type, found: %s", conType)
}
// GetConditionType returns the condition type from a string.
// It checks if the string starts with a valid condition type prefix.
// If the string is not valid, an error is returned.
func GetConditionType(s string) (ConditionType, error) {
var conditionType ConditionType
if len(s) > 3 {
conditionType = ConditionType(s[:3])
} else {
conditionType = ConditionType(s)
}
if _, ok := ValidConditionTypes[conditionType]; !ok {
return conditionType, fmt.Errorf("invalid condition type: %s", conditionType)
}
return conditionType, nil
}
// GetConditionByType extracts the condition value from a string based on the condition type.
// It trims the prefix of the condition type from the string and returns the remaining part.
// If the condition type is not valid or the value is empty, an error is returned.
func GetConditionByType(conditionFull string, conditionType ConditionType) (string, error) {
if len(conditionType) != 3 {
return "", fmt.Errorf("length of conditionType has to be 3: %s", conditionType)
}
condition := strings.TrimPrefix(conditionFull, string(conditionType))
if len(condition) == 0 {
return "", fmt.Errorf("empty %s value", conditionType)
}
return condition, nil
}
package model
import (
"fmt"
"strings"
)
// Group represents a validation group with a name, condition type, and condition value.
// The name is expected to start with "gr" followed by a condition type and value.
// The condition type is validated against known condition types, allowed are min and max.
// The condition value is extracted based on the condition type.
type Group struct {
Name string
ConditionType ConditionType
ConditionValue string
}
// GetGroups parses a string to extract validation groups.
// It splits the string by spaces and creates a Group for each part.
// Each group must start with "gr" and can have a condition type and value.
// If the group name is invalid or the condition type is not recognized, an error is returned.
func GetGroups(s string) ([]*Group, error) {
groups := []*Group{}
groupsString := strings.Split(s, " ")
for _, g := range groupsString {
group := &Group{}
var groupCondition string
if len(g) > 3 {
group.Name = g[:3]
groupCondition = g[3:]
} else {
return nil, fmt.Errorf("group too short: %s", group.Name)
}
if !strings.HasPrefix(group.Name, "gr") {
return nil, fmt.Errorf("invalid group name: %s", group.Name)
}
var err error
group.ConditionType, err = GetConditionType(groupCondition)
if err != nil {
return nil, err
}
group.ConditionValue, err = GetConditionByType(groupCondition, group.ConditionType)
if err != nil {
return nil, err
}
groups = append(groups, group)
}
return groups, nil
}
package model
import (
"fmt"
"strings"
)
// RootNode is what starts every parsed AST (=abstract syntax tree).
type RootNode struct {
RootValue *AstValue
}
// AstValue (=abstract syntax tree value) holds a Type ("Condition" or "Group") as well as a `ConditionType` and `ConditionValue`.
// The ConditionType is a [model.ConditionType] and the ConditionValue is any string (numbers are also represented as string).
type AstValue struct {
Type AstValueType
ConditionType ConditionType
ConditionValue string
ConditionGroup ConditionGroup
Operator Operator
Start int
End int
}
// AstValueType is the type for all available AST value types.
type AstValueType string
const (
EMPTY AstValueType = "Empty"
GROUP AstValueType = "Group"
CONDITION AstValueType = "Condition"
)
// ConditionGroup is a slice of AstValue pointers.
type ConditionGroup []*AstValue
// AstGroupToString converts the AstValue's ConditionGroup to a string representation.
// It iterates over each AstValue in the group and formats it based on its type.
// If the AstValue is a group, it recursively calls itself to get the string representation of the group.
// If the AstValue is a condition, it formats it as a string with its ConditionType and ConditionValue.
// The resulting string is a concatenation of all conditions and groups, separated by spaces.
func (r AstValue) AstGroupToString() string {
groupConditions := []string{}
groupString := ""
for _, v := range r.ConditionGroup {
switch v.Type {
case GROUP:
if len(v.Operator) > 0 {
groupConditions = append(groupConditions, fmt.Sprintf("(%v) %v", v.AstGroupToString(), v.Operator))
} else {
groupConditions = append(groupConditions, fmt.Sprintf("(%v)", v.AstGroupToString()))
}
case CONDITION:
groupConditions = append(groupConditions, v.AstConditionToString())
}
}
groupString = strings.Join(groupConditions, " ")
return groupString
}
// AstConditionToString converts the AstValue's ConditionType and ConditionValue to a string representation.
// If the AstValue has an Operator, it includes that in the string.
// The resulting string is formatted as "<ConditionType>'<ConditionValue>' <Operator>" if the Operator is present,
// or as "<ConditionType>'<ConditionValue>'" if the Operator is not present.
func (r AstValue) AstConditionToString() string {
if len(r.Operator) > 0 {
return fmt.Sprintf("%v'%v' %v", r.ConditionType, r.ConditionValue, r.Operator)
} else {
return fmt.Sprintf("%v'%v'", r.ConditionType, r.ConditionValue)
}
}
// Operator is the type for all available operators.
type Operator string
// Available operators.
const (
// Group states
AND Operator = "&&"
OR Operator = "||"
)
// validOperator is a map that holds valid operators and their corresponding integer values.
// This map is used to validate operators in the AST.
var validOperator = map[Operator]int{
AND: 0,
OR: 1,
}
// LookupOperator checks our validOperator map for the scanned operator.
// If not found, an error is returned.
func LookupOperator(operator Operator) error {
if _, ok := validOperator[operator]; ok {
return nil
}
return fmt.Errorf("expected a valid operator, found: %s", operator)
}
package model
import (
"fmt"
"reflect"
"strings"
"github.com/siherrmann/validator/helper"
)
// Default tag type.
const VLD string = "vld"
// Validation represents a validation rule for a struct field.
type Validation struct {
Key string
Type ValidatorType
Requirement string
Groups []*Group
Default string
// Inner Struct validation
InnerValidation []Validation
}
// GetValidationsFromStruct extracts validation rules from a struct based on the provided tag type.
// It iterates over the struct fields, checks for the specified tag type, and constructs Validation.
func GetValidationsFromStruct(in any, tagType string) ([]Validation, error) {
err := helper.CheckValidPointerToStruct(in)
if err != nil {
return nil, err
}
validations := []Validation{}
structFull := reflect.ValueOf(in).Elem()
for i := 0; i < structFull.Type().NumField(); i++ {
field := structFull.Field(i)
fieldType := structFull.Type().Field(i)
validation, err := GetValidationFromStructField(tagType, field, fieldType)
if err != nil {
return nil, err
}
if len(validation.Requirement) > 0 {
validations = append(validations, validation)
}
}
return validations, nil
}
// GetValidationFromStructField extracts validation rules from a struct field based on the provided tag type.
// It checks the field's tag for the specified tag type and constructs a Validation object.
// If no json tag is found, it uses the field name as the key.
func GetValidationFromStructField(tagType string, fieldValue reflect.Value, fieldType reflect.StructField) (Validation, error) {
validation := Validation{}
validation.Key = fieldType.Name
if len(fieldType.Tag.Get("json")) > 0 {
validation.Key = fieldType.Tag.Get("json")
}
validation.Type = ReflectKindToValidatorType(fieldValue.Type().Kind())
validation.Requirement = "-"
tagIndex := 0
tagSplit := strings.Split(fieldType.Tag.Get(string(tagType)), ", ")
if len(tagSplit) > tagIndex {
if len(tagSplit[tagIndex]) <= 3 && tagSplit[tagIndex] != "-" {
return Validation{}, fmt.Errorf("invalid requirement %v for field %v", tagSplit[tagIndex], fieldType.Name)
}
validation.Requirement = tagSplit[tagIndex]
tagIndex++
}
if len(tagSplit) > tagIndex {
var err error
validation.Groups, err = GetGroups(tagSplit[tagIndex])
if err != nil {
return Validation{}, fmt.Errorf("error extracting group: %v", err)
}
}
if helper.IsArrayOfStruct(fieldValue.Interface()) {
innerStruct := reflect.New(fieldValue.Type().Elem()).Interface()
innerValidation, err := GetValidationsFromStruct(innerStruct, string(tagType))
if err != nil {
return Validation{}, fmt.Errorf("error getting inner validation from array: %v", err)
}
validation.InnerValidation = append(validation.InnerValidation, innerValidation...)
} else if helper.IsStruct(fieldValue.Interface()) {
innerStruct := reflect.New(fieldValue.Type()).Interface()
innerValidation, err := GetValidationsFromStruct(innerStruct, string(tagType))
if err != nil {
return Validation{}, fmt.Errorf("error getting inner validation from struct: %v", err)
}
validation.InnerValidation = append(validation.InnerValidation, innerValidation...)
}
return validation, nil
}
// ValidatorMap is a map of validation keys to Validation objects.
// It is used to store and manage multiple validation rules for different struct fields.
type ValidatorMap map[string]Validation
package model
import (
"reflect"
)
// ValidatorType is the type for all available validation types.
type ValidatorType string
const (
String ValidatorType = "string"
Int ValidatorType = "int"
Float ValidatorType = "float"
Bool ValidatorType = "bool"
Array ValidatorType = "array"
Map ValidatorType = "map"
Struct ValidatorType = "struct"
)
func (v ValidatorType) ToReflectType() reflect.Type {
switch v {
case String:
return reflect.TypeOf("")
case Int:
return reflect.TypeOf(int(0))
case Float:
return reflect.TypeOf(float64(0))
case Bool:
return reflect.TypeOf(false)
case Array:
return reflect.TypeOf([]string{})
case Map:
return reflect.TypeOf(map[string]string{})
case Struct:
return reflect.TypeOf(struct{}{})
default:
return reflect.TypeOf(struct{}{})
}
}
// TypeFromInterface determines the ValidatorType based on the type of the input interface.
// It checks the type of the input and returns the corresponding ValidatorType.
// If the type is not recognized, it defaults to Struct.
// It handles basic types like string, int, float, bool, and complex types like JsonMap and arrays.
// It also checks for time.Time type and returns the appropriate ValidatorType.
func ReflectKindToValidatorType(reflectType reflect.Kind) ValidatorType {
switch reflectType {
case reflect.String:
return String
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
return Int
case reflect.Float64, reflect.Float32:
return Float
case reflect.Bool:
return Bool
case reflect.Map:
return Map
case reflect.Slice, reflect.Array:
return Array
case reflect.Struct:
return Struct
default:
return Struct
}
}
package parser
import (
"strings"
"github.com/siherrmann/validator/model"
)
// Lexer performs lexical analysis/scanning of the JSON
type Lexer struct {
Input []rune
char rune // current char under examination
lastTokenType model.TokenType // last TokenType for splitting condition type from value.
position int // current position in input (points to current char)
nextPosition int // current reading position in input (after current char)
line int // line number for better error reporting, etc
}
// NewLexer creates and returns a pointer to the Lexer
func NewLexer(input string) *Lexer {
l := &Lexer{Input: []rune(input)}
l.readChar()
return l
}
func (l *Lexer) readChar() {
if l.nextPosition >= len(l.Input) {
// End of input (haven't read anything yet or EOF)
// 0 is ASCII code for "NUL" character
l.char = 0
} else {
l.char = l.Input[l.nextPosition]
}
l.position = l.nextPosition
l.nextPosition++
}
// NextToken switches through the lexer's current char and creates a new model.
// It then it calls readChar to advance the lexer and it returns the token.
func (l *Lexer) NextToken() model.Token {
var t model.Token
l.skipWhitespace()
switch l.char {
case '-':
t = newToken(model.LexerEmptyRequirement, l.line, l.position, l.position+1, l.char)
case '(':
t = newToken(model.LexerLeftBrace, l.line, l.position, l.position+1, l.char)
case ')':
t = newToken(model.LexerRightBrace, l.line, l.position, l.position+1, l.char)
case '|', '&':
t.Literal = l.readOperator()
t.Line = l.line
t.Start = l.position
t.End = l.position + 2
t.Type = model.LexerOperator
l.lastTokenType = model.LexerOperator
case '\'':
t.Literal = l.readString()
t.Line = l.line
t.Start = l.position
t.End = l.position + 1
t.Type = model.LexerConditionValue
l.lastTokenType = model.LexerConditionValue
case 0:
t.Literal = ""
t.Line = l.line
t.Type = model.LexerEOF
l.lastTokenType = model.LexerEOF
default:
if l.lastTokenType == model.LexerConditionType {
t.Literal = l.readConditionValue()
t.Line = l.line
t.Start = l.position
t.End = l.position
t.Type = model.LexerConditionValue
l.lastTokenType = model.LexerConditionValue
return t
} else if isLetter(l.char) {
t.Literal = l.readConditionType()
t.Line = l.line
t.Start = l.position
t.End = l.position
t.Type = model.LexerConditionType
l.lastTokenType = model.LexerConditionType
return t
}
t = newToken(model.LexerIllegal, l.line, l.position, l.position, l.char)
}
l.readChar()
return t
}
func (l *Lexer) skipWhitespace() {
for l.char == ' ' || l.char == '\t' || l.char == '\n' || l.char == '\r' {
if l.char == '\n' {
l.line++
}
l.readChar()
}
}
func newToken(tokenType model.TokenType, line, start, end int, char ...rune) model.Token {
return model.Token{
Type: tokenType,
Literal: string(char),
Line: line,
Start: start,
End: end,
}
}
func isLetter(char rune) bool {
return 'a' <= char && char <= 'z'
}
func isOperator(char rune) bool {
return char == '|' || char == '&'
}
// readOperator sets a start position and reads through two characters to get a full operator
func (l *Lexer) readOperator() string {
position := l.position
for isOperator(l.char) && l.position < position+2 {
l.readChar()
}
return string(l.Input[position:l.position])
}
// readString sets a start position and reads through characters
// until it finds a closing `'`. It stops consuming characters and
// returns the string between the start and end positions.
// The charakter`'` inside the string is escaped with a '/'
// and the '/' is removed after reading the string.
func (l *Lexer) readString() string {
position := l.position + 1
for {
prevChar := l.char
l.readChar()
if (l.char == '\'' && prevChar != '/') || l.char == 0 {
break
}
}
// remove custom escaped `'`
return strings.ReplaceAll(string(l.Input[position:l.position]), "/'", "'")
}
// readConditionType sets a start position and reads through 3 characters
// to get a condition type (unvalidated).
func (l *Lexer) readConditionType() string {
position := l.position
for isLetter(l.char) && l.position < position+3 {
l.readChar()
}
return string(l.Input[position:l.position])
}
// readConditionValue sets a start position and reads through characters
// until any kind of whitespace to get a condition value (unvalidated).
func (l *Lexer) readConditionValue() string {
position := l.position
for l.char != ' ' && l.char != '\t' && l.char != '\n' && l.char != '\r' && l.char != ')' && l.char != 0 {
l.readChar()
}
return string(l.Input[position:l.position])
}
package parser
import (
"errors"
"fmt"
"strings"
"github.com/siherrmann/validator/model"
)
// Parser holds a Lexer, errors, the currentToken, and the peekToken (next token).
// Parser methods handle iterating through tokens and building and AST.
type Parser struct {
lexer *Lexer
errors []string
currentToken model.Token
peekToken model.Token
}
// NewParser creates a new Parser instance.
func NewParser() *Parser {
return &Parser{}
}
// ParseValidation parses tokens and creates an AST. It returns the RootNode
// which holds a Value and in it the rest of the tree.
// It creates a new Lexer for reset lexer state and initializes the parser's tokens.
// It returns an error if the parsing fails.
func (p *Parser) ParseValidation(validation string) (model.RootNode, error) {
p.errors = []string{}
p.lexer = NewLexer(validation)
// Read two tokens, so currentToken and peekToken are both set.
p.nextToken()
p.nextToken()
var rootNode model.RootNode
val := p.parseGroup(true)
if val == nil || len(p.Errors()) > 0 {
p.parseError(fmt.Sprintf(
"error parsing validation, expected a value, got: %v:",
p.currentToken.Literal,
))
return model.RootNode{RootValue: &model.AstValue{}}, errors.New(p.Errors())
}
rootNode.RootValue = val
return rootNode, nil
}
// nextToken sets our current token to the peekToken and the peekToken to
// p.lexer.NextToken() which scans and returns the next token.
func (p *Parser) nextToken() {
p.currentToken = p.peekToken
p.peekToken = p.lexer.NextToken()
}
func (p *Parser) currentTokenTypeIs(t model.TokenType) bool {
return p.currentToken.Type == t
}
// parseGroup is called when an open left brace `(` token is found or a requirement starts without a '('.
func (p *Parser) parseGroup(root bool) *model.AstValue {
group := &model.AstValue{Type: model.GROUP}
grpState := model.GrpStart
for !p.currentTokenTypeIs(model.LexerEOF) && grpState != model.GrpEnd {
if len(p.errors) > 0 {
return nil
}
switch grpState {
case model.GrpStart:
if p.currentTokenTypeIs(model.LexerLeftBrace) {
if root {
innerGroup := p.parseGroup(false)
group.ConditionGroup = append(group.ConditionGroup, innerGroup)
grpState = model.GrpOpen
} else {
group.Start = p.currentToken.Start
p.nextToken()
grpState = model.GrpOpen
}
} else if p.currentTokenTypeIs(model.LexerConditionType) {
group.Start = p.currentToken.Start
grpState = model.GrpOpen
} else if p.currentTokenTypeIs(model.LexerEmptyRequirement) {
group.Type = model.EMPTY
group.Start = p.currentToken.Start
group.End = p.currentToken.End
grpState = model.GrpEnd
return group
} else {
p.parseError(fmt.Sprintf(
"error parsing validation group, expected left brace, `-` or condition, got: %s",
p.currentToken.Literal,
))
return nil
}
case model.GrpOpen:
if p.currentTokenTypeIs(model.LexerRightBrace) {
group.End = p.currentToken.End
p.nextToken()
grpState = model.GrpEnd
} else if p.currentTokenTypeIs(model.LexerLeftBrace) {
innerGroup := p.parseGroup(false)
group.ConditionGroup = append(group.ConditionGroup, innerGroup)
if len(group.ConditionGroup) > 1 && len(group.ConditionGroup[len(group.ConditionGroup)-2].Operator) == 0 {
group.ConditionGroup[len(group.ConditionGroup)-2].Operator = model.AND
}
} else if p.currentTokenTypeIs(model.LexerConditionType) {
condition := p.parseCondition()
group.ConditionGroup = append(group.ConditionGroup, condition)
if len(group.ConditionGroup) > 1 && len(group.ConditionGroup[len(group.ConditionGroup)-2].Operator) == 0 {
group.ConditionGroup[len(group.ConditionGroup)-2].Operator = model.AND
}
} else if p.currentTokenTypeIs(model.LexerOperator) && len(group.ConditionGroup) > 0 {
operator := p.parseOperator()
group.ConditionGroup[len(group.ConditionGroup)-1].Operator = operator
p.nextToken()
} else {
p.parseError(fmt.Sprintf(
"error parsing group, expected right brace, condition or operator, got: %s, type: %v",
p.currentToken.Literal,
p.currentToken.Type,
))
return nil
}
}
}
if p.currentTokenTypeIs(model.LexerEOF) && grpState == model.GrpOpen && !root {
p.parseError(fmt.Sprintf(
"error parsing group, expected right brace, got end of line after: %s",
p.lexer.lastTokenType,
))
return nil
}
group.End = p.currentToken.Start
return group
}
// parseCondition is used to parse a condition and setting the `conditionType`:`condition` pair.
func (p *Parser) parseCondition() *model.AstValue {
condition := &model.AstValue{Type: model.CONDITION}
conditionState := model.ConType
for conditionState != model.ConEnd {
switch conditionState {
case model.ConType:
if p.currentTokenTypeIs(model.LexerConditionType) {
condition.ConditionType = p.parseConditionType()
p.nextToken()
conditionState = model.ConValue
} else {
p.parseError(fmt.Sprintf(
"error parsing condition type, expected CndType token, got: %s",
p.currentToken.Literal,
))
return condition
}
case model.ConValue:
if p.currentTokenTypeIs(model.LexerConditionValue) || p.currentTokenTypeIs(model.LexerConditionValueString) {
condition.ConditionValue = p.parseConditionValue()
p.nextToken()
conditionState = model.ConEnd
} else {
p.parseError(fmt.Sprintf(
"error parsing condition, expected ConValue token, got: %s",
p.currentToken.Literal,
))
return condition
}
}
}
return condition
}
// parseOperator is used to parse the condition type.
func (p *Parser) parseOperator() model.Operator {
operator := model.Operator(p.currentToken.Literal)
err := model.LookupOperator(operator)
if err != nil {
p.parseError(fmt.Sprintf(
"error parsing operator type %s with error: %v",
p.currentToken.Literal,
err.Error(),
))
}
return operator
}
// parseConditionType is used to parse the condition type.
func (p *Parser) parseConditionType() model.ConditionType {
conType := model.ConditionType(p.currentToken.Literal)
err := model.LookupConditionType(conType)
if err != nil {
p.parseError(fmt.Sprintf(
"error parsing condition type %s with error: %v",
p.currentToken.Literal,
err.Error(),
))
}
return conType
}
// parseConditionValue is used to parse the condition value (eg. 10 if min length of string is 10 (min10)).
func (p *Parser) parseConditionValue() string {
return p.currentToken.Literal
}
// parseError is very similar to `peekError`, except it simply takes a string message that
// gets appended to the parser's errors
func (p *Parser) parseError(msg string) {
p.errors = append(p.errors, msg)
}
// Errors is simply a helper function that returns the parser's errors
func (p *Parser) Errors() string {
return strings.Join(p.errors, ", ")
}
package validator
import (
"fmt"
"slices"
"strings"
"github.com/siherrmann/validator/helper"
"github.com/siherrmann/validator/model"
"github.com/siherrmann/validator/parser"
"github.com/siherrmann/validator/validators"
)
type ValidationFunc func(input any, astValue *model.AstValue) error
// Validator is the main struct for validation.
type Validator struct {
ValidationFuncs map[string]ValidationFunc
}
// NewValidator creates a new Validator instance with an empty validation functions map.
func NewValidator() *Validator {
return &Validator{
ValidationFuncs: make(map[string]ValidationFunc),
}
}
// AddValidationFunc adds a custom validation function to the Validator.
// The function can be used in validation requirements with the name provided (`fun<name>`).
func (r *Validator) AddValidationFunc(fn ValidationFunc, name string) {
r.ValidationFuncs[name] = fn
}
// Validate validates a given struct by the given tagType.
// It checks if the keys are in the struct and validates the values.
// It returns an error if the validation fails.
func (r *Validator) Validate(v any, tagType ...string) error {
tagTypeSet := model.VLD
if len(tagType) > 0 {
tagTypeSet = tagType[0]
}
jsonMap := map[string]any{}
err := helper.UnmapStructToJsonMap(v, &jsonMap)
if err != nil {
return fmt.Errorf("error unmapping struct to json map: %v", err)
}
validations, err := model.GetValidationsFromStruct(v, tagTypeSet)
if err != nil {
return fmt.Errorf("error getting validations from struct: %v", err)
}
_, err = r.ValidateWithValidation(jsonMap, validations)
if err != nil {
return fmt.Errorf("error validating struct: %v", err)
}
return nil
}
// ValidateAndUpdate validates a given JsonMap by the given validations and updates the struct.
// It checks if the keys are in the map, validates the values and updates the struct if the validation passes.
// It returns an error if the validation fails or if the struct cannot be updated.
func (r *Validator) ValidateAndUpdate(jsonInput map[string]any, structToUpdate any, tagType ...string) error {
tagTypeSet := model.VLD
if len(tagType) > 0 {
tagTypeSet = tagType[0]
}
validations, err := model.GetValidationsFromStruct(structToUpdate, tagTypeSet)
if err != nil {
return fmt.Errorf("error getting validations from struct: %v", err)
}
validatedMap, err := r.ValidateWithValidation(jsonInput, validations)
if err != nil {
return fmt.Errorf("error validating struct: %v", err)
}
err = helper.MapJsonMapToStruct(validatedMap, structToUpdate)
if err != nil {
return fmt.Errorf("error mapping json map to struct: %v", err)
}
return nil
}
// ValidateAndUpdateWithValidation validates a given JsonMap by the given validations and updates the map.
// It checks if the keys are in the map, validates the values and updates the map if the validation passes.
// It returns an error if the validation fails or if the map cannot be updated.
func (r *Validator) ValidateAndUpdateWithValidation(jsonInput map[string]any, mapToUpdate *map[string]any, validations []model.Validation) error {
validatedValues, err := r.ValidateWithValidation(jsonInput, validations)
if err != nil {
return fmt.Errorf("error validating json map: %v", err)
}
for k, v := range validatedValues {
(*mapToUpdate)[k] = v
}
return nil
}
// ValidateWithValidation validates a given JsonMap by the given validations.
// This is the main validation function that is used for all validation.
// It checks if the keys are in the map, validates the values and returns a new JsonMap.
//
// If a validation has groups, it checks if the values are valid for the groups.
// If a validation has a key that is already in the map, it returns an error.
//
// It returns a new JsonMap with the validated values or an error if the validation fails.
func (r *Validator) ValidateWithValidation(jsonInput map[string]any, validations []model.Validation) (map[string]any, error) {
keys := []string{}
groups := map[string]*model.Group{}
groupSize := map[string]int{}
groupErrors := map[string][]error{}
validateValues := map[string]any{}
for validationIndex := range validations {
validation := validations[validationIndex]
if len(validation.Key) > 0 && slices.Contains(keys, validation.Key) {
return map[string]any{}, fmt.Errorf("duplicate validation key: %v", validation.Key)
} else {
keys = append(keys, validation.Key)
}
for _, g := range validation.Groups {
groups[g.Name] = g
groupSize[g.Name]++
}
var ok bool
var jsonValue any
if jsonValue, ok = jsonInput[validation.Key]; !ok {
if strings.TrimSpace(validation.Requirement) == string(model.NONE) {
continue
} else if len(validation.Groups) == 0 {
return map[string]any{}, fmt.Errorf("json %v key not in map", validation.Key)
} else {
for _, group := range validation.Groups {
groupErrors[group.Name] = append(groupErrors[group.Name], fmt.Errorf("json %v key not in map", validation.Key))
}
continue
}
}
var err error
switch validation.Type {
case model.Struct:
if jsonValueMap, ok := jsonValue.(map[string]any); ok {
jsonValue, err = r.ValidateWithValidation(jsonValueMap, validation.InnerValidation)
if err != nil {
return map[string]any{}, fmt.Errorf("field %v invalid: %v", validation.Key, err.Error())
}
} else {
err = r.ValidateValueWithParser(jsonValue, &validation)
}
case model.Array:
if helper.IsArray(jsonValue) && len(validation.InnerValidation) > 0 {
jsonArray, ok := jsonValue.([]any)
if !ok {
return map[string]any{}, fmt.Errorf("field %v must be of type array, was %T", validation.Key, jsonValue)
}
for _, jsonValueInner := range jsonArray {
jsonValueInnerMap, err := helper.GetValidMap(jsonValueInner)
if err != nil {
return map[string]any{}, fmt.Errorf("field %v invalid: %v", validation.Key, err.Error())
}
_, err = r.ValidateWithValidation(jsonValueInnerMap, validation.InnerValidation)
if err != nil {
return map[string]any{}, fmt.Errorf("field %v invalid: %v", validation.Key, err.Error())
}
}
} else if helper.IsArray(jsonValue) {
err = r.ValidateValueWithParser(jsonValue, &validation)
} else if helper.IsString(jsonValue) {
// Check if the value is a string from a url value.
jsonValue = []string{jsonValue.(string)}
err = r.ValidateValueWithParser(jsonValue, &validation)
}
default:
err = r.ValidateValueWithParser(jsonValue, &validation)
}
if err != nil && len(validation.Groups) == 0 {
return map[string]any{}, fmt.Errorf("field %v invalid: %v", validation.Key, err.Error())
} else if err != nil {
for _, group := range validation.Groups {
groupErrors[group.Name] = append(groupErrors[group.Name], fmt.Errorf("field %v invalid: %v", validation.Key, err.Error()))
}
continue
}
validateValues[validation.Key] = jsonValue
}
err := validators.ValidateGroups(groups, groupSize, groupErrors)
if err != nil {
return map[string]any{}, err
}
return validateValues, nil
}
// ValidateValueWithParser validates a value against a given validation using the parser.
// It parses the validation requirement and runs the validation function on the input value.
//
// It returns an error if the validation fails.
func (r *Validator) ValidateValueWithParser(input any, validation *model.Validation) error {
p := parser.NewParser()
v, err := p.ParseValidation(validation.Requirement)
if err != nil {
return err
}
err = r.RunValidatorsOnConditionGroup(input, v.RootValue)
if err != nil {
return err
}
return nil
}
// RunFuncOnConditionGroup runs the function [f] on each condition in the [astValue].
// If the condition is a group, it recursively calls itself on the group.
// If the condition is a condition, it calls the function [f] with the input and the condition.
// If the operator is AND, it returns an error if any condition fails.
// If the operator is OR, it collects all errors and returns them if all conditions fail.
func (r *Validator) RunValidatorsOnConditionGroup(input any, astValue *model.AstValue) error {
var errors []error
for i, v := range astValue.ConditionGroup {
var err error
switch v.Type {
case model.EMPTY:
return nil
case model.GROUP:
err = r.RunValidatorsOnConditionGroup(input, v)
case model.CONDITION:
switch v.ConditionType {
case model.NONE:
continue
case model.EQUAL:
err = validators.ValidateEqual(input, v)
case model.NOT_EQUAL:
err = validators.ValidateNotEqual(input, v)
case model.MIN_VALUE:
err = validators.ValidateMin(input, v)
case model.MAX_VALUE:
err = validators.ValidateMax(input, v)
case model.CONTAINS:
err = validators.ValidateContains(input, v)
case model.NOT_CONTAINS:
err = validators.ValidateNotContains(input, v)
case model.FROM:
err = validators.ValidateFrom(input, v)
case model.NOT_FROM:
err = validators.ValidateNotFrom(input, v)
case model.REGX:
err = validators.ValidateRegex(input, v)
case model.FUNC:
fun, ok := r.ValidationFuncs[v.ConditionValue]
if !ok {
return fmt.Errorf("unknown validation function: %v", v.ConditionValue)
}
err = fun(input, v)
default:
return fmt.Errorf("unknown condition type: %v", v.ConditionType)
}
}
if err != nil {
if (i == 0 && v.Operator == model.OR) || (i > 0 && astValue.ConditionGroup[i-1].Operator == model.OR) {
errors = append(errors, err)
} else {
return err
}
}
}
if len(astValue.ConditionGroup) > 0 && len(errors) >= len(astValue.ConditionGroup) {
return fmt.Errorf("no condition fulfilled, all errors: %v", errors)
}
return nil
}
package validator
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/siherrmann/validator/helper"
"github.com/siherrmann/validator/model"
)
// UnmapOrUnmarshalAndValidate unmarshals given json ([]byte) or given url.Values (from request.Form),
// validates them and updates the given struct.
// It returns an error if the unmapping or validation fails.
//
// It is actually doing the same as UnmapOrUnmarshalValidateAndUpdate, but in another order.
// It does directly update the struct and validates afterwards.
// Normally you would either only use Validate or use UnmapOrUnmarshalValidateAndUpdate for early return on error.
func (r *Validator) UnmapOrUnmarshalAndValidate(request *http.Request, structToUpdate any, tagType ...string) error {
err := request.ParseForm()
if err != nil {
return err
}
if len(request.Form.Encode()) > 0 {
err = r.UnmapAndValidate(request, structToUpdate, tagType...)
} else {
err = r.UnmarshalAndValidate(request, structToUpdate, tagType...)
}
return err
}
// UnmarshalAndValidate unmarshals given json ([]byte) into pointer v.
// It validates the struct by the given tagType.
// It returns an error if the unmarshaling or validation fails.
//
// It is actually doing the same as UnmarshalValidateAndUpdate, but in another order.
// It does directly update the struct and validates afterwards.
// Normally you would either only use Validate or use UnmarshalValidateAndUpdate for early return on error.
func (r *Validator) UnmarshalAndValidate(request *http.Request, structToValidate any, tagType ...string) error {
err := helper.CheckValidPointerToStruct(structToValidate)
if err != nil {
return err
}
var bodyBytes []byte
bodyBytes, err = io.ReadAll(request.Body)
if err != nil {
return err
}
err = json.Unmarshal(bodyBytes, structToValidate)
if err != nil {
return fmt.Errorf("error unmarshaling json: %v", err)
}
err = r.Validate(structToValidate, tagType...)
if err != nil {
return fmt.Errorf("error validating struct: %v", err)
}
return nil
}
// UnmapAndValidate unmaps the url.Values from the request.Form into a JsonMap and puts it into the given struct.
// It validates the struct by the given tagType.
// It returns an error if the unmapping or validation fails.
//
// It is actually doing the same as UnmapValidateAndUpdate, but in another order.
// It does directly update the struct and validates afterwards.
// Normally you would either only use Validate or use UnmapValidateAndUpdate for early return on error.
func (r *Validator) UnmapAndValidate(request *http.Request, structToValidate any, tagType ...string) error {
mapOut, err := helper.UnmapRequestToJsonMap(request)
if err != nil {
return fmt.Errorf("error unmapping form values: %v", err)
}
err = helper.MapJsonMapToStruct(mapOut, structToValidate)
if err != nil {
return fmt.Errorf("error mapping json map to struct: %v", err)
}
err = r.Validate(structToValidate, tagType...)
if err != nil {
return fmt.Errorf("error validating url values: %v", err)
}
return nil
}
// UnmapOrUnmarshalValidateAndUpdate unmarshals given json ([]byte) or given url.Values (from request.Form),
// validates them and updates the given struct.
// It returns an error if the unmapping, validation or update fails.
//
// For more information look at ValidateAndUpdate.
func (r *Validator) UnmapOrUnmarshalValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
err := request.ParseForm()
if err != nil {
return err
}
if len(request.Form.Encode()) > 0 {
err = r.UnmapValidateAndUpdate(request, structToUpdate, tagType...)
} else {
err = r.UnmarshalValidateAndUpdate(request, structToUpdate, tagType...)
}
return err
}
// UnmarshalValidateAndUpdate unmarshals given json ([]byte) into pointer v.
// It returns an error if the unmapping, validation or update fails.
//
// For more information look at ValidateAndUpdate.
func (r *Validator) UnmarshalValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
mapOut, err := helper.UnmarshalRequestToJsonMap(request)
if err != nil {
return fmt.Errorf("error unmarshaling request body: %v", err)
}
err = r.ValidateAndUpdate(mapOut, structToUpdate, tagType...)
if err != nil {
return fmt.Errorf("error updating struct: %v", err)
}
return nil
}
// UnmapValidateAndUpdate unmaps given url.Values into pointer jsonMap.
// It returns an error if the unmapping, validation or update fails.
//
// For more information look at ValidateAndUpdate.
func (r *Validator) UnmapValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
mapOut, err := helper.UnmapRequestToJsonMap(request)
if err != nil {
return fmt.Errorf("error unmapping form values: %v", err)
}
err = r.ValidateAndUpdate(mapOut, structToUpdate, tagType...)
if err != nil {
return fmt.Errorf("error updating struct: %v", err)
}
return nil
}
// UnmapOrUnmarshalValidateAndUpdateWithValidation unmarshals given json ([]byte) or given url.Values (from request.Form).
// It validates the map with the given validations and updates the given map.
// It returns an error if the unmapping, validation or update fails.
func (r *Validator) UnmapOrUnmarshalValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
err := request.ParseForm()
if err != nil {
return err
}
if len(request.Form.Encode()) > 0 {
err = r.UnmapValidateAndUpdateWithValidation(request, mapToUpdate, validations)
} else {
err = r.UnmarshalValidateAndUpdateWithValidation(request, mapToUpdate, validations)
}
return err
}
// UnmarshalValidateAndUpdateWithValidation unmarshals given json ([]byte) into pointer mapToUpdate.
// It validates the map by the given validations and updates it.
// It returns an error if the unmarshaling, validation or update fails.
func (r *Validator) UnmarshalValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
mapOut, err := helper.UnmarshalRequestToJsonMap(request)
if err != nil {
return fmt.Errorf("error unmarshaling request body: %v", err)
}
err = r.ValidateAndUpdateWithValidation(mapOut, mapToUpdate, validations)
if err != nil {
return fmt.Errorf("error updating struct: %v", err)
}
return nil
}
// UnmapValidateAndUpdateWithValidation unmaps given url.Values into pointer jsonMap.
// It validates the map by the given validations and updates it.
// It returns an error if the unmapping, validation or update fails.
func (r *Validator) UnmapValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
mapOut, err := helper.UnmapRequestToJsonMap(request)
if err != nil {
return fmt.Errorf("error unmapping form values: %v", err)
}
err = r.ValidateAndUpdateWithValidation(mapOut, mapToUpdate, validations)
if err != nil {
return fmt.Errorf("error updating struct: %v", err)
}
return nil
}
package validator
import (
"net/http"
"github.com/siherrmann/validator/model"
)
// Validate is the wrapper function for the Validate method of the Validator struct.
// More details can be found in the Validate method.
func Validate(v any, tagType ...string) error {
r := NewValidator()
return r.Validate(v, tagType...)
}
// ValidateWithValidation is the wrapper function for the ValidateWithValidation method of the Validator struct.
// More details can be found in the ValidateWithValidation method.
func ValidateWithValidation(jsonInput map[string]any, validations []model.Validation) (map[string]any, error) {
r := NewValidator()
return r.ValidateWithValidation(jsonInput, validations)
}
// ValidateAndUpdate is the wrapper function for the ValidateAndUpdate method of the Validator struct.
// More details can be found in the ValidateAndUpdate method.
func ValidateAndUpdate(jsonInput map[string]any, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.ValidateAndUpdate(jsonInput, structToUpdate, tagType...)
}
// ValidateAndUpdateWithValidation is the wrapper function for the ValidateAndUpdateWithValidation method of the Validator struct.
// More details can be found in the ValidateAndUpdateWithValidation method.
func ValidateAndUpdateWithValidation(jsonInput map[string]any, mapToUpdate *map[string]any, validations []model.Validation) error {
r := NewValidator()
return r.ValidateAndUpdateWithValidation(jsonInput, mapToUpdate, validations)
}
// UnmapOrUnmarshalAndValidate is the wrapper function for the UnmapOrUnmarshalAndValidate method of the Validator struct.
// More details can be found in the UnmapOrUnmarshalAndValidate method.
func UnmapOrUnmarshalAndValidate(request *http.Request, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.UnmapOrUnmarshalAndValidate(request, structToUpdate, tagType...)
}
// UnmapAndValidate is the wrapper function for the UnmapAndValidate method of the Validator struct.
// More details can be found in the UnmapAndValidate method.
func UnmapAndValidate(request *http.Request, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.UnmapAndValidate(request, structToUpdate, tagType...)
}
// UnmarshalAndValidate is the wrapper function for the UnmarshalAndValidate method of the Validator struct.
// More details can be found in the UnmarshalAndValidate method.
func UnmarshalAndValidate(request *http.Request, v any, tagType ...string) error {
r := NewValidator()
return r.UnmarshalAndValidate(request, v, tagType...)
}
// UnmapOrUnmarshalValidateAndUpdate is the wrapper function for the UnmapOrUnmarshalValidateAndUpdate method of the Validator struct.
// More details can be found in the UnmapOrUnmarshalValidateAndUpdate method.
func UnmapOrUnmarshalValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.UnmapOrUnmarshalValidateAndUpdate(request, structToUpdate, tagType...)
}
// UnmapValidateAndUpdate is the wrapper function for the UnmapValidateAndUpdate method of the Validator struct.
// More details can be found in the UnmapValidateAndUpdate method.
func UnmapValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.UnmapValidateAndUpdate(request, structToUpdate, tagType...)
}
// UnmarshalValidateAndUpdate is the wrapper function for the UnmarshalValidateAndUpdate method of the Validator struct.
// More details can be found in the UnmarshalValidateAndUpdate method.
func UnmarshalValidateAndUpdate(request *http.Request, structToUpdate any, tagType ...string) error {
r := NewValidator()
return r.UnmarshalValidateAndUpdate(request, structToUpdate, tagType...)
}
// UnmapOrUnmarshalValidateAndUpdateWithValidation is the wrapper function for the UnmapOrUnmarshalValidateAndUpdateWithValidation method of the Validator struct.
// More details can be found in the UnmapOrUnmarshalValidateAndUpdateWithValidation method.
func UnmapOrUnmarshalValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
r := NewValidator()
return r.UnmapOrUnmarshalValidateAndUpdateWithValidation(request, mapToUpdate, validations)
}
// UnmapValidateAndUpdateWithValidation is the wrapper function for the UnmapValidateAndUpdateWithValidation method of the Validator struct.
// More details can be found in the UnmapValidateAndUpdateWithValidation method.
func UnmapValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
r := NewValidator()
return r.UnmapValidateAndUpdateWithValidation(request, mapToUpdate, validations)
}
// UnmarshalValidateAndUpdateWithValidation is the wrapper function for the UnmarshalValidateAndUpdateWithValidation method of the Validator struct.
// More details can be found in the UnmarshalValidateAndUpdateWithValidation method.
func UnmarshalValidateAndUpdateWithValidation(request *http.Request, mapToUpdate *map[string]any, validations []model.Validation) error {
r := NewValidator()
return r.UnmarshalValidateAndUpdateWithValidation(request, mapToUpdate, validations)
}
package validators
import (
"fmt"
"reflect"
"github.com/siherrmann/validator/model"
)
func ValidateContains[T any](v T, ast *model.AstValue) error {
switch reflect.TypeOf(v).Kind() {
case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
contains, err := Contains(v, ast.ConditionValue)
if err != nil {
return fmt.Errorf("error checking contains: %v", err)
}
if !contains {
return fmt.Errorf("value does not contain %v", ast.ConditionValue)
}
default:
return fmt.Errorf("value to validate has to be a string, array, slice or map, was %v", reflect.TypeOf(v).Kind())
}
return nil
}
func ValidateNotContains[T any](v T, ast *model.AstValue) error {
switch reflect.TypeOf(v).Kind() {
case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
contains, err := Contains(v, ast.ConditionValue)
if err != nil {
return fmt.Errorf("error checking not contains: %v", err)
}
if contains {
return fmt.Errorf("value contains condition %v", ast.ConditionValue)
}
default:
return fmt.Errorf("value to validate has to be a string, array, slice or map, was %v", reflect.TypeOf(v).Kind())
}
return nil
}
package validators
import (
"fmt"
"reflect"
"github.com/siherrmann/validator/helper"
"github.com/siherrmann/validator/model"
)
func ValidateEqual(v any, ast *model.AstValue) error {
var check any
var compare any
var err error
switch reflect.TypeOf(v).Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
check = reflect.ValueOf(v).Len()
compare, err = helper.ConditionValueToT(check, ast.ConditionValue)
if err != nil {
return err
}
default:
check = v
compare, err = helper.ConditionValueToT(v, ast.ConditionValue)
if err != nil {
return err
}
}
if !Equal(check, compare) {
return fmt.Errorf("value not equal condition %v", ast.ConditionValue)
}
return nil
}
func ValidateNotEqual(v any, ast *model.AstValue) error {
var check any
var compare any
var err error
switch reflect.TypeOf(v).Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
check = reflect.ValueOf(v).Len()
compare, err = helper.ConditionValueToT(check, ast.ConditionValue)
if err != nil {
return err
}
default:
check = v
compare, err = helper.ConditionValueToT(v, ast.ConditionValue)
if err != nil {
return err
}
}
if Equal(check, compare) {
return fmt.Errorf("value equal condition %v", ast.ConditionValue)
}
return nil
}
package validators
import (
"fmt"
"github.com/siherrmann/validator/model"
)
func ValidateFrom[T any](v T, ast *model.AstValue) error {
from, err := From(v, ast.ConditionValue, false)
if err != nil {
return fmt.Errorf("error checking from: %v", err)
}
if !from {
return fmt.Errorf("from %v does not contain value", ast.ConditionValue)
}
return nil
}
func ValidateNotFrom[T any](v T, ast *model.AstValue) error {
notFrom, err := From(v, ast.ConditionValue, true)
if err != nil {
return fmt.Errorf("error checking not from: %v", err)
}
if !notFrom {
return fmt.Errorf("from %v does contain value", ast.ConditionValue)
}
return nil
}
package validators
import (
"fmt"
"strconv"
"github.com/siherrmann/validator/model"
)
func ValidateGroups(groups map[string]*model.Group, groupSize map[string]int, groupErrors map[string][]error) error {
if len(groups) != 0 {
for groupName, group := range groups {
switch group.ConditionType {
case model.MIN_VALUE:
if len(group.ConditionValue) != 0 {
minValue, err := strconv.Atoi(group.ConditionValue)
if err != nil {
return err
} else if (groupSize[groupName] - len(groupErrors[groupName])) < minValue {
return fmt.Errorf("less then %v in group %s without error, all errors: %v", minValue, groupName, groupErrors[groupName])
}
}
case model.MAX_VALUE:
if len(group.ConditionValue) != 0 {
maxValue, err := strconv.Atoi(group.ConditionValue)
if err != nil {
return err
} else if (groupSize[groupName] - len(groupErrors[groupName])) > maxValue {
return fmt.Errorf("more then %v in group %s without error, all errors: %v", maxValue, groupName, groupErrors[groupName])
}
}
default:
return fmt.Errorf("invalid group condition type %s", group.ConditionType)
}
}
}
return nil
}
package validators
import (
"fmt"
"github.com/siherrmann/validator/helper"
"github.com/siherrmann/validator/model"
)
func ValidateMin(v any, ast *model.AstValue) error {
check, err := helper.AnyToFloat(v)
if err != nil {
return fmt.Errorf("invalid value for min validation: %v", err)
}
compare, err := helper.ConditionValueToT(check, ast.ConditionValue)
if err != nil {
return err
}
if !Min(check, compare) {
return fmt.Errorf("value less than minimum condition %v", ast.ConditionValue)
}
return nil
}
func ValidateMax(v any, ast *model.AstValue) error {
check, err := helper.AnyToFloat(v)
if err != nil {
return fmt.Errorf("invalid value for max validation: %v", err)
}
compare, err := helper.ConditionValueToT(check, ast.ConditionValue)
if err != nil {
return err
}
if !Max(check, compare) {
return fmt.Errorf("value greater than maximum condition %v", ast.ConditionValue)
}
return nil
}
package validators
import (
"fmt"
"reflect"
"github.com/siherrmann/validator/helper"
"github.com/siherrmann/validator/model"
)
func ValidateRegex(v any, ast *model.AstValue) error {
switch reflect.TypeOf(v).Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
checks, err := helper.AnyToArrayOfString(v)
if err != nil {
return err
}
for _, check := range checks {
match := Regex(check, ast.ConditionValue)
if !match {
return fmt.Errorf("value %v does not match regex %v", check, ast.ConditionValue)
}
}
default:
check, err := helper.AnyToString(v)
if err != nil {
return fmt.Errorf("error converting value to string: %v", err)
}
match := Regex(check, ast.ConditionValue)
if !match {
return fmt.Errorf("value %v does not match regex %v", check, ast.ConditionValue)
}
}
return nil
}
package validators
import (
"fmt"
"reflect"
"regexp"
"slices"
"strings"
"github.com/siherrmann/validator/helper"
)
func Equal[T comparable](a, equal T) bool {
return a == equal
}
func NotEqual[T comparable](a, notEqual T) bool {
return a != notEqual
}
func Min[T int | float64](v, min T) bool {
return v >= min
}
func Max[T int | float64](v, max T) bool {
return v <= max
}
func Contains(va any, contain string) (bool, error) {
if s, ok := va.(string); ok {
return strings.Contains(s, contain), nil
}
rv := reflect.ValueOf(va)
rt := rv.Type()
switch rt.Kind() {
case reflect.Array, reflect.Slice:
if rv.Len() == 0 {
return false, nil
}
con, err := helper.AnyToType(contain, rt.Elem())
if err != nil {
return false, fmt.Errorf("error converting condition to %T: %v", rt.Elem().Kind, err)
}
vaany, err := helper.ArrayToArrayOfAny(va)
if err != nil {
return false, fmt.Errorf("error converting value to array of any: %v", err)
}
return slices.Contains(vaany, con), nil
case reflect.Map:
if rv.Len() == 0 {
return false, nil
}
con, err := helper.AnyToType(contain, rt.Key())
if err != nil {
return false, fmt.Errorf("error converting condition to %T: %v", rt.Elem().Kind, err)
}
vaany, err := helper.MapKeysToArrayOfAny(va)
if err != nil {
return false, fmt.Errorf("error converting value to array of any: %v", err)
}
return slices.Contains(vaany, con), nil
default:
return false, fmt.Errorf("type %v not supported for contains", reflect.TypeOf(va))
}
}
func From(v any, from string, not bool) (bool, error) {
switch v := v.(type) {
case string, bool,
int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64:
b, err := helper.ConditionValueToArrayOfAny(from, reflect.TypeOf(v))
if err != nil {
return false, err
}
return !not == slices.Contains(b, any(v)), nil
default:
switch reflect.TypeOf(v).Kind() {
case reflect.Array, reflect.Slice:
f, err := helper.ConditionValueToArrayOfAny(from, reflect.TypeOf(v).Elem())
if err != nil {
return false, err
}
conditionValues, err := helper.ArrayToArrayOfAny(f)
if err != nil {
return false, err
}
values := []any{}
rv := reflect.ValueOf(v)
for i := 0; i < rv.Len(); i++ {
avi := rv.Index(i).Interface()
values = append(values, avi)
}
return FromArray(values, conditionValues, not)
case reflect.Map:
f, err := helper.ConditionValueToArrayOfAny(from, reflect.TypeOf(v).Key())
if err != nil {
return false, err
}
conditionValues, err := helper.ArrayToArrayOfAny(f)
if err != nil {
return false, err
}
values := []any{}
rv := reflect.ValueOf(v)
for _, mk := range rv.MapKeys() {
values = append(values, mk.Interface())
}
return FromArray(values, conditionValues, not)
}
return false, fmt.Errorf("type %v not supported for From validation", reflect.TypeOf(v))
}
}
func FromArray[T comparable](v []T, from []T, not bool) (bool, error) {
for _, item := range v {
if not == slices.Contains(from, item) {
return false, nil
}
}
return true, nil
}
func Regex(s, regex string) bool {
matched, _ := regexp.MatchString(regex, s)
return matched
}