diff --git a/datacomparison/entitytype_enumer.go b/datacomparison/entitytype_enumer.go index dcbc48f..e86898e 100644 --- a/datacomparison/entitytype_enumer.go +++ b/datacomparison/entitytype_enumer.go @@ -10,11 +10,11 @@ import ( "strings" ) -const _EntityTypeName = "DataObjectColumnReferenceByName" +const _EntityTypeName = "DataObjectColumnReferenceByNameColumnReferenceByTag" -var _EntityTypeIndex = [...]uint8{0, 10, 31} +var _EntityTypeIndex = [...]uint8{0, 10, 31, 51} -const _EntityTypeLowerName = "dataobjectcolumnreferencebyname" +const _EntityTypeLowerName = "dataobjectcolumnreferencebynamecolumnreferencebytag" func (i EntityType) String() string { if i < 0 || i >= EntityType(len(_EntityTypeIndex)-1) { @@ -33,20 +33,24 @@ func _EntityTypeNoOp() { var x [1]struct{} _ = x[EntityTypeDataObject-(0)] _ = x[EntityTypeColumnReferenceByName-(1)] + _ = x[EntityTypeColumnReferenceByTag-(2)] } -var _EntityTypeValues = []EntityType{EntityTypeDataObject, EntityTypeColumnReferenceByName} +var _EntityTypeValues = []EntityType{EntityTypeDataObject, EntityTypeColumnReferenceByName, EntityTypeColumnReferenceByTag} var _EntityTypeNameToValueMap = map[string]EntityType{ _EntityTypeName[0:10]: EntityTypeDataObject, _EntityTypeLowerName[0:10]: EntityTypeDataObject, _EntityTypeName[10:31]: EntityTypeColumnReferenceByName, _EntityTypeLowerName[10:31]: EntityTypeColumnReferenceByName, + _EntityTypeName[31:51]: EntityTypeColumnReferenceByTag, + _EntityTypeLowerName[31:51]: EntityTypeColumnReferenceByTag, } var _EntityTypeNames = []string{ _EntityTypeName[0:10], _EntityTypeName[10:31], + _EntityTypeName[31:51], } // EntityTypeString retrieves an enum value from the enum constants string name. diff --git a/datacomparison/reference.go b/datacomparison/reference.go index 771da9f..d914a20 100644 --- a/datacomparison/reference.go +++ b/datacomparison/reference.go @@ -2,6 +2,7 @@ package datacomparison import ( "context" + "errors" "fmt" "github.com/collibra/go-bexpression/base" @@ -13,14 +14,24 @@ type EntityType int const ( EntityTypeDataObject EntityType = iota EntityTypeColumnReferenceByName + EntityTypeColumnReferenceByTag ) +var ErrTagKeyRequired = errors.New("tagKey is required for a ColumnReferenceByTag reference") + type Reference struct { EntityType EntityType `json:"entityType,omitempty" yaml:"entityType,omitempty" gqlgen:"entityType"` EntityID string `json:"entityId,omitempty" yaml:"entityId,omitempty" gqlgen:"entityId"` + // TagKey and TagValue select the column for EntityTypeColumnReferenceByTag. An empty TagValue matches any value. + TagKey string `json:"tagKey,omitempty" yaml:"tagKey,omitempty" gqlgen:"tagKey"` + TagValue string `json:"tagValue,omitempty" yaml:"tagValue,omitempty" gqlgen:"tagValue"` } func (r *Reference) Validate(_ context.Context) error { + if r.EntityType == EntityTypeColumnReferenceByTag && r.TagKey == "" { + return ErrTagKeyRequired + } + return nil } diff --git a/datacomparison/reference_test.go b/datacomparison/reference_test.go index fbe6d60..c9eff48 100644 --- a/datacomparison/reference_test.go +++ b/datacomparison/reference_test.go @@ -2,11 +2,13 @@ package datacomparison import ( "context" + "encoding/json" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "gopkg.in/yaml.v3" "github.com/collibra/go-bexpression/base" ) @@ -15,6 +17,8 @@ func TestReference_Accept(t *testing.T) { type fields struct { EntityType EntityType EntityID string + TagKey string + TagValue string } type args struct { ctx context.Context @@ -40,6 +44,21 @@ func TestReference_Accept(t *testing.T) { }, wantErr: false, }, + { + name: "tag reference", + fields: fields{ + EntityType: EntityTypeColumnReferenceByTag, + TagKey: "Country", + TagValue: "DE", + }, + args: args{ + ctx: context.Background(), + visitorSetup: func(visitor *base.MockVisitor) { + visitor.EXPECT().Literal(mock.Anything, &Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country", TagValue: "DE"}).Return(nil).Once() + }, + }, + wantErr: false, + }, { name: "visitor failed", fields: fields{ @@ -63,6 +82,8 @@ func TestReference_Accept(t *testing.T) { r := &Reference{ EntityType: tt.fields.EntityType, EntityID: tt.fields.EntityID, + TagKey: tt.fields.TagKey, + TagValue: tt.fields.TagValue, } if err := r.Accept(tt.args.ctx, visitorMock); (err != nil) != tt.wantErr { t.Errorf("Accept() error = %v, wantErr %v", err, tt.wantErr) @@ -72,7 +93,105 @@ func TestReference_Accept(t *testing.T) { } func TestReference_Validate(t *testing.T) { - r := Reference{} + tests := []struct { + name string + ref Reference + wantErr error + }{ + {name: "empty reference", ref: Reference{}}, + {name: "column reference by name", ref: Reference{EntityType: EntityTypeColumnReferenceByName, EntityID: "COUNTRY"}}, + {name: "tag reference with key", ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country"}}, + {name: "tag reference with key and value", ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country", TagValue: "DE"}}, + {name: "tag reference without key", ref: Reference{EntityType: EntityTypeColumnReferenceByTag}, wantErr: ErrTagKeyRequired}, + {name: "tag reference with value only", ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagValue: "DE"}, wantErr: ErrTagKeyRequired}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.ref.Validate(context.Background()) + + if tt.wantErr == nil { + assert.NoError(t, err) + return + } + assert.ErrorIs(t, err, tt.wantErr) + }) + } +} + +func TestReference_JSONRoundTrip(t *testing.T) { + tests := []struct { + name string + ref Reference + wantJSON string + }{ + { + name: "tag key and value", + ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country", TagValue: "DE"}, + wantJSON: `{"entityType":"ColumnReferenceByTag","tagKey":"Country","tagValue":"DE"}`, + }, + { + name: "tag key only omits the value", + ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country"}, + wantJSON: `{"entityType":"ColumnReferenceByTag","tagKey":"Country"}`, + }, + { + name: "column reference by name omits the tag fields", + ref: Reference{EntityType: EntityTypeColumnReferenceByName, EntityID: "COUNTRY"}, + wantJSON: `{"entityType":"ColumnReferenceByName","entityId":"COUNTRY"}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.ref) + assert.NoError(t, err) + assert.JSONEq(t, tt.wantJSON, string(got)) + + var back Reference + assert.NoError(t, json.Unmarshal(got, &back)) + assert.Equal(t, tt.ref, back) + }) + } +} + +func TestReference_YAMLRoundTrip(t *testing.T) { + tests := []struct { + name string + ref Reference + wantYAML string + }{ + { + name: "tag key and value", + ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country", TagValue: "DE"}, + wantYAML: "entityType: ColumnReferenceByTag\ntagKey: Country\ntagValue: DE\n", + }, + { + name: "tag key only omits the value", + ref: Reference{EntityType: EntityTypeColumnReferenceByTag, TagKey: "Country"}, + wantYAML: "entityType: ColumnReferenceByTag\ntagKey: Country\n", + }, + { + name: "column reference by name omits the tag fields", + ref: Reference{EntityType: EntityTypeColumnReferenceByName, EntityID: "COUNTRY"}, + wantYAML: "entityType: ColumnReferenceByName\nentityId: COUNTRY\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := yaml.Marshal(tt.ref) + assert.NoError(t, err) + assert.Equal(t, tt.wantYAML, string(got)) + + var back Reference + assert.NoError(t, yaml.Unmarshal(got, &back)) + assert.Equal(t, tt.ref, back) + }) + } +} + +func TestEntityType_ColumnReferenceByTag(t *testing.T) { + assert.Equal(t, "ColumnReferenceByTag", EntityTypeColumnReferenceByTag.String()) - assert.NoError(t, r.Validate(context.Background())) + got, err := EntityTypeString("ColumnReferenceByTag") + assert.NoError(t, err) + assert.Equal(t, EntityTypeColumnReferenceByTag, got) } diff --git a/go.mod b/go.mod index 8f1cbdb..97efc7e 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,10 @@ tool ( github.com/vektra/mockery/v3 ) -require github.com/stretchr/testify v1.11.1 +require ( + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v3 v3.0.1 +) require ( github.com/brunoga/deep v1.2.5 // indirect @@ -50,5 +53,4 @@ require ( golang.org/x/term v0.35.0 // indirect golang.org/x/text v0.29.0 // indirect golang.org/x/tools v0.37.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/language b/language index 0a618a0..e900018 100644 --- a/language +++ b/language @@ -11,7 +11,7 @@ BinaryOperand: | Reference: - ENTITYTYPE ENTITYID + ENTITYTYPE ENTITYID | ColumnReferenceByTag TAGKEY [TAGVALUE] Literal: BOOL|INT|FLOAT|STRING|TIMESTAMP