From 13fe2222dd4c6b6c84d9b5599df876effa5569cf Mon Sep 17 00:00:00 2001 From: Eden Kirin Date: Sun, 23 Jun 2024 18:16:32 +0200 Subject: [PATCH] Filter field names --- app/main.go | 20 ++++- app/repository/smartfilter/filterfield.go | 1 - app/repository/smartfilter/handlers_test.go | 22 +++--- app/repository/smartfilter/smartfilter.go | 57 ++++++++------ .../smartfilter/smartfilter_test.go | 74 +++++++++++++++++++ 5 files changed, 134 insertions(+), 40 deletions(-) diff --git a/app/main.go b/app/main.go index 30c07a4..7fe9a16 100644 --- a/app/main.go +++ b/app/main.go @@ -8,6 +8,7 @@ import ( "repo-pattern/app/models" "repo-pattern/app/repository" "repo-pattern/app/repository/smartfilter" + "time" "gorm.io/gorm" ) @@ -17,6 +18,17 @@ var ( FALSE = false ) +type CertFilter struct { + Alive *bool `filterfield:"field=alive,operator=EQ"` + SerialNumber *string `filterfield:"field=serial_number,operator=NE"` + SerialNumberContains *string `filterfield:"field=serial_number,operator=LIKE"` + IssuerContains *string `filterfield:"field=issuer,operator=ILIKE"` + Id *string `filterfield:"field=id,operator=EQ"` + Ids *[]string `filterfield:"field=id,operator=IN"` + IdsNot *[]string `filterfield:"field=id,operator=NOT_IN"` + CreatedAt_Lt *time.Time `filterfield:"field=created_at,operator=LT"` +} + func doMagic(db *gorm.DB) { var err error query := db @@ -29,7 +41,7 @@ func doMagic(db *gorm.DB) { // createdTime := time.Date(2024, 5, 26, 16, 8, 0, 0, location) ids := []string{"eb2bcac6-5173-4dbb-93b7-e7c03b924a03", "db9fb837-3483-4736-819d-f427dc8cda23", "1fece5e7-8e8d-4828-8298-3b1f07fd29ff"} - filter := smartfilter.CertFilter{ + filter := CertFilter{ // Alive: &FALSE, // Id: &id, // SerialNumber: &serialNumber, @@ -56,7 +68,7 @@ func doList(db *gorm.DB) { repo := repository.RepoBase[models.Cert]{} repo.Init(db) - filter := smartfilter.CertFilter{ + filter := CertFilter{ Alive: &TRUE, } @@ -76,7 +88,7 @@ func doGet(db *gorm.DB) { id := "db9fb837-3483-4736-819d-f427dc8cda23" - filter := smartfilter.CertFilter{ + filter := CertFilter{ Id: &id, } @@ -93,7 +105,7 @@ func doExists(db *gorm.DB) { id := "db9fb837-3483-4736-819d-f427dc8cda23" - filter := smartfilter.CertFilter{ + filter := CertFilter{ Id: &id, } diff --git a/app/repository/smartfilter/filterfield.go b/app/repository/smartfilter/filterfield.go index e2c2fa4..1593cdc 100644 --- a/app/repository/smartfilter/filterfield.go +++ b/app/repository/smartfilter/filterfield.go @@ -234,7 +234,6 @@ type sliceGetter struct { func (sg sliceGetter) getValue(ff *FilterField, v reflect.Value) error { for n := range v.Len() { element := v.Index(n) - fmt.Printf("ELEMENT: %+v\n", element) switch element.Kind() { case reflect.Bool: diff --git a/app/repository/smartfilter/handlers_test.go b/app/repository/smartfilter/handlers_test.go index 4efc07a..dbe02e4 100644 --- a/app/repository/smartfilter/handlers_test.go +++ b/app/repository/smartfilter/handlers_test.go @@ -38,7 +38,7 @@ func (m MyModel) TableName() string { return "my_models" } -type TestCase struct { +type HandleOperatorTestCase struct { name string filterField FilterField expected string @@ -63,7 +63,7 @@ func TestHandleOperatorEQ(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorEQ - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorEQ bool true", filterField: FilterField{ @@ -136,7 +136,7 @@ func TestHandleOperatorNE(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorNE - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorNE bool true", filterField: FilterField{ @@ -209,7 +209,7 @@ func TestHandleOperatorLIKE(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorLIKE - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorLIKE", filterField: FilterField{ @@ -237,7 +237,7 @@ func TestHandleOperatorILIKE(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorILIKE - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorILIKE", filterField: FilterField{ @@ -265,7 +265,7 @@ func TestHandleOperatorGT(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorGT - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorGT int64", filterField: FilterField{ @@ -320,7 +320,7 @@ func TestHandleOperatorGE(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorGE - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorGE int64", filterField: FilterField{ @@ -375,7 +375,7 @@ func TestHandleOperatorLT(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorLT - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorLT int64", filterField: FilterField{ @@ -430,7 +430,7 @@ func TestHandleOperatorLE(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorLE - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorLE int64", filterField: FilterField{ @@ -485,7 +485,7 @@ func TestHandleOperatorIN(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorIN - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorIN bool", filterField: FilterField{ @@ -549,7 +549,7 @@ func TestHandleOperatorNOT_IN(t *testing.T) { db, _ := NewMockDB() testFunc := handleOperatorNOT_IN - testCases := []TestCase{ + testCases := []HandleOperatorTestCase{ { name: "handleOperatorNOT_IN bool", filterField: FilterField{ diff --git a/app/repository/smartfilter/smartfilter.go b/app/repository/smartfilter/smartfilter.go index 89f3597..b77ba4d 100644 --- a/app/repository/smartfilter/smartfilter.go +++ b/app/repository/smartfilter/smartfilter.go @@ -5,14 +5,14 @@ import ( "reflect" "slices" "strings" - "time" "gorm.io/gorm" "gorm.io/gorm/schema" ) const TAG_NAME = "filterfield" -const TAG_VALUE_SEPARATOR = "," +const TAG_PAIRS_SEPARATOR = "," +const TAG_KEYVALUE_SEPARATOR = "=" type handlerFunc func(query *gorm.DB, tableName string, filterField *FilterField) *gorm.DB @@ -29,17 +29,6 @@ var operatorHandlers = map[Operator]handlerFunc{ OperatorNOT_IN: handleOperatorNOT_IN, } -type CertFilter struct { - Alive *bool `filterfield:"alive,EQ"` - SerialNumber *string `filterfield:"serial_number,NE"` - SerialNumberContains *string `filterfield:"serial_number,LIKE"` - IssuerContains *string `filterfield:"issuer,ILIKE"` - Id *string `filterfield:"id,EQ"` - Ids *[]string `filterfield:"id,IN"` - IdsNot *[]string `filterfield:"id,NOT_IN"` - CreatedAt_Lt *time.Time `filterfield:"created_at,LT"` -} - type ReflectedStructField struct { name string value reflect.Value @@ -112,19 +101,39 @@ func ToQuery(model schema.Tabler, filter interface{}, query *gorm.DB) (*gorm.DB, } func newFilterField(tagValue string) (*FilterField, error) { - values := strings.Split(tagValue, TAG_VALUE_SEPARATOR) - if len(values) != 2 { - return nil, fmt.Errorf("incorrect number of tag values: %s", tagValue) + filterField := FilterField{} + + tagValue = strings.TrimSpace(tagValue) + pairs := strings.Split(tagValue, TAG_PAIRS_SEPARATOR) + + for _, pair := range pairs { + kvs := strings.Split(pair, TAG_KEYVALUE_SEPARATOR) + if len(kvs) != 2 { + return nil, fmt.Errorf("invalid tag value: %s", strings.TrimSpace(pair)) + } + key := strings.TrimSpace(kvs[0]) + value := strings.TrimSpace(kvs[1]) + + switch key { + case "field": + filterField.Name = value + case "operator": + operator := Operator(value) + if !slices.Contains(OPERATORS, operator) { + return nil, fmt.Errorf("unknown operator: %s", operator) + } + filterField.Operator = operator + default: + return nil, fmt.Errorf("invalid value key: %s", key) + } } - operator := Operator(values[1]) - if !slices.Contains(OPERATORS, operator) { - return nil, fmt.Errorf("unknown operator: %s", operator) + if len(filterField.Name) == 0 { + return nil, fmt.Errorf("missing field name in tag: %s", tagValue) + } + if len(filterField.Operator) == 0 { + return nil, fmt.Errorf("missing operator in tag: %s", tagValue) } - f := FilterField{ - Name: values[0], - Operator: operator, - } - return &f, nil + return &filterField, nil } diff --git a/app/repository/smartfilter/smartfilter_test.go b/app/repository/smartfilter/smartfilter_test.go index d8eca14..13ab166 100644 --- a/app/repository/smartfilter/smartfilter_test.go +++ b/app/repository/smartfilter/smartfilter_test.go @@ -129,3 +129,77 @@ func TestGetFilterFields(t *testing.T) { assert.Equal(t, 0, len(result)) }) } + +type TagParseTestCase struct { + name string + tagValue string + expected FilterField +} + +func TestFilterField(t *testing.T) { + testCases := []TagParseTestCase{ + { + name: "Parse without spaces", + tagValue: "field=field_1,operator=EQ", + expected: FilterField{ + Name: "field_1", + Operator: OperatorEQ, + }, + }, + { + name: "Parse spaces between pairs", + tagValue: " field=field_2 , operator=LT ", + expected: FilterField{ + Name: "field_2", + Operator: OperatorLT, + }, + }, + { + name: "Parse spaces between around keys and values", + tagValue: "operator = LIKE , field = field_3", + expected: FilterField{ + Name: "field_3", + Operator: OperatorLIKE, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + filterField, err := newFilterField(testCase.tagValue) + assert.Nil(t, err) + assert.Equal(t, testCase.expected.Name, filterField.Name) + assert.Equal(t, testCase.expected.Operator, filterField.Operator) + }) + } + + t.Run("Fail on invalid tag value", func(t *testing.T) { + filterField, err := newFilterField("field=field_1=fail, operator=EQ") + assert.Nil(t, filterField) + assert.EqualError(t, err, "invalid tag value: field=field_1=fail") + }) + + t.Run("Fail on invalid operator", func(t *testing.T) { + filterField, err := newFilterField("field=field_1, operator=FAIL") + assert.Nil(t, filterField) + assert.EqualError(t, err, "unknown operator: FAIL") + }) + + t.Run("Fail on invalid value key", func(t *testing.T) { + filterField, err := newFilterField("failkey=field_1, operator=FAIL") + assert.Nil(t, filterField) + assert.EqualError(t, err, "invalid value key: failkey") + }) + + t.Run("Fail on missing field name", func(t *testing.T) { + filterField, err := newFilterField("operator=EQ") + assert.Nil(t, filterField) + assert.EqualError(t, err, "missing field name in tag: operator=EQ") + }) + + t.Run("Fail on missing operator", func(t *testing.T) { + filterField, err := newFilterField("field=field_1") + assert.Nil(t, filterField) + assert.EqualError(t, err, "missing operator in tag: field=field_1") + }) +}