Files
tasks/chasm/fields_iterator.go
Alex ShtinandGitHub 6943eb4b5f CHASM: follow up collection changes (#7795)
## What changed?
Follow up to #7761:
1. Use more concrete types instead of `comparable`.
2. Use `softassert` for "compile" time errors.
3. Rename `chasm.Collection` to `chasm.Map` but left proto
`CollectionAttributes` intact. This will allow to add support for other
collection type in future (slice, array).

## Why?
It is better to narrow key type as much as possible. Other types are not
supported anyway.

## How did you test it?
- [ ] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)
2025-05-29 14:03:03 +00:00

99 lines
2.4 KiB
Go

package chasm
import (
"iter"
"reflect"
"strings"
"go.temporal.io/api/serviceerror"
)
const (
chasmFieldTypePrefix = "chasm.Field["
chasmMapTypePrefix = "chasm.Map["
fieldNameTag = "name"
)
type fieldKind uint8
const (
fieldKindUnspecified fieldKind = iota
fieldKindData
fieldKindSubField
fieldKindSubMap
)
type fieldInfo struct {
val reflect.Value
typ reflect.Type
name string
kind fieldKind
err error
}
//nolint:revive // cognitive complexity 26 (> max enabled 25)
func fieldsOf(valueV reflect.Value) iter.Seq[fieldInfo] {
valueT := valueV.Type()
dataFieldName := ""
return func(yield func(fi fieldInfo) bool) {
for i := 0; i < valueT.Elem().NumField(); i++ {
fieldV := valueV.Elem().Field(i)
fieldT := fieldV.Type()
if fieldT == UnimplementedComponentT {
continue
}
fieldN := fieldName(valueT.Elem().Field(i))
var fieldErr error
fieldK := fieldKindUnspecified
if fieldT.AssignableTo(protoMessageT) {
if dataFieldName != "" {
fieldErr = serviceerror.NewInternalf("%s.%s: only one data field %s (implements proto.Message) allowed in component", valueT, fieldN, dataFieldName)
}
dataFieldName = fieldN
fieldK = fieldKindData
} else {
prefix := genericTypePrefix(fieldT)
if strings.HasPrefix(prefix, "*") {
fieldErr = serviceerror.NewInternalf("%s.%s: chasm field type %s must not be a pointer", valueT, fieldN, fieldT)
} else {
switch prefix {
case chasmFieldTypePrefix:
fieldK = fieldKindSubField
case chasmMapTypePrefix:
fieldK = fieldKindSubMap
default:
fieldErr = serviceerror.NewInternalf("%s.%s: unsupported field type %s: must implement proto.Message, or be chasm.Field[T] or chasm.Map[T]", valueT, fieldN, fieldT)
}
}
}
if !yield(fieldInfo{val: fieldV, typ: fieldT, name: fieldN, kind: fieldK, err: fieldErr}) {
return
}
}
// If the data field is not found, generate one more fake field with only an error set.
if dataFieldName == "" {
yield(fieldInfo{err: serviceerror.NewInternalf("%s: no data field (implements proto.Message) found", valueT)})
}
}
}
func genericTypePrefix(t reflect.Type) string {
tn := t.String()
bracketPos := strings.Index(tn, "[")
if bracketPos == -1 {
return ""
}
return tn[:bracketPos+1]
}
func fieldName(f reflect.StructField) string {
if tagName := f.Tag.Get(fieldNameTag); tagName != "" {
return tagName
}
return f.Name
}