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 }