Repository: klothoplatform/klotho
Pull request #891 (2024-01-18 13:55:33)

Op side e deep
Look at nested properties for determining operational side effects

### Standard checks

- **Unit tests**: Any special considerations?
- **Docs**: Do we need to update any docs, internal or public?
- **Backwards compatibility**: Will this break existing apps? If so, what would be the extra work required to keep them working?



Patch:
diff --git a/pkg/engine2/operational_eval/vertex_property.go b/pkg/engine2/operational_eval/vertex_property.go
index 3f313900..a81ea48c 100644
--- a/pkg/engine2/operational_eval/vertex_property.go
+++ b/pkg/engine2/operational_eval/vertex_property.go
@@ -402,7 +402,6 @@ func addConfigurationRuleToPropertyVertex(
 			))
 			continue
 		}
-
 		key := Key{Ref: ref}
 		vertex, err := eval.graph.Vertex(key)
 		if err != nil {
diff --git a/pkg/knowledge_base2/graph.go b/pkg/knowledge_base2/graph.go
index 5690a210..b899d4f9 100644
--- a/pkg/knowledge_base2/graph.go
+++ b/pkg/knowledge_base2/graph.go
@@ -293,23 +293,35 @@ func IsOperationalResourceSideEffect(dag construct.Graph, kb TemplateKB, rid, si
 	}
 
 	dynCtx := DynamicValueContext{Graph: dag, KnowledgeBase: kb}
-	for _, property := range template.Properties {
+
+	isSideEffect := false
+
+	err = template.LoopProperties(resource, func(property Property) error {
 		ruleSatisfied := false
 		rule := property.Details().OperationalRule
 		if rule == nil || len(rule.Step.Resources) == 0 {
-			continue
+			return nil
+
 		}
+		path, err := resource.PropertyPath(property.Details().Path)
+		if err != nil {
+			return fmt.Errorf(
+				"error checking if %s is side effect of %s in property %s: %w",
+				sideEffect, rid, property.Details().Name, err,
+			)
+		}
+		data := DynamicValueData{Resource: rid, Path: path}
 		step := rule.Step
 		// We only check if the resource selector is a match in terms of properties and classifications (not the actual id)
 		// We do this because if we have explicit ids in the selector and someone changes the id of a side effect resource
 		// we would no longer think it is a side effect since the id would no longer match.
 		// To combat this we just check against type
 		for j, resourceSelector := range step.Resources {
-			if match, err := resourceSelector.IsMatch(dynCtx, DynamicValueData{Resource: rid}, sideEffectResource); match {
+			if match, err := resourceSelector.IsMatch(dynCtx, data, sideEffectResource); match {
 				ruleSatisfied = true
 				break
 			} else if err != nil {
-				return false, fmt.Errorf(
+				return fmt.Errorf(
 					"error checking if %s is side effect of %s in property %s, resource %d: %w",
 					sideEffect, rid, property.Details().Name, j, err,
 				)
@@ -317,7 +329,8 @@ func IsOperationalResourceSideEffect(dag construct.Graph, kb TemplateKB, rid, si
 		}
 
 		if !ruleSatisfied {
-			continue
+			return nil
+
 		}
 
 		// If the side effect resource fits the rule we then perform 2 more checks
@@ -326,35 +339,46 @@ func IsOperationalResourceSideEffect(dag construct.Graph, kb TemplateKB, rid, si
 		if step.Direction == DirectionUpstream {
 			resources, err := graph.ShortestPathStable(dag, sideEffect, rid, construct.ResourceIdLess)
 			if len(resources) == 0 || err != nil {
-				continue
+				return nil
+
 			}
 		} else {
 			resources, err := graph.ShortestPathStable(dag, rid, sideEffect, construct.ResourceIdLess)
 			if len(resources) == 0 || err != nil {
-				continue
+				return nil
+
 			}
 		}
 
 		propertyVal, err := resource.GetProperty(property.Details().Path)
 		if err != nil || propertyVal == nil {
-			continue
+			return nil
+
 		}
 		val := reflect.ValueOf(propertyVal)
 		if val.Kind() == reflect.Array || val.Kind() == reflect.Slice {
 			for i := 0; i < val.Len(); i++ {
 				if arrId, ok := val.Index(i).Interface().(construct.ResourceId); ok && arrId == sideEffect {
-					return true, nil
+					isSideEffect = true
+					return ErrStopWalk
+				} else if ref, ok := val.Index(i).Interface().(construct.PropertyRef); ok && ref.Resource == sideEffect {
+					isSideEffect = true
+					return ErrStopWalk
 				}
 			}
 		} else {
 			if val.IsZero() {
-				continue
+				return nil
 			}
 			if valId, ok := val.Interface().(construct.ResourceId); ok && valId == sideEffect {
-				return true, nil
+				isSideEffect = true
+				return ErrStopWalk
+			} else if ref, ok := val.Interface().(construct.PropertyRef); ok && ref.Resource == sideEffect {
+				isSideEffect = true
+				return ErrStopWalk
 			}
 		}
-
-	}
-	return false, nil
+		return nil
+	})
+	return isSideEffect, err
 }
diff --git a/pkg/knowledge_base2/resource_template.go b/pkg/knowledge_base2/resource_template.go
index 0471a5d3..b595f23f 100644
--- a/pkg/knowledge_base2/resource_template.go
+++ b/pkg/knowledge_base2/resource_template.go
@@ -13,6 +13,8 @@ import (
 	"gopkg.in/yaml.v3"
 )
 
+//go:generate 	mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase2
+
 type (
 	// ResourceTemplate defines how rules are handled by the engine in terms of making sure they are functional in the graph
 	ResourceTemplate struct {
diff --git a/pkg/templates/aws/resources/ecs_task_definition.yaml b/pkg/templates/aws/resources/ecs_task_definition.yaml
index 42b0fbf7..b23c5d55 100644
--- a/pkg/templates/aws/resources/ecs_task_definition.yaml
+++ b/pkg/templates/aws/resources/ecs_task_definition.yaml
@@ -45,6 +45,7 @@ properties:
         description: The environment variables to pass to a container
       Image:
         type: resource(aws:ecr_image)
+        required: true
         operational_rule:
           step:
             direction: downstream


Test patch:
diff --git a/pkg/engine2/testdata/ecs_rds.dataflow-viz.yaml b/pkg/engine2/testdata/ecs_rds.dataflow-viz.yaml
index 902067bc..2e18e294 100755
--- a/pkg/engine2/testdata/ecs_rds.dataflow-viz.yaml
+++ b/pkg/engine2/testdata/ecs_rds.dataflow-viz.yaml
@@ -23,8 +23,11 @@ resources:
 
   ecs_service/ecs_service_0:
     children:
+        - aws:ecr_image:ecs_service_0-ecs_service_0
+        - aws:ecr_repo:ecr_repo-0
         - aws:ecs_task_definition:ecs_service_0
         - aws:iam_role:ecs_service_0-execution-role
+        - aws:log_group:ecs_service_0-log-group
     parent: vpc/vpc-0
     tag: big
 
diff --git a/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml b/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
index 9cfab9ef..a4b4ad68 100755
--- a/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
+++ b/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
@@ -2,7 +2,10 @@ provider: aws
 resources:
   kubernetes:pod:eks_cluster-0/pod2:
     children:
+        - aws:ecr_image:pod2-ecr_image
+        - aws:ecr_repo:ecr_repo-0
         - aws:iam_role:ClusterRole-eks_cluster-0
+        - kubernetes:service_account:eks_cluster-0:pod2
     parent: eks_cluster/eks_cluster-0
     tag: big
 
diff --git a/pkg/knowledge_base2/graph_test.go b/pkg/knowledge_base2/graph_test.go
new file mode 100644
index 00000000..d6a0f770
--- /dev/null
+++ b/pkg/knowledge_base2/graph_test.go
@@ -0,0 +1,211 @@
+package knowledgebase2
+
+import (
+	"testing"
+
+	construct "github.com/klothoplatform/klotho/pkg/construct2"
+	"github.com/klothoplatform/klotho/pkg/construct2/graphtest"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/mock/gomock"
+)
+
+func TestIsOperationalResourceSideEffect(t *testing.T) {
+	type args struct {
+		resource   *construct.Resource
+		sideEffect construct.ResourceId
+	}
+	tests := []struct {
+		name         string
+		initialState []any
+		args         args
+		want         bool
+		mocks        func(mockProp *MockProperty, mockNestedProp *MockProperty, mockKB *MockTemplateKB)
+		wantErr      bool
+	}{
+		{
+			name:         "operational resource side effect, shallow resource id",
+			initialState: []any{"p:t:side", "p:t:test -> p:t:side"},
+			args: args{
+				resource: &construct.Resource{
+					ID: construct.ResourceId{
+						Provider: "p",
+						Type:     "t",
+						Name:     "test",
+					},
+					Properties: construct.Properties{
+						"test": construct.ResourceId{
+							Provider: "p",
+							Type:     "t",
+							Name:     "side",
+						},
+					},
+				},
+				sideEffect: construct.ResourceId{
+					Provider: "p",
+					Type:     "t",
+					Name:     "side",
+				},
+			},
+			mocks: func(mockProp *MockProperty, mockNestedProp *MockProperty, mockKB *MockTemplateKB) {
+				template := &ResourceTemplate{
+					Properties: Properties{
+						"test": mockProp,
+					},
+				}
+				mockKB.EXPECT().GetResourceTemplate(gomock.Any()).Return(template, nil).AnyTimes()
+				mockProp.EXPECT().Details().Return(&PropertyDetails{
+					OperationalRule: &PropertyRule{
+						Step: OperationalStep{
+							Direction: DirectionDownstream,
+							Resources: []ResourceSelector{
+								{
+									Selector: "p:t:side",
+								},
+							},
+						},
+					},
+					Name: "test",
+					Path: "test",
+				}).AnyTimes()
+				mockProp.EXPECT().Type().Return("resource").AnyTimes()
+				mockProp.EXPECT().SubProperties().Return(nil).AnyTimes()
+			},
+			want: true,
+		},
+		{
+			name:         "operational resource side effect, shallow property ref",
+			initialState: []any{"p:t:side", "p:t:test -> p:t:side"},
+			args: args{
+				resource: &construct.Resource{
+					ID: construct.ResourceId{
+						Provider: "p",
+						Type:     "t",
+						Name:     "test",
+					},
+					Properties: construct.Properties{
+						"test": construct.PropertyRef{
+							Resource: construct.ResourceId{
+								Provider: "p",
+								Type:     "t",
+								Name:     "side",
+							},
+							Property: "test",
+						},
+					},
+				},
+				sideEffect: construct.ResourceId{
+					Provider: "p",
+					Type:     "t",
+					Name:     "side",
+				},
+			},
+			mocks: func(mockProp *MockProperty, mockNestedProp *MockProperty, mockKB *MockTemplateKB) {
+				template := &ResourceTemplate{
+					Properties: Properties{
+						"test": mockProp,
+					},
+				}
+				mockKB.EXPECT().GetResourceTemplate(gomock.Any()).Return(template, nil).AnyTimes()
+				mockProp.EXPECT().Details().Return(&PropertyDetails{
+					OperationalRule: &PropertyRule{
+						Step: OperationalStep{
+							Direction: DirectionDownstream,
+							Resources: []ResourceSelector{
+								{
+									Selector: "p:t:side",
+								},
+							},
+						},
+					},
+					Name: "test",
+					Path: "test",
+				}).AnyTimes()
+				mockProp.EXPECT().Type().Return("resource").AnyTimes()
+				mockProp.EXPECT().SubProperties().Return(nil).AnyTimes()
+			},
+			want: true,
+		},
+		{
+			name:         "operational resource side effect, nested resource id",
+			initialState: []any{"p:t:side", "p:t:test -> p:t:side"},
+			args: args{
+				resource: &construct.Resource{
+					ID: construct.ResourceId{
+						Provider: "p",
+						Type:     "t",
+						Name:     "test",
+					},
+					Properties: construct.Properties{
+						"test": map[string]any{
+							"test": construct.ResourceId{
+								Provider: "p",
+								Type:     "t",
+								Name:     "side",
+							},
+						},
+					},
+				},
+				sideEffect: construct.ResourceId{
+					Provider: "p",
+					Type:     "t",
+					Name:     "side",
+				},
+			},
+			mocks: func(mockProp *MockProperty, mockNestedProp *MockProperty, mockKB *MockTemplateKB) {
+				template := &ResourceTemplate{
+					Properties: Properties{
+						"test": mockProp,
+					},
+				}
+				mockKB.EXPECT().GetResourceTemplate(gomock.Any()).Return(template, nil).AnyTimes()
+				mockProp.EXPECT().Type().Return("map").AnyTimes()
+				mockProp.EXPECT().SubProperties().Return(map[string]Property{
+					"test": mockNestedProp,
+				}).AnyTimes()
+				mockProp.EXPECT().Details().Return(&PropertyDetails{
+					Name: "test",
+					Path: "test",
+				}).AnyTimes()
+				mockNestedProp.EXPECT().Details().Return(&PropertyDetails{
+					OperationalRule: &PropertyRule{
+						Step: OperationalStep{
+							Direction: DirectionDownstream,
+							Resources: []ResourceSelector{
+								{
+									Selector: "p:t:side",
+								},
+							},
+						},
+					},
+					Name: "test",
+					Path: "test.test",
+				}).AnyTimes()
+				mockNestedProp.EXPECT().Type().Return("resource").AnyTimes()
+				mockNestedProp.EXPECT().SubProperties().Return(nil).AnyTimes()
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			ctrl := gomock.NewController(t)
+			mockProp := NewMockProperty(ctrl)
+			mockNestedProp := NewMockProperty(ctrl)
+			mockKB := NewMockTemplateKB(ctrl)
+			g := construct.NewGraph()
+			err := g.AddVertex(tt.args.resource)
+			assert.NoError(err)
+			dag := graphtest.MakeGraph(t, g, tt.initialState...)
+			tt.mocks(mockProp, mockNestedProp, mockKB)
+			got, err := IsOperationalResourceSideEffect(dag, mockKB, tt.args.resource.ID, tt.args.sideEffect)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			assert.NoError(err)
+			assert.Equal(tt.want, got)
+			ctrl.Finish()
+		})
+	}
+}
diff --git a/pkg/knowledge_base2/resource_template_mock_test.go b/pkg/knowledge_base2/resource_template_mock_test.go
new file mode 100644
index 00000000..fc332dcc
--- /dev/null
+++ b/pkg/knowledge_base2/resource_template_mock_test.go
@@ -0,0 +1,298 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: ./resource_template.go
+//
+// Generated by this command:
+//
+//	mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase2
+//
+
+// Package knowledgebase2 is a generated GoMock package.
+package knowledgebase2
+
+import (
+	reflect "reflect"
+
+	construct2 "github.com/klothoplatform/klotho/pkg/construct2"
+	gomock "go.uber.org/mock/gomock"
+)
+
+// MockProperty is a mock of Property interface.
+type MockProperty struct {
+	ctrl     *gomock.Controller
+	recorder *MockPropertyMockRecorder
+}
+
+// MockPropertyMockRecorder is the mock recorder for MockProperty.
+type MockPropertyMockRecorder struct {
+	mock *MockProperty
+}
+
+// NewMockProperty creates a new mock instance.
+func NewMockProperty(ctrl *gomock.Controller) *MockProperty {
+	mock := &MockProperty{ctrl: ctrl}
+	mock.recorder = &MockPropertyMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockProperty) EXPECT() *MockPropertyMockRecorder {
+	return m.recorder
+}
+
+// AppendProperty mocks base method.
+func (m *MockProperty) AppendProperty(resource *construct2.Resource, value any) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AppendProperty", resource, value)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AppendProperty indicates an expected call of AppendProperty.
+func (mr *MockPropertyMockRecorder) AppendProperty(resource, value any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendProperty", reflect.TypeOf((*MockProperty)(nil).AppendProperty), resource, value)
+}
+
+// Clone mocks base method.
+func (m *MockProperty) Clone() Property {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Clone")
+	ret0, _ := ret[0].(Property)
+	return ret0
+}
+
+// Clone indicates an expected call of Clone.
+func (mr *MockPropertyMockRecorder) Clone() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clone", reflect.TypeOf((*MockProperty)(nil).Clone))
+}
+
+// Contains mocks base method.
+func (m *MockProperty) Contains(value, contains any) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Contains", value, contains)
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// Contains indicates an expected call of Contains.
+func (mr *MockPropertyMockRecorder) Contains(value, contains any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contains", reflect.TypeOf((*MockProperty)(nil).Contains), value, contains)
+}
+
+// Details mocks base method.
+func (m *MockProperty) Details() *PropertyDetails {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Details")
+	ret0, _ := ret[0].(*PropertyDetails)
+	return ret0
+}
+
+// Details indicates an expected call of Details.
+func (mr *MockPropertyMockRecorder) Details() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Details", reflect.TypeOf((*MockProperty)(nil).Details))
+}
+
+// GetDefaultValue mocks base method.
+func (m *MockProperty) GetDefaultValue(ctx DynamicContext, data DynamicValueData) (any, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetDefaultValue", ctx, data)
+	ret0, _ := ret[0].(any)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetDefaultValue indicates an expected call of GetDefaultValue.
+func (mr *MockPropertyMockRecorder) GetDefaultValue(ctx, data any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultValue", reflect.TypeOf((*MockProperty)(nil).GetDefaultValue), ctx, data)
+}
+
+// Parse mocks base method.
+func (m *MockProperty) Parse(value any, ctx DynamicContext, data DynamicValueData) (any, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Parse", value, ctx, data)
+	ret0, _ := ret[0].(any)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Parse indicates an expected call of Parse.
+func (mr *MockPropertyMockRecorder) Parse(value, ctx, data any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parse", reflect.TypeOf((*MockProperty)(nil).Parse), value, ctx, data)
+}
+
+// RemoveProperty mocks base method.
+func (m *MockProperty) RemoveProperty(resource *construct2.Resource, value any) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "RemoveProperty", resource, value)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// RemoveProperty indicates an expected call of RemoveProperty.
+func (mr *MockPropertyMockRecorder) RemoveProperty(resource, value any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProperty", reflect.TypeOf((*MockProperty)(nil).RemoveProperty), resource, value)
+}
+
+// SetProperty mocks base method.
+func (m *MockProperty) SetProperty(resource *construct2.Resource, value any) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetProperty", resource, value)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetProperty indicates an expected call of SetProperty.
+func (mr *MockPropertyMockRecorder) SetProperty(resource, value any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProperty", reflect.TypeOf((*MockProperty)(nil).SetProperty), resource, value)
+}
+
+// SubProperties mocks base method.
+func (m *MockProperty) SubProperties() Properties {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SubProperties")
+	ret0, _ := ret[0].(Properties)
+	return ret0
+}
+
+// SubProperties indicates an expected call of SubProperties.
+func (mr *MockPropertyMockRecorder) SubProperties() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubProperties", reflect.TypeOf((*MockProperty)(nil).SubProperties))
+}
+
+// Type mocks base method.
+func (m *MockProperty) Type() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Type")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// Type indicates an expected call of Type.
+func (mr *MockPropertyMockRecorder) Type() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockProperty)(nil).Type))
+}
+
+// Validate mocks base method.
+func (m *MockProperty) Validate(resource *construct2.Resource, value any, ctx DynamicContext) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Validate", resource, value, ctx)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Validate indicates an expected call of Validate.
+func (mr *MockPropertyMockRecorder) Validate(resource, value, ctx any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockProperty)(nil).Validate), resource, value, ctx)
+}
+
+// ZeroValue mocks base method.
+func (m *MockProperty) ZeroValue() any {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ZeroValue")
+	ret0, _ := ret[0].(any)
+	return ret0
+}
+
+// ZeroValue indicates an expected call of ZeroValue.
+func (mr *MockPropertyMockRecorder) ZeroValue() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ZeroValue", reflect.TypeOf((*MockProperty)(nil).ZeroValue))
+}
+
+// MockMapProperty is a mock of MapProperty interface.
+type MockMapProperty struct {
+	ctrl     *gomock.Controller
+	recorder *MockMapPropertyMockRecorder
+}
+
+// MockMapPropertyMockRecorder is the mock recorder for MockMapProperty.
+type MockMapPropertyMockRecorder struct {
+	mock *MockMapProperty
+}
+
+// NewMockMapProperty creates a new mock instance.
+func NewMockMapProperty(ctrl *gomock.Controller) *MockMapProperty {
+	mock := &MockMapProperty{ctrl: ctrl}
+	mock.recorder = &MockMapPropertyMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockMapProperty) EXPECT() *MockMapPropertyMockRecorder {
+	return m.recorder
+}
+
+// Key mocks base method.
+func (m *MockMapProperty) Key() Property {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Key")
+	ret0, _ := ret[0].(Property)
+	return ret0
+}
+
+// Key indicates an expected call of Key.
+func (mr *MockMapPropertyMockRecorder) Key() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockMapProperty)(nil).Key))
+}
+
+// Value mocks base method.
+func (m *MockMapProperty) Value() Property {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Value")
+	ret0, _ := ret[0].(Property)
+	return ret0
+}
+
+// Value indicates an expected call of Value.
+func (mr *MockMapPropertyMockRecorder) Value() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockMapProperty)(nil).Value))
+}
+
+// MockCollectionProperty is a mock of CollectionProperty interface.
+type MockCollectionProperty struct {
+	ctrl     *gomock.Controller
+	recorder *MockCollectionPropertyMockRecorder
+}
+
+// MockCollectionPropertyMockRecorder is the mock recorder for MockCollectionProperty.
+type MockCollectionPropertyMockRecorder struct {
+	mock *MockCollectionProperty
+}
+
+// NewMockCollectionProperty creates a new mock instance.
+func NewMockCollectionProperty(ctrl *gomock.Controller) *MockCollectionProperty {
+	mock := &MockCollectionProperty{ctrl: ctrl}
+	mock.recorder = &MockCollectionPropertyMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockCollectionProperty) EXPECT() *MockCollectionPropertyMockRecorder {
+	return m.recorder
+}
+
+// Item mocks base method.
+func (m *MockCollectionProperty) Item() Property {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Item")
+	ret0, _ := ret[0].(Property)
+	return ret0
+}
+
+// Item indicates an expected call of Item.
+func (mr *MockCollectionPropertyMockRecorder) Item() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Item", reflect.TypeOf((*MockCollectionProperty)(nil).Item))
+}