Files
temporal/chasm/parent_pointer.go
Roey Berman 7e73933cf0 Various SAA fixes (#9047)
## What changed?

- Replaced custom search attribute aliases with standard ones:
- ActivityStatus → ExecutionStatus (using the standard execution status
field)
  - ActivityTaskQueue → TaskQueue (using the standard task queue field)
  - ActivityType now uses the alias name directly instead of a constant
- Renamed the GetValue() function to SearchAttributeValue() for better
clarity

- Changed Activity.Store field type from chasm.Field[ActivityStore] to
chasm.ParentPtr[ActivityStore] to avoid the storage overhead of a
pointer that is only ever expected to be a parent
- Fixed error types in handler.go: changed from
serviceerror.NewFailedPrecondition to serviceerror.NewInvalidArgumentf
for invalid policy errors
- Improved ParentPtr.TryGet() to gracefully return false instead of
panicking when not initialized
2026-01-15 23:39:28 +00:00

103 lines
3.1 KiB
Go

package chasm
import (
"fmt"
"reflect"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common/softassert"
)
const (
parentPtrInternalFieldName = "Internal"
)
// ParentPtr is a in-memory pointer to the parent component of a CHASM component.
//
// CHASM map is not a component, so if a component is inside a map, its ParentPtr
// will point to the nearest ancestor component that is not a map.
//
// ParentPtr is only initialized and available for use **after** the transition that
// creates the component using ParentPtr is completed.
type ParentPtr[T any] struct {
// Exporting this field as this generic struct needs to be created via reflection,
// and reflection can't set private fields.
Internal parentPtrInternal
}
type parentPtrInternal struct {
// Storing currentNode instead of parent component Node here so that
// we can differentiate between root node and non-initialized ParentPtr.
currentNode *Node
}
// Get returns the parent component, deserializing it if necessary.
// Panics rather than returning an error, as errors are supposed to be handled by the framework as opposed to the
// application.
func (p ParentPtr[T]) Get(chasmContext Context) T {
vT, ok := p.TryGet(chasmContext)
if !ok {
// nolint:forbidigo // Panic is intended here for framework error handling.
panic(serviceerror.NewInternal("expect parent component value but got nil"))
}
return vT
}
// TryGet returns the parent component and a boolean indicating if the value was found,
// deserializing if necessary.
// Panics rather than returning an error, as errors are supposed to be handled by the framework as opposed to the
// application.
func (p ParentPtr[T]) TryGet(chasmContext Context) (T, bool) {
var nilT T
if p.Internal.currentNode == nil {
// ParentPtr not initialized
return nilT, false
}
parent := p.Internal.currentNode.parent
if parent == nil {
return nilT, false
}
for parent.isMap() {
parent = parent.parent
if parent == nil {
encodedPath, _ := p.Internal.currentNode.getEncodedPath()
// nolint:forbidigo // Panic is intended here for framework error handling.
panic(softassert.UnexpectedInternalErr(
p.Internal.currentNode.logger,
"unable to find parent component for CHASM component inside a map",
fmt.Errorf("child node name: %s", encodedPath),
))
}
}
if !parent.isComponent() {
// nolint:forbidigo // Panic is intended here for framework error handling.
panic(softassert.UnexpectedInternalErr(
parent.logger,
"unexpected CHASM node that has a child component",
fmt.Errorf("node %s, node metadata: %s",
parent.nodeName,
parent.serializedNode.GetMetadata().String(),
),
))
}
if err := parent.prepareComponentValue(chasmContext); err != nil {
// nolint:forbidigo // Panic is intended here for framework error handling.
panic(err)
}
if parent.value == nil {
return nilT, false
}
vT, isT := parent.value.(T)
if !isT {
// nolint:forbidigo // Panic is intended here for framework error handling.
panic(serviceerror.NewInternalf("parent component value doesn't implement %s", reflect.TypeFor[T]().Name()))
}
return vT, true
}