github.com/weaviate/weaviate@v1.24.6/usecases/objects/validation/properties_validation.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package validation
    13  
    14  import (
    15  	"context"
    16  	"encoding/json"
    17  	"fmt"
    18  	"regexp"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/google/uuid"
    23  	"github.com/weaviate/weaviate/entities/models"
    24  	"github.com/weaviate/weaviate/entities/schema"
    25  	"github.com/weaviate/weaviate/entities/schema/crossref"
    26  )
    27  
    28  const (
    29  	// ErrorInvalidSingleRef message
    30  	ErrorInvalidSingleRef string = "only direct references supported at the moment, concept references not supported yet: class '%s' with property '%s' requires exactly 1 arguments: 'beacon'. Check your input schema, got: %#v"
    31  	// ErrorMissingSingleRefCRef message
    32  	ErrorMissingSingleRefCRef string = "only direct references supported at the moment, concept references not supported yet:  class '%s' with property '%s' requires exactly 1 argument: 'beacon' is missing, check your input schema"
    33  	// ErrorCrefInvalidURI message
    34  	ErrorCrefInvalidURI string = "class '%s' with property '%s' is not a valid URI: %s"
    35  	// ErrorCrefInvalidURIPath message
    36  	ErrorCrefInvalidURIPath string = "class '%s' with property '%s' does not contain a valid path, must have 2 segments: /<kind>/<id>"
    37  	// ErrorMissingSingleRefLocationURL message
    38  	ErrorMissingSingleRefLocationURL string = "class '%s' with property '%s' requires exactly 3 arguments: 'beacon', 'locationUrl' and 'type'. 'locationUrl' is missing, check your input schema"
    39  	// ErrorMissingSingleRefType message
    40  	ErrorMissingSingleRefType string = "class '%s' with property '%s' requires exactly 3 arguments: 'beacon', 'locationUrl' and 'type'. 'type' is missing, check your input schema"
    41  )
    42  
    43  func (v *Validator) properties(ctx context.Context, class *models.Class,
    44  	incomingObject *models.Object, existingObject *models.Object,
    45  ) error {
    46  	className := incomingObject.Class
    47  	isp := incomingObject.Properties
    48  	vectorWeights := incomingObject.VectorWeights
    49  	tenant := incomingObject.Tenant
    50  
    51  	if existingObject != nil && tenant != existingObject.Tenant {
    52  		return fmt.Errorf("tenant mismatch, expected %s but got %s", existingObject.Tenant, tenant)
    53  	}
    54  
    55  	if vectorWeights != nil {
    56  		res, err := v.validateVectorWeights(vectorWeights)
    57  		if err != nil {
    58  			return fmt.Errorf("vector weights: %v", err)
    59  		}
    60  
    61  		vectorWeights = res
    62  	}
    63  
    64  	if isp == nil {
    65  		// no properties means nothing to validate
    66  		return nil
    67  	}
    68  
    69  	inputSchema, ok := isp.(map[string]interface{})
    70  	if !ok {
    71  		return fmt.Errorf("could not recognize object's properties: %v", isp)
    72  	}
    73  	returnSchema := map[string]interface{}{}
    74  
    75  	for propertyKey, propertyValue := range inputSchema {
    76  		if propertyValue == nil {
    77  			continue // nil values are removed and filtered out
    78  		}
    79  
    80  		// properties in the class are saved with lower case first letter
    81  		propertyKeyLowerCase := strings.ToLower(propertyKey[:1])
    82  		if len(propertyKey) > 1 {
    83  			propertyKeyLowerCase += propertyKey[1:]
    84  		}
    85  		property, err := schema.GetPropertyByName(class, propertyKeyLowerCase)
    86  		if err != nil {
    87  			return err
    88  		}
    89  		dataType, err := schema.GetPropertyDataType(class, propertyKeyLowerCase)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		// autodetect to_class in references
    95  		if dataType.String() == schema.DataTypeCRef.String() {
    96  			propertyValueSlice, ok := propertyValue.([]interface{})
    97  			if !ok {
    98  				return fmt.Errorf("reference property is not a slice %v", propertyValue)
    99  			}
   100  			for i := range propertyValueSlice {
   101  				propertyValueMap, ok := propertyValueSlice[i].(map[string]interface{})
   102  				if !ok {
   103  					return fmt.Errorf("reference property is not a map: %T", propertyValueMap)
   104  				}
   105  				beacon, ok := propertyValueMap["beacon"].(string)
   106  				if !ok {
   107  					return fmt.Errorf("beacon property is not a string: %T", propertyValueMap["beacon"])
   108  				}
   109  
   110  				beaconParsed, err := crossref.Parse(beacon)
   111  				if err != nil {
   112  					return err
   113  				}
   114  
   115  				if beaconParsed.Class == "" {
   116  					prop, err := schema.GetPropertyByName(class, schema.LowercaseFirstLetter(propertyKey))
   117  					if err != nil {
   118  						return err
   119  					}
   120  					if len(prop.DataType) > 1 {
   121  						continue
   122  					}
   123  					toClass := prop.DataType[0] // datatype is the name of the class that is referenced
   124  					toBeacon := crossref.NewLocalhost(toClass, beaconParsed.TargetID).String()
   125  					propertyValueMap["beacon"] = toBeacon
   126  				}
   127  			}
   128  		}
   129  
   130  		var data interface{}
   131  		if schema.IsNested(*dataType) {
   132  			data, err = v.extractAndValidateNestedProperty(ctx, propertyKeyLowerCase, propertyValue, className,
   133  				dataType, property.NestedProperties)
   134  		} else {
   135  			data, err = v.extractAndValidateProperty(ctx, propertyKeyLowerCase, propertyValue, className, dataType, tenant)
   136  		}
   137  		if err != nil {
   138  			return err
   139  		}
   140  
   141  		returnSchema[propertyKeyLowerCase] = data
   142  	}
   143  
   144  	incomingObject.Properties = returnSchema
   145  	incomingObject.VectorWeights = vectorWeights
   146  
   147  	return nil
   148  }
   149  
   150  func nestedPropertiesToMap(nestedProperties []*models.NestedProperty) map[string]*models.NestedProperty {
   151  	nestedPropertiesMap := map[string]*models.NestedProperty{}
   152  	for _, nestedProperty := range nestedProperties {
   153  		nestedPropertiesMap[nestedProperty.Name] = nestedProperty
   154  	}
   155  	return nestedPropertiesMap
   156  }
   157  
   158  // TODO nested
   159  // refactor/simplify + improve recurring error msgs on nested properties
   160  func (v *Validator) extractAndValidateNestedProperty(ctx context.Context, propertyName string,
   161  	val interface{}, className string, dataType *schema.DataType, nestedProperties []*models.NestedProperty,
   162  ) (interface{}, error) {
   163  	var data interface{}
   164  	var err error
   165  
   166  	switch *dataType {
   167  	case schema.DataTypeObject:
   168  		data, err = objectVal(ctx, v, val, propertyName, className, nestedPropertiesToMap(nestedProperties))
   169  		if err != nil {
   170  			return nil, fmt.Errorf("invalid object property '%s' on class '%s': %w", propertyName, className, err)
   171  		}
   172  	case schema.DataTypeObjectArray:
   173  		data, err = objectArrayVal(ctx, v, val, propertyName, className, nestedPropertiesToMap(nestedProperties))
   174  		if err != nil {
   175  			return nil, fmt.Errorf("invalid object[] property '%s' on class '%s': %w", propertyName, className, err)
   176  		}
   177  	default:
   178  		return nil, fmt.Errorf("unrecognized data type '%s'", *dataType)
   179  	}
   180  
   181  	return data, nil
   182  }
   183  
   184  func objectVal(ctx context.Context, v *Validator, val interface{}, propertyPrefix string,
   185  	className string, nestedPropertiesMap map[string]*models.NestedProperty,
   186  ) (map[string]interface{}, error) {
   187  	typed, ok := val.(map[string]interface{})
   188  	if !ok {
   189  		return nil, fmt.Errorf("object must be a map, but got: %T", val)
   190  	}
   191  
   192  	for nestedKey, nestedValue := range typed {
   193  		propertyName := propertyPrefix + "." + nestedKey
   194  		nestedProperty, ok := nestedPropertiesMap[nestedKey]
   195  		if !ok {
   196  			return nil, fmt.Errorf("unknown property '%s'", propertyName)
   197  		}
   198  
   199  		nestedDataType, err := schema.GetValueDataTypeFromString(nestedProperty.DataType[0])
   200  		if err != nil {
   201  			return nil, fmt.Errorf("property '%s': %w", propertyName, err)
   202  		}
   203  
   204  		var data interface{}
   205  		if schema.IsNested(*nestedDataType) {
   206  			data, err = v.extractAndValidateNestedProperty(ctx, propertyName, nestedValue,
   207  				className, nestedDataType, nestedProperty.NestedProperties)
   208  		} else {
   209  			data, err = v.extractAndValidateProperty(ctx, propertyName, nestedValue,
   210  				className, nestedDataType, "")
   211  			// tenant isn't relevant for nested properties since crossrefs are not allowed
   212  		}
   213  		if err != nil {
   214  			return nil, fmt.Errorf("property '%s': %w", propertyName, err)
   215  		}
   216  		typed[nestedKey] = data
   217  	}
   218  
   219  	return typed, nil
   220  }
   221  
   222  func objectArrayVal(ctx context.Context, v *Validator, val interface{}, propertyPrefix string,
   223  	className string, nestedPropertiesMap map[string]*models.NestedProperty,
   224  ) (interface{}, error) {
   225  	typed, ok := val.([]interface{})
   226  	if !ok {
   227  		return nil, fmt.Errorf("not an object array, but %T", val)
   228  	}
   229  
   230  	for i := range typed {
   231  		data, err := objectVal(ctx, v, typed[i], propertyPrefix, className, nestedPropertiesMap)
   232  		if err != nil {
   233  			return nil, fmt.Errorf("invalid object '%d' in array: %w", i, err)
   234  		}
   235  		typed[i] = data
   236  	}
   237  
   238  	return typed, nil
   239  }
   240  
   241  func (v *Validator) extractAndValidateProperty(ctx context.Context, propertyName string, pv interface{},
   242  	className string, dataType *schema.DataType, tenant string,
   243  ) (interface{}, error) {
   244  	var (
   245  		data interface{}
   246  		err  error
   247  	)
   248  
   249  	switch *dataType {
   250  	case schema.DataTypeCRef:
   251  		data, err = v.cRef(ctx, propertyName, pv, className, tenant)
   252  		if err != nil {
   253  			return nil, fmt.Errorf("invalid cref: %s", err)
   254  		}
   255  	case schema.DataTypeText:
   256  		data, err = stringVal(pv)
   257  		if err != nil {
   258  			return nil, fmt.Errorf("invalid text property '%s' on class '%s': %s", propertyName, className, err)
   259  		}
   260  	case schema.DataTypeUUID:
   261  		data, err = uuidVal(pv)
   262  		if err != nil {
   263  			return nil, fmt.Errorf("invalid uuid property '%s' on class '%s': %s", propertyName, className, err)
   264  		}
   265  	case schema.DataTypeInt:
   266  		data, err = intVal(pv)
   267  		if err != nil {
   268  			return nil, fmt.Errorf("invalid integer property '%s' on class '%s': %s", propertyName, className, err)
   269  		}
   270  	case schema.DataTypeNumber:
   271  		data, err = numberVal(pv)
   272  		if err != nil {
   273  			return nil, fmt.Errorf("invalid number property '%s' on class '%s': %s", propertyName, className, err)
   274  		}
   275  	case schema.DataTypeBoolean:
   276  		data, err = boolVal(pv)
   277  		if err != nil {
   278  			return nil, fmt.Errorf("invalid boolean property '%s' on class '%s': %s", propertyName, className, err)
   279  		}
   280  	case schema.DataTypeDate:
   281  		data, err = dateVal(pv)
   282  		if err != nil {
   283  			return nil, fmt.Errorf("invalid date property '%s' on class '%s': %s", propertyName, className, err)
   284  		}
   285  	case schema.DataTypeGeoCoordinates:
   286  		data, err = geoCoordinates(pv)
   287  		if err != nil {
   288  			return nil, fmt.Errorf("invalid geoCoordinates property '%s' on class '%s': %s", propertyName, className, err)
   289  		}
   290  	case schema.DataTypePhoneNumber:
   291  		data, err = phoneNumber(pv)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("invalid phoneNumber property '%s' on class '%s': %s", propertyName, className, err)
   294  		}
   295  	case schema.DataTypeBlob:
   296  		data, err = blobVal(pv)
   297  		if err != nil {
   298  			return nil, fmt.Errorf("invalid blob property '%s' on class '%s': %s", propertyName, className, err)
   299  		}
   300  	case schema.DataTypeTextArray:
   301  		data, err = stringArrayVal(pv, "text")
   302  		if err != nil {
   303  			return nil, fmt.Errorf("invalid text array property '%s' on class '%s': %s", propertyName, className, err)
   304  		}
   305  	case schema.DataTypeIntArray:
   306  		data, err = intArrayVal(pv)
   307  		if err != nil {
   308  			return nil, fmt.Errorf("invalid integer array property '%s' on class '%s': %s", propertyName, className, err)
   309  		}
   310  	case schema.DataTypeNumberArray:
   311  		data, err = numberArrayVal(pv)
   312  		if err != nil {
   313  			return nil, fmt.Errorf("invalid number array property '%s' on class '%s': %s", propertyName, className, err)
   314  		}
   315  	case schema.DataTypeBooleanArray:
   316  		data, err = boolArrayVal(pv)
   317  		if err != nil {
   318  			return nil, fmt.Errorf("invalid boolean array property '%s' on class '%s': %s", propertyName, className, err)
   319  		}
   320  	case schema.DataTypeDateArray:
   321  		data, err = dateArrayVal(pv)
   322  		if err != nil {
   323  			return nil, fmt.Errorf("invalid date array property '%s' on class '%s': %s", propertyName, className, err)
   324  		}
   325  	case schema.DataTypeUUIDArray:
   326  		data, err = uuidArrayVal(pv)
   327  		if err != nil {
   328  			return nil, fmt.Errorf("invalid uuid array property '%s' on class '%s': %s", propertyName, className, err)
   329  		}
   330  	// deprecated string
   331  	case schema.DataTypeString:
   332  		data, err = stringVal(pv)
   333  		if err != nil {
   334  			return nil, fmt.Errorf("invalid string property '%s' on class '%s': %s", propertyName, className, err)
   335  		}
   336  	// deprecated string
   337  	case schema.DataTypeStringArray:
   338  		data, err = stringArrayVal(pv, "string")
   339  		if err != nil {
   340  			return nil, fmt.Errorf("invalid string array property '%s' on class '%s': %s", propertyName, className, err)
   341  		}
   342  
   343  	default:
   344  		return nil, fmt.Errorf("unrecognized data type '%s'", *dataType)
   345  	}
   346  
   347  	return data, nil
   348  }
   349  
   350  func (v *Validator) cRef(ctx context.Context, propertyName string, pv interface{},
   351  	className, tenant string,
   352  ) (interface{}, error) {
   353  	switch refValue := pv.(type) {
   354  	case map[string]interface{}:
   355  		return nil, fmt.Errorf("reference must be an array, but got a map: %#v", refValue)
   356  	case []interface{}:
   357  		crefs := models.MultipleRef{}
   358  		for _, ref := range refValue {
   359  			refTyped, ok := ref.(map[string]interface{})
   360  			if !ok {
   361  				return nil, fmt.Errorf("Multiple references in %s.%s should be a list of maps, but we got: %T",
   362  					className, propertyName, ref)
   363  			}
   364  
   365  			cref, err := v.parseAndValidateSingleRef(ctx, propertyName, refTyped, className, tenant)
   366  			if err != nil {
   367  				return nil, err
   368  			}
   369  
   370  			crefs = append(crefs, cref)
   371  		}
   372  
   373  		return crefs, nil
   374  	default:
   375  		return nil, fmt.Errorf("invalid ref type. Needs to be []map, got %T", pv)
   376  	}
   377  }
   378  
   379  func stringVal(val interface{}) (string, error) {
   380  	typed, ok := val.(string)
   381  	if !ok {
   382  		return "", fmt.Errorf("not a string, but %T", val)
   383  	}
   384  
   385  	return typed, nil
   386  }
   387  
   388  func boolVal(val interface{}) (bool, error) {
   389  	typed, ok := val.(bool)
   390  	if !ok {
   391  		return false, fmt.Errorf("not a bool, but %T", val)
   392  	}
   393  
   394  	return typed, nil
   395  }
   396  
   397  func dateVal(val interface{}) (time.Time, error) {
   398  	if dateStr, ok := val.(string); ok {
   399  		if date, err := time.Parse(time.RFC3339, dateStr); err == nil {
   400  			return date, nil
   401  		}
   402  	}
   403  
   404  	errorInvalidDate := "requires a string with a RFC3339 formatted date, but the given value is '%v'"
   405  	return time.Time{}, fmt.Errorf(errorInvalidDate, val)
   406  }
   407  
   408  func uuidVal(val interface{}) (uuid.UUID, error) {
   409  	if uuidStr, ok := val.(string); ok {
   410  		if uuid, err := uuid.Parse(uuidStr); err == nil {
   411  			return uuid, nil
   412  		}
   413  	}
   414  
   415  	errorInvalidUuid := "requires a string of UUID format, but the given value is '%v'"
   416  	return uuid.UUID{}, fmt.Errorf(errorInvalidUuid, val)
   417  }
   418  
   419  func intVal(val interface{}) (float64, error) {
   420  	errInvalidInteger := "requires an integer, the given value is '%v'"
   421  	errInvalidIntegerConvertion := "the JSON number '%v' could not be converted to an int"
   422  
   423  	switch typed := val.(type) {
   424  	case json.Number:
   425  		asInt, err := typed.Int64()
   426  		if err != nil {
   427  			// return err when the input can not be converted to an int
   428  			return 0, fmt.Errorf(errInvalidIntegerConvertion, val)
   429  		}
   430  		return float64(asInt), nil
   431  
   432  	case int64:
   433  		return float64(typed), nil
   434  
   435  	case float64:
   436  		if typed != float64(int64(typed)) {
   437  			// return err when float contains a decimal
   438  			return 0, fmt.Errorf(errInvalidInteger, val)
   439  		}
   440  		return typed, nil
   441  
   442  	default:
   443  		return 0, fmt.Errorf(errInvalidInteger, val)
   444  	}
   445  }
   446  
   447  func numberVal(val interface{}) (float64, error) {
   448  	errInvalidFloat := "requires a float, the given value is '%v'"
   449  	errInvalidFloatConvertion := "the JSON number '%v' could not be converted to a float."
   450  
   451  	switch typed := val.(type) {
   452  	case json.Number:
   453  		asFloat, err := typed.Float64()
   454  		if err != nil {
   455  			// return err when the input can not be converted to an int
   456  			return 0, fmt.Errorf(errInvalidFloatConvertion, val)
   457  		}
   458  		return asFloat, nil
   459  
   460  	case int64:
   461  		return float64(typed), nil
   462  
   463  	case float64:
   464  		return typed, nil
   465  
   466  	default:
   467  		return 0, fmt.Errorf(errInvalidFloat, val)
   468  	}
   469  }
   470  
   471  func geoCoordinates(input interface{}) (*models.GeoCoordinates, error) {
   472  	inputMap, ok := input.(map[string]interface{})
   473  	if !ok {
   474  		return nil, fmt.Errorf("geoCoordinates must be a map, but got: %T", input)
   475  	}
   476  
   477  	lon, ok := inputMap["longitude"]
   478  	if !ok {
   479  		return nil, fmt.Errorf("geoCoordinates is missing required field 'longitude'")
   480  	}
   481  
   482  	lat, ok := inputMap["latitude"]
   483  	if !ok {
   484  		return nil, fmt.Errorf("geoCoordinates is missing required field 'latitude'")
   485  	}
   486  
   487  	lonFloat, err := parseCoordinate(lon)
   488  	if err != nil {
   489  		return nil, fmt.Errorf("invalid longitude: %s", err)
   490  	}
   491  
   492  	latFloat, err := parseCoordinate(lat)
   493  	if err != nil {
   494  		return nil, fmt.Errorf("invalid latitude: %s", err)
   495  	}
   496  
   497  	return &models.GeoCoordinates{
   498  		Longitude: ptFloat32(float32(lonFloat)),
   499  		Latitude:  ptFloat32(float32(latFloat)),
   500  	}, nil
   501  }
   502  
   503  func ptFloat32(in float32) *float32 {
   504  	return &in
   505  }
   506  
   507  func phoneNumber(data interface{}) (*models.PhoneNumber, error) {
   508  	dataMap, ok := data.(map[string]interface{})
   509  	if !ok {
   510  		return nil, fmt.Errorf("phoneNumber must be a map, but got: %T", data)
   511  	}
   512  
   513  	input, ok := dataMap["input"]
   514  	if !ok {
   515  		return nil, fmt.Errorf("phoneNumber is missing required field 'input'")
   516  	}
   517  
   518  	inputString, ok := input.(string)
   519  	if !ok {
   520  		return nil, fmt.Errorf("phoneNumber.input must be a string")
   521  	}
   522  
   523  	var defaultCountryString string
   524  	defaultCountry, ok := dataMap["defaultCountry"]
   525  	if !ok {
   526  		defaultCountryString = ""
   527  	} else {
   528  		defaultCountryString, ok = defaultCountry.(string)
   529  		if !ok {
   530  			return nil, fmt.Errorf("phoneNumber.defaultCountry must be a string")
   531  		}
   532  	}
   533  
   534  	return parsePhoneNumber(inputString, defaultCountryString)
   535  }
   536  
   537  func parseCoordinate(raw interface{}) (float64, error) {
   538  	switch v := raw.(type) {
   539  	case json.Number:
   540  		asFloat, err := v.Float64()
   541  		if err != nil {
   542  			return 0, fmt.Errorf("cannot interpret as float: %s", err)
   543  		}
   544  		return asFloat, nil
   545  	case float64:
   546  		return v, nil
   547  	default:
   548  		return 0, fmt.Errorf("must be json.Number or float, but got %T", raw)
   549  	}
   550  }
   551  
   552  func blobVal(val interface{}) (string, error) {
   553  	typed, ok := val.(string)
   554  	if !ok {
   555  		return "", fmt.Errorf("not a blob base64 string, but %T", val)
   556  	}
   557  
   558  	base64Regex := regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$`)
   559  	ok = base64Regex.MatchString(typed)
   560  	if !ok {
   561  		return "", fmt.Errorf("not a valid blob base64 string")
   562  	}
   563  
   564  	return typed, nil
   565  }
   566  
   567  func (v *Validator) parseAndValidateSingleRef(ctx context.Context, propertyName string,
   568  	pvcr map[string]interface{}, className, tenant string,
   569  ) (*models.SingleRef, error) {
   570  	delete(pvcr, "href")
   571  
   572  	// Return different types of errors for cref input
   573  	if len(pvcr) != 1 {
   574  		// Give an error if the cref is not filled with correct number of properties
   575  		return nil, fmt.Errorf(
   576  			ErrorInvalidSingleRef,
   577  			className,
   578  			propertyName,
   579  			pvcr,
   580  		)
   581  	} else if _, ok := pvcr["beacon"]; !ok {
   582  		// Give an error if the cref is not filled with correct properties (beacon)
   583  		return nil, fmt.Errorf(
   584  			ErrorMissingSingleRefCRef,
   585  			className,
   586  			propertyName,
   587  		)
   588  	}
   589  
   590  	ref, err := crossref.Parse(pvcr["beacon"].(string))
   591  	if err != nil {
   592  		return nil, fmt.Errorf("invalid reference: %s", err)
   593  	}
   594  	errVal := fmt.Sprintf("'cref' %s:%s", className, propertyName)
   595  	ref, err = v.ValidateSingleRef(ref.SingleRef())
   596  	if err != nil {
   597  		return nil, err
   598  	}
   599  
   600  	if err = v.ValidateExistence(ctx, ref, errVal, tenant); err != nil {
   601  		return nil, err
   602  	}
   603  
   604  	// Validate whether reference exists based on given Type
   605  	return ref.SingleRef(), nil
   606  }
   607  
   608  // vectorWeights are passed as a non-typed interface{}, this is due to a
   609  // limitation in go-swagger which itself is coming from swagger 2.0 which does
   610  // not have support for arbitrary key/value objects
   611  //
   612  // we must thus validate that it's a map and they keys are strings
   613  // NOTE: We are not validating the semantic correctness of the equations
   614  // themselves, as they are in the contextinoary's responsibility
   615  func (v *Validator) validateVectorWeights(in interface{}) (map[string]string, error) {
   616  	asMap, ok := in.(map[string]interface{})
   617  	if !ok {
   618  		return nil, fmt.Errorf("must be key/value object with strings as keys and values, got %#v", in)
   619  	}
   620  
   621  	out := make(map[string]string, len(asMap))
   622  	for key, value := range asMap {
   623  		asString, ok := value.(string)
   624  		if !ok {
   625  			return nil, fmt.Errorf("key '%s': incorrect datatype: must be string, got %T", key, value)
   626  		}
   627  
   628  		out[key] = asString
   629  	}
   630  
   631  	return out, nil
   632  }
   633  
   634  func stringArrayVal(val interface{}, typeName string) ([]string, error) {
   635  	typed, ok := val.([]interface{})
   636  	if !ok {
   637  		return nil, fmt.Errorf("not a %s array, but %T", typeName, val)
   638  	}
   639  
   640  	data := make([]string, len(typed))
   641  	for i := range typed {
   642  		sval, err := stringVal(typed[i])
   643  		if err != nil {
   644  			return nil, fmt.Errorf("invalid %s array value: %s", typeName, val)
   645  		}
   646  		data[i] = sval
   647  	}
   648  
   649  	return data, nil
   650  }
   651  
   652  func intArrayVal(val interface{}) ([]float64, error) {
   653  	typed, ok := val.([]interface{})
   654  	if !ok {
   655  		return nil, fmt.Errorf("not an integer array, but %T", val)
   656  	}
   657  
   658  	data := make([]float64, len(typed))
   659  	for i := range typed {
   660  		ival, err := intVal(typed[i])
   661  		if err != nil {
   662  			return nil, fmt.Errorf("invalid integer array value: %s", val)
   663  		}
   664  		data[i] = ival
   665  	}
   666  
   667  	return data, nil
   668  }
   669  
   670  func numberArrayVal(val interface{}) ([]float64, error) {
   671  	typed, ok := val.([]interface{})
   672  	if !ok {
   673  		return nil, fmt.Errorf("not an integer array, but %T", val)
   674  	}
   675  
   676  	data := make([]float64, len(typed))
   677  	for i := range typed {
   678  		nval, err := numberVal(typed[i])
   679  		if err != nil {
   680  			return nil, fmt.Errorf("invalid integer array value: %s", val)
   681  		}
   682  		data[i] = nval
   683  	}
   684  
   685  	return data, nil
   686  }
   687  
   688  func boolArrayVal(val interface{}) ([]bool, error) {
   689  	typed, ok := val.([]interface{})
   690  	if !ok {
   691  		return nil, fmt.Errorf("not a boolean array, but %T", val)
   692  	}
   693  
   694  	data := make([]bool, len(typed))
   695  	for i := range typed {
   696  		bval, err := boolVal(typed[i])
   697  		if err != nil {
   698  			return nil, fmt.Errorf("invalid boolean array value: %s", val)
   699  		}
   700  		data[i] = bval
   701  	}
   702  
   703  	return data, nil
   704  }
   705  
   706  func dateArrayVal(val interface{}) ([]time.Time, error) {
   707  	typed, ok := val.([]interface{})
   708  	if !ok {
   709  		return nil, fmt.Errorf("not a date array, but %T", val)
   710  	}
   711  
   712  	data := make([]time.Time, len(typed))
   713  	for i := range typed {
   714  		dval, err := dateVal(typed[i])
   715  		if err != nil {
   716  			return nil, fmt.Errorf("invalid date array value: %s", val)
   717  		}
   718  		data[i] = dval
   719  	}
   720  
   721  	return data, nil
   722  }
   723  
   724  func uuidArrayVal(val interface{}) ([]uuid.UUID, error) {
   725  	typed, ok := val.([]interface{})
   726  	if !ok {
   727  		return nil, fmt.Errorf("not a uuid array, but %T", val)
   728  	}
   729  
   730  	data := make([]uuid.UUID, len(typed))
   731  	for i := range typed {
   732  		uval, err := uuidVal(typed[i])
   733  		if err != nil {
   734  			return nil, fmt.Errorf("invalid uuid array value: %s", val)
   735  		}
   736  		data[i] = uval
   737  	}
   738  
   739  	return data, nil
   740  }
   741  
   742  func ParseUUIDArray(in any) ([]uuid.UUID, error) {
   743  	var err error
   744  
   745  	if parsed, ok := in.([]uuid.UUID); ok {
   746  		return parsed, nil
   747  	}
   748  
   749  	asSlice, ok := in.([]any)
   750  	if !ok {
   751  		return nil, fmt.Errorf("not a slice type: %T", in)
   752  	}
   753  
   754  	d := make([]uuid.UUID, len(asSlice))
   755  	for i, elem := range asSlice {
   756  		asUUID, ok := elem.(uuid.UUID)
   757  		if ok {
   758  			d[i] = asUUID
   759  			continue
   760  		}
   761  
   762  		asStr, ok := elem.(string)
   763  		if !ok {
   764  			return nil, fmt.Errorf("array element neither uuid.UUID nor str, but: %T", elem)
   765  		}
   766  
   767  		d[i], err = uuid.Parse(asStr)
   768  		if err != nil {
   769  			return nil, fmt.Errorf("at pos %d: %w", i, err)
   770  		}
   771  	}
   772  
   773  	return d, nil
   774  }