mirror of
https://github.com/temporalio/temporal.git
synced 2026-08-30 18:41:49 -07:00
## What changed? - Previously when creating pointers within a CHASM tree, the pointed-to component had to already exist within the tree, as the pointer was resolved immediately. This was cumbersome in some common cases like setting up an initial tree of CHASM components (such as setting up a scheduler tree), where ideally, all components would be created and linked up within the first transaction. - A new field type, `fieldTypeDeferredPointer`, has been introduced. This field stores the pointer value directly, and resolves the value to the path of the component within the tree during `CloseTransaction` (or fails if the pointer is dangling). ## How did you test it? - [ ] built - [ ] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s)
99 lines
2.4 KiB
Go
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
|
|
}
|