Skip to content

Commit aab2ec9

Browse files
committed
Merge branch 'main' into shahab/versioning-metrics
2 parents 30b1f73 + 36fd39b commit aab2ec9

File tree

21 files changed

+871
-355
lines changed

21 files changed

+871
-355
lines changed

chasm/collection.go

Lines changed: 0 additions & 3 deletions
This file was deleted.

chasm/fields_iterator.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
)
1010

1111
const (
12-
chasmFieldTypePrefix = "chasm.Field["
13-
chasmCollectionTypePrefix = "chasm.Collection["
12+
chasmFieldTypePrefix = "chasm.Field["
13+
chasmMapTypePrefix = "chasm.Map["
1414

1515
fieldNameTag = "name"
1616
)
@@ -21,7 +21,7 @@ const (
2121
fieldKindUnspecified fieldKind = iota
2222
fieldKindData
2323
fieldKindSubField
24-
fieldKindSubCollection
24+
fieldKindSubMap
2525
)
2626

2727
type fieldInfo struct {
@@ -61,10 +61,10 @@ func fieldsOf(valueV reflect.Value) iter.Seq[fieldInfo] {
6161
switch prefix {
6262
case chasmFieldTypePrefix:
6363
fieldK = fieldKindSubField
64-
case chasmCollectionTypePrefix:
65-
fieldK = fieldKindSubCollection
64+
case chasmMapTypePrefix:
65+
fieldK = fieldKindSubMap
6666
default:
67-
fieldErr = serviceerror.NewInternalf("%s.%s: unsupported field type %s: must implement proto.Message, or be chasm.Field[T] or chasm.Collection[T]", valueT, fieldN, fieldT)
67+
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)
6868
}
6969
}
7070

chasm/fields_iterator_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ func (s *fieldsIteratorSuite) TestGenericTypePrefix() {
5252
expected: chasmFieldTypePrefix,
5353
},
5454
{
55-
name: "Collection type",
56-
input: Collection[string, int]{},
57-
expected: chasmCollectionTypePrefix,
55+
name: "Map type",
56+
input: Map[string, int]{},
57+
expected: chasmMapTypePrefix,
5858
},
5959
{
6060
name: "Non-generic type",
@@ -83,10 +83,10 @@ func (s *fieldsIteratorSuite) TestChasmFieldTypePrefix() {
8383
s.True(strings.HasPrefix(fT.String(), chasmFieldTypePrefix))
8484
}
8585

86-
func (s *fieldsIteratorSuite) TestChasmCollectionTypePrefix() {
87-
c := Collection[string, any]{}
86+
func (s *fieldsIteratorSuite) TestChasmMapTypePrefix() {
87+
c := Map[string, any]{}
8888
cT := reflect.TypeOf(c)
89-
s.True(strings.HasPrefix(cT.String(), chasmCollectionTypePrefix))
89+
s.True(strings.HasPrefix(cT.String(), chasmMapTypePrefix))
9090
}
9191

9292
func (s *fieldsIteratorSuite) TestFieldsOf() {
@@ -96,8 +96,8 @@ func (s *fieldsIteratorSuite) TestFieldsOf() {
9696
}
9797

9898
type noDataField struct {
99-
SubField Field[string]
100-
SubCollection Collection[string, int]
99+
SubField Field[string]
100+
SubMap Map[string, int]
101101
}
102102

103103
type twoDataFields struct {
@@ -121,21 +121,21 @@ func (s *fieldsIteratorSuite) TestFieldsOf() {
121121
name: "Valid component with one data field",
122122
input: &struct {
123123
UnimplementedComponent
124-
DataField *protoMessageType
125-
SubField Field[string]
126-
SubCollection Collection[string, int]
124+
DataField *protoMessageType
125+
SubField Field[string]
126+
SubMap Map[string, int]
127127
}{},
128-
expectedKinds: []fieldKind{fieldKindData, fieldKindSubField, fieldKindSubCollection},
129-
expectedNames: []string{"DataField", "SubField", "SubCollection"},
130-
expectedTypes: []string{"*persistence.WorkflowExecutionState", "chasm.Field[string]", "chasm.Collection[string,int]"},
128+
expectedKinds: []fieldKind{fieldKindData, fieldKindSubField, fieldKindSubMap},
129+
expectedNames: []string{"DataField", "SubField", "SubMap"},
130+
expectedTypes: []string{"*persistence.WorkflowExecutionState", "chasm.Field[string]", "chasm.Map[string,int]"},
131131
expectedErrors: []string{"", "", ""},
132132
},
133133
{
134134
name: "Component with no data field",
135135
input: &noDataField{},
136-
expectedKinds: []fieldKind{fieldKindSubField, fieldKindSubCollection, fieldKindUnspecified},
137-
expectedNames: []string{"SubField", "SubCollection", ""},
138-
expectedTypes: []string{"chasm.Field[string]", "chasm.Collection[string,int]", ""},
136+
expectedKinds: []fieldKind{fieldKindSubField, fieldKindSubMap, fieldKindUnspecified},
137+
expectedNames: []string{"SubField", "SubMap", ""},
138+
expectedTypes: []string{"chasm.Field[string]", "chasm.Map[string,int]", ""},
139139
expectedErrors: []string{"", "", "*chasm.noDataField: no data field (implements proto.Message) found"},
140140
},
141141
{

chasm/map.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package chasm
2+
3+
// mapKeyTypes must match actual Map key type definition.
4+
const mapKeyTypes = "string | int | int8 | int32 | int64 | uint | uint8 | uint32 | uint64 | bool"
5+
6+
type Map[K string | int | int8 | int32 | int64 | uint | uint8 | uint32 | uint64 | bool, T any] map[K]Field[T]

chasm/map_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package chasm
2+
3+
import (
4+
"go/ast"
5+
"go/parser"
6+
"go/printer"
7+
"go/token"
8+
"path/filepath"
9+
"runtime"
10+
"strings"
11+
"testing"
12+
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// Another approach would be to code generate string const.
17+
func TestMapKeyTypesMatchConst(t *testing.T) {
18+
_, currentFile, _, ok := runtime.Caller(0)
19+
require.True(t, ok, "failed to get current file path")
20+
srcFile := filepath.Join(filepath.Dir(currentFile), "map.go")
21+
22+
fset := token.NewFileSet()
23+
file, err := parser.ParseFile(fset, srcFile, nil, parser.AllErrors)
24+
require.NoError(t, err)
25+
26+
var found string
27+
// Walk the top‐level declarations looking for:
28+
// type Map[K ... , T any] map[K]T
29+
for _, decl := range file.Decls {
30+
gd, ok := decl.(*ast.GenDecl)
31+
if !ok || gd.Tok != token.TYPE {
32+
continue
33+
}
34+
for _, spec := range gd.Specs {
35+
ts, ok := spec.(*ast.TypeSpec)
36+
if !ok || ts.Name.Name != "Map" {
37+
continue
38+
}
39+
// ts.TypeParams.List[0] is the field for K
40+
if ts.TypeParams != nil && len(ts.TypeParams.List) > 0 {
41+
field := ts.TypeParams.List[0]
42+
var buf strings.Builder
43+
// pretty‐print the AST node for the constraint
44+
err = printer.Fprint(&buf, fset, field.Type)
45+
require.NoError(t, err)
46+
found = buf.String()
47+
}
48+
}
49+
}
50+
51+
require.NotEmpty(t, found, "could not locate Map[K …] in AST")
52+
require.Equal(t, mapKeyTypes, found)
53+
}

chasm/test_component_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ type (
2424
SubComponent1 Field[*TestSubComponent1]
2525
SubComponent2 Field[*TestSubComponent2]
2626
SubData1 Field[*protoMessageType]
27-
SubComponents Collection[string, *TestSubComponent1]
28-
PendingActivities Collection[int, *TestSubComponent1]
27+
SubComponents Map[string, *TestSubComponent1]
28+
PendingActivities Map[int, *TestSubComponent1]
2929
}
3030

3131
TestSubComponent1 struct {

chasm/tree.go

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ type (
6565

6666
// Type of attributes controls the type of the node.
6767
serializedNode *persistencespb.ChasmNode // serialized component | data | collection with metadata
68-
value any // deserialized component | data | collection
68+
value any // deserialized component | data | map
6969

7070
// valueState indicates if the value field and the persistence field serializedNode are in sync.
7171
// If new value might be changed since it was deserialized and serialize method wasn't called yet, then valueState is valueStateNeedSerialize.
@@ -556,9 +556,9 @@ func (n *Node) syncSubComponentsInternal(
556556
if keepChild {
557557
childrenToKeep[field.name] = struct{}{}
558558
}
559-
case fieldKindSubCollection:
559+
case fieldKindSubMap:
560560
if field.val.IsNil() {
561-
// If Collection field is nil then delete all collection items nodes and collection node itself.
561+
// If Map field is nil then delete all collection items nodes and collection node itself.
562562
continue
563563
}
564564

@@ -570,38 +570,42 @@ func (n *Node) syncSubComponentsInternal(
570570
n.children[field.name] = collectionNode
571571
}
572572

573-
// Validate collection type
573+
// Validate map type.
574574
if field.val.Kind() != reflect.Map {
575-
return serviceerror.NewInternalf("CHASM collection must be of map[comparable]Field[T] type: value of %s is not of a map type", n.nodeName)
575+
errMsg := fmt.Sprintf("CHASM map must be of map type: value of %s is not of a map type", n.nodeName)
576+
softassert.Fail(n.logger, errMsg)
577+
return serviceerror.NewInternal(errMsg)
576578
}
577579

578580
if len(field.val.MapKeys()) == 0 {
579-
// If Collection field is empty then delete all collection items nodes and collection node itself.
581+
// If Map field is empty then delete all collection items nodes and collection node itself.
580582
continue
581583
}
582584

583-
collectionValT := field.typ.Elem()
584-
if collectionValT.Kind() != reflect.Struct || genericTypePrefix(collectionValT) != chasmFieldTypePrefix {
585-
return serviceerror.NewInternalf("CHASM collection must be of map[comparable]Field[T] type: %s map value type is not Field[T] but %s", n.nodeName, collectionValT)
585+
mapValT := field.typ.Elem()
586+
if mapValT.Kind() != reflect.Struct || genericTypePrefix(mapValT) != chasmFieldTypePrefix {
587+
errMsg := fmt.Sprintf("CHASM map value must be of Field[T] type: %s collection value type is not Field[T] but %s", n.nodeName, mapValT)
588+
softassert.Fail(n.logger, errMsg)
589+
return serviceerror.NewInternal(errMsg)
586590
}
587591

588592
collectionItemsToKeep := make(map[string]struct{})
589-
for _, collectionKeyV := range field.val.MapKeys() {
590-
collectionItemV := field.val.MapIndex(collectionKeyV)
591-
collectionKeyStr, err := comparableKeyToString(n.nodeName, collectionKeyV)
593+
for _, mapKeyV := range field.val.MapKeys() {
594+
mapItemV := field.val.MapIndex(mapKeyV)
595+
collectionKey, err := n.mapKeyToString(mapKeyV)
592596
if err != nil {
593597
return err
594598
}
595-
keepItem, updatedCollectionItemV, err := collectionNode.syncSubField(collectionItemV, collectionKeyStr, append(nodePath, field.name))
599+
keepItem, updatedMapItemV, err := collectionNode.syncSubField(mapItemV, collectionKey, append(nodePath, field.name))
596600
if err != nil {
597601
return err
598602
}
599-
if updatedCollectionItemV.IsValid() {
603+
if updatedMapItemV.IsValid() {
600604
// The only way to update item in the map is to set it back.
601-
field.val.SetMapIndex(collectionKeyV, updatedCollectionItemV)
605+
field.val.SetMapIndex(mapKeyV, updatedMapItemV)
602606
}
603607
if keepItem {
604-
collectionItemsToKeep[collectionKeyStr] = struct{}{}
608+
collectionItemsToKeep[collectionKey] = struct{}{}
605609
}
606610
}
607611
if err := collectionNode.deleteChildren(collectionItemsToKeep, append(nodePath, field.name)); err != nil {
@@ -615,7 +619,7 @@ func (n *Node) syncSubComponentsInternal(
615619
return err
616620
}
617621

618-
func comparableKeyToString(nodeName string, keyV reflect.Value) (string, error) {
622+
func (n *Node) mapKeyToString(keyV reflect.Value) (string, error) {
619623
switch keyV.Kind() {
620624
case reflect.String:
621625
return keyV.String(), nil
@@ -626,11 +630,13 @@ func comparableKeyToString(nodeName string, keyV reflect.Value) (string, error)
626630
case reflect.Bool:
627631
return strconv.FormatBool(keyV.Bool()), nil
628632
default:
629-
return "", serviceerror.NewInternalf("CHASM collection must be of map[comparable|string]Field[T] type: %s map key type not comparable but %s", nodeName, keyV.Type().String())
633+
errMsg := fmt.Sprintf("CHASM map key type for node %s must be one of [%s], got %s", n.nodeName, mapKeyTypes, keyV.Type().String())
634+
softassert.Fail(n.logger, errMsg)
635+
return "", serviceerror.NewInternal(errMsg)
630636
}
631637
}
632638

633-
func stringToComparableKey(nodeName string, key string, keyT reflect.Type) (reflect.Value, error) {
639+
func (n *Node) stringToMapKey(nodeName string, key string, keyT reflect.Type) (reflect.Value, error) {
634640
var (
635641
keyV reflect.Value
636642
err error
@@ -683,15 +689,18 @@ func stringToComparableKey(nodeName string, key string, keyT reflect.Type) (refl
683689
b, err = strconv.ParseBool(key)
684690
keyV = reflect.ValueOf(b)
685691
default:
686-
err = fmt.Errorf("unsupported type %s of kind %s", keyT.String(), keyT.Kind().String())
692+
err = fmt.Errorf("unsupported type %s of kind %s: supported key types: %s", keyT.String(), keyT.Kind().String(), mapKeyTypes)
693+
softassert.Fail(n.logger, err.Error())
694+
// Use softassert only here because this is the only case that indicates "compile" time error.
695+
// The other errors below can come from data type mismatch between a component and persisted data.
687696
}
688697

689698
if err == nil && !keyV.IsValid() {
690699
err = fmt.Errorf("value %s is not valid of type %s of kind %s", key, keyT.String(), keyT.Kind().String())
691700
}
692701

693702
if err != nil {
694-
err = serviceerror.NewInternalf("serialized collection %s key value %s can't be parsed to CHASM collection key type %s: %s", nodeName, key, keyT.String(), err.Error())
703+
err = serviceerror.NewInternalf("serialized map %s key value %s can't be parsed to CHASM map key type %s: %s", nodeName, key, keyT.String(), err.Error())
695704
}
696705

697706
return keyV, err
@@ -852,24 +861,24 @@ func (n *Node) deserializeComponentNode(
852861
chasmFieldV.FieldByName(internalFieldName).Set(internalValue)
853862
field.val.Set(chasmFieldV)
854863
}
855-
case fieldKindSubCollection:
864+
case fieldKindSubMap:
856865
if collectionNode, found := n.children[field.name]; found {
857-
collectionFieldV := field.val
858-
if collectionFieldV.IsNil() {
859-
collectionFieldV = reflect.MakeMapWithSize(field.typ, field.val.Len())
860-
field.val.Set(collectionFieldV)
866+
mapFieldV := field.val
867+
if mapFieldV.IsNil() {
868+
mapFieldV = reflect.MakeMapWithSize(field.typ, field.val.Len())
869+
field.val.Set(mapFieldV)
861870
}
862871

863872
for collectionItemName, collectionItemNode := range collectionNode.children {
864873
// field.typ.Elem() is a go type of map item: Field[T]
865874
chasmFieldV := reflect.New(field.typ.Elem()).Elem()
866875
internalValue := reflect.ValueOf(newFieldInternalWithNode(collectionItemNode))
867876
chasmFieldV.FieldByName(internalFieldName).Set(internalValue)
868-
collectionKeyV, err := stringToComparableKey(field.name, collectionItemName, collectionFieldV.Type().Key())
877+
mapKeyV, err := n.stringToMapKey(field.name, collectionItemName, mapFieldV.Type().Key())
869878
if err != nil {
870879
return err
871880
}
872-
collectionFieldV.SetMapIndex(collectionKeyV, chasmFieldV)
881+
mapFieldV.SetMapIndex(mapKeyV, chasmFieldV)
873882
}
874883
}
875884
}

0 commit comments

Comments
 (0)