|
1 | 1 | package chasm |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "errors" |
4 | 5 | "fmt" |
5 | 6 | "time" |
6 | 7 |
|
7 | 8 | enumspb "go.temporal.io/api/enums/v1" |
| 9 | + "go.temporal.io/api/serviceerror" |
8 | 10 | "go.temporal.io/server/common/searchattribute/defs" |
9 | 11 | ) |
10 | 12 |
|
@@ -382,3 +384,167 @@ func (s SearchAttributeKeywordList) Value(value []string) SearchAttributeKeyValu |
382 | 384 | Value: VisibilityValueStringSlice(value), |
383 | 385 | } |
384 | 386 | } |
| 387 | + |
| 388 | +// SearchAttributeMap wraps search attribute values with type-safe access. |
| 389 | +type SearchAttributesMap struct { |
| 390 | + values map[string]VisibilityValue |
| 391 | +} |
| 392 | + |
| 393 | +// NewSearchAttributeMap creates a new SearchAttributeMap from raw values. |
| 394 | +func NewSearchAttributesMap(values map[string]VisibilityValue) SearchAttributesMap { |
| 395 | + return SearchAttributesMap{values: values} |
| 396 | +} |
| 397 | + |
| 398 | +// GetBool returns the boolean value for a given SearchAttributeBool. If not found or map is nil, second parameter is false. |
| 399 | +func (m SearchAttributesMap) GetBool(sa SearchAttributeBool) (bool, bool) { |
| 400 | + if m.values == nil { |
| 401 | + return false, false |
| 402 | + } |
| 403 | + |
| 404 | + alias := sa.definition().alias |
| 405 | + boolValue, ok := m.values[alias].(VisibilityValueBool) |
| 406 | + if !ok { |
| 407 | + return false, false |
| 408 | + } |
| 409 | + |
| 410 | + return bool(boolValue), true |
| 411 | +} |
| 412 | + |
| 413 | +// GetInt returns the int value for a given SearchAttributeInt. If not found or map is nil, second parameter is false. |
| 414 | +func (m SearchAttributesMap) GetInt(sa SearchAttributeInt) (int, bool) { |
| 415 | + if m.values == nil { |
| 416 | + return 0, false |
| 417 | + } |
| 418 | + |
| 419 | + alias := sa.definition().alias |
| 420 | + intValue, ok := m.values[alias].(VisibilityValueInt) |
| 421 | + if !ok { |
| 422 | + return 0, false |
| 423 | + } |
| 424 | + |
| 425 | + return int(intValue), true |
| 426 | +} |
| 427 | + |
| 428 | +// GetDouble returns the double value for a given SearchAttributeDouble. If not found or map is nil, second parameter is false. |
| 429 | +func (m SearchAttributesMap) GetDouble(sa SearchAttributeDouble) (float64, bool) { |
| 430 | + if m.values == nil { |
| 431 | + return 0, false |
| 432 | + } |
| 433 | + |
| 434 | + alias := sa.definition().alias |
| 435 | + doubleValue, ok := m.values[alias].(VisibilityValueFloat64) |
| 436 | + if !ok { |
| 437 | + return 0, false |
| 438 | + } |
| 439 | + |
| 440 | + return float64(doubleValue), true |
| 441 | +} |
| 442 | + |
| 443 | +// GetKeyword returns the string value for a given SearchAttributeKeyword. If not found or map is nil, second parameter is false. |
| 444 | +func (m SearchAttributesMap) GetKeyword(sa SearchAttributeKeyword) (string, bool) { |
| 445 | + if m.values == nil { |
| 446 | + return "", false |
| 447 | + } |
| 448 | + |
| 449 | + alias := sa.definition().alias |
| 450 | + stringValue, ok := m.values[alias].(VisibilityValueString) |
| 451 | + if !ok { |
| 452 | + return "", false |
| 453 | + } |
| 454 | + |
| 455 | + return string(stringValue), true |
| 456 | +} |
| 457 | + |
| 458 | +// GetDateTime returns the time value for a given SearchAttributeDateTime. If not found or map is nil, second parameter is false. |
| 459 | +func (m SearchAttributesMap) GetDateTime(sa SearchAttributeDateTime) (time.Time, bool) { |
| 460 | + if m.values == nil { |
| 461 | + return time.Time{}, false |
| 462 | + } |
| 463 | + |
| 464 | + alias := sa.definition().alias |
| 465 | + timeValue, ok := m.values[alias].(VisibilityValueTime) |
| 466 | + if !ok { |
| 467 | + return time.Time{}, false |
| 468 | + } |
| 469 | + |
| 470 | + return time.Time(timeValue), true |
| 471 | +} |
| 472 | + |
| 473 | +// GetKeywordList returns the string list value for a given SearchAttributeKeywordList. If not found or map is nil, second parameter is false. |
| 474 | +func (m SearchAttributesMap) GetKeywordList(sa SearchAttributeKeywordList) ([]string, bool) { |
| 475 | + if m.values == nil { |
| 476 | + return nil, false |
| 477 | + } |
| 478 | + |
| 479 | + alias := sa.definition().alias |
| 480 | + keywordListValue, ok := m.values[alias].(VisibilityValueStringSlice) |
| 481 | + if !ok { |
| 482 | + return nil, false |
| 483 | + } |
| 484 | + |
| 485 | + return []string(keywordListValue), true |
| 486 | +} |
| 487 | + |
| 488 | +// convertToVisibilityValue converts a value to VisibilityValue based on its runtime type. |
| 489 | +func convertToVisibilityValue(value interface{}) VisibilityValue { |
| 490 | + switch val := value.(type) { |
| 491 | + case int: |
| 492 | + return VisibilityValueInt64(int64(val)) |
| 493 | + case int32: |
| 494 | + return VisibilityValueInt64(int64(val)) |
| 495 | + case int64: |
| 496 | + return VisibilityValueInt64(val) |
| 497 | + case float32: |
| 498 | + return VisibilityValueFloat64(float64(val)) |
| 499 | + case float64: |
| 500 | + return VisibilityValueFloat64(val) |
| 501 | + case bool: |
| 502 | + return VisibilityValueBool(val) |
| 503 | + case time.Time: |
| 504 | + return VisibilityValueTime(val) |
| 505 | + case string: |
| 506 | + // Try to parse as datetime first |
| 507 | + if parsedTime, err := time.Parse(time.RFC3339, val); err == nil { |
| 508 | + return VisibilityValueTime(parsedTime) |
| 509 | + } |
| 510 | + return VisibilityValueString(val) |
| 511 | + case []byte: |
| 512 | + return VisibilityValueByteSlice(val) |
| 513 | + case []string: |
| 514 | + return VisibilityValueStringSlice(val) |
| 515 | + default: |
| 516 | + // Return as string if type is unknown |
| 517 | + return VisibilityValueString(fmt.Sprintf("%v", val)) |
| 518 | + } |
| 519 | +} |
| 520 | + |
| 521 | +// AliasChasmSearchAttributes converts search attribute values to VisibilityValue and aliases field names. |
| 522 | +// It takes a map of field names to interface{} values, converts them to VisibilityValue based on their runtime type, |
| 523 | +// and then aliases the field names using the mapper. |
| 524 | +func AliasChasmSearchAttributes( |
| 525 | + chasmSearchAttributes map[string]interface{}, |
| 526 | + mapper *VisibilitySearchAttributesMapper, |
| 527 | +) (map[string]VisibilityValue, error) { |
| 528 | + if len(chasmSearchAttributes) == 0 { |
| 529 | + return nil, nil |
| 530 | + } |
| 531 | + |
| 532 | + chasmSAs := make(map[string]VisibilityValue, len(chasmSearchAttributes)) |
| 533 | + for fieldName, value := range chasmSearchAttributes { |
| 534 | + visibilityValue := convertToVisibilityValue(value) |
| 535 | + aliasName, err := mapper.Alias(fieldName) |
| 536 | + if err != nil { |
| 537 | + // Silently ignore serviceerror.InvalidArgument because it indicates unmapped field, search attribute is not registered. |
| 538 | + // IMPORTANT: AliasChasmSearchAttributes InvalidArgument indicates a bug in the code, not a user error. |
| 539 | + // Chasm search attributes must be registered with the CHASM Registry using the WithSearchAttributes() option. |
| 540 | + var invalidArgumentErr *serviceerror.InvalidArgument |
| 541 | + if !errors.As(err, &invalidArgumentErr) { |
| 542 | + return nil, err |
| 543 | + } |
| 544 | + continue |
| 545 | + } |
| 546 | + chasmSAs[aliasName] = visibilityValue |
| 547 | + } |
| 548 | + |
| 549 | + return chasmSAs, nil |
| 550 | +} |
0 commit comments