From a134de152312beaacdde033b2c5a77095190c52c Mon Sep 17 00:00:00 2001 From: DawnKosmos Date: Sat, 7 Sep 2024 18:14:31 +0200 Subject: [PATCH] added fields and typesense api type enum --- .DS_Store | Bin 0 -> 6148 bytes typesense/api/types.go | 21 ++++++ typesense/fields.go | 137 +++++++++++++++++++++++++++++++++++++++ typesense/fields_test.go | 80 +++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 .DS_Store create mode 100644 typesense/fields.go create mode 100644 typesense/fields_test.go diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..59ed4b2c519e4a523c6ae5680a0de2f8ffe1aa0e GIT binary patch literal 6148 zcmeHKF>V4u473vpqBN8#_Y3*K3XvD&15tnu1tg%LyDIO>(=ubb2s+Y1gT|6QyI#+p zZi@5S%zS=ay_(I;YzimZH^bO?P9NDrWgG~{9eZ2b7vpL_s=k~c_sI@+vK_d8_{+`b zcDv!RObSQ=DIf);fE2i*0@Yln&sV%tS4jaWa32cr`_SOTUN|Jirvo8I0N@1aFsx&i z05&Foy>LiG2IffxCe>@i@T4Q(s;(CfiAguF;lu06UK5JP({X=`a`T?3C-z0V!}-3ixF4zL?{cs<)0_j=i?QKj5tSfzz-K3PQAFV6 e.g. join:user.id -> This will automatically create a reference to the user schema +*/ +func ToFields(Struct any) ([]api.Field, error) { + val := reflect.ValueOf(Struct) + if val.Kind() == reflect.Ptr || val.Kind() != reflect.Struct { + return nil, errors.New("input should be a struct") + } + return lexField(val.Type()) +} + +func lexField(typ reflect.Type) ([]api.Field, error) { + var collectionFields []api.Field + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if field.PkgPath != "" { + continue // Skip unexported fields + } + fieldType := typeAllowed(field.Type) + if fieldType == api.OBJECT { + // Check if Object is embedded. + if field.Anonymous { + // Get each Individual Field to be Parsed + composited, err := lexField(field.Type) + if err != nil { + return nil, err + } + collectionFields = append(collectionFields, composited...) + continue + } + } + tags := field.Tag.Get("json") // tags save the field_name and Options like facet, index, join, optional etc. + apiField, err := parseField(fieldType, tags) + if err != nil { + return nil, err + } + collectionFields = append(collectionFields, apiField) + } + return collectionFields, nil +} + +func parseField(T api.Type, tag string) (api.Field, error) { + params := strings.Split(tag, ",") + var field api.Field + var True bool = true + + // We need the json Field.Tag + if len(params) == 0 { + return api.Field{}, errors.New("field name has to be provided for matching") + } + + field.Name = params[0] + field.Type = string(T) + + for _, key := range params[1:] { + switch key { + case "optional": // optional fields, can be null + field.Optional = &True + case "facet": // If a field is facet its also automatically indexed, correct? + field.Facet = &True + field.Index = &True + case "index": + field.Index = &True + case "sort": + field.Sort = &True + case "infix": + field.Infix = &True + default: + if ref, ok := strings.CutPrefix(key, "join:"); ok { + field.Reference = Pointer(ref) + continue + } + } + } + + return field, nil +} + +func typeAllowed(t reflect.Type) api.Type { + switch t.Kind() { + case reflect.String: + return api.STRING + case reflect.Int32, reflect.Int: + return api.INT32 + case reflect.Int64: + return api.INT64 + case reflect.Float32, reflect.Float64: + return api.FLOAT + case reflect.Bool: + return api.BOOL + case reflect.Slice: + elemType := typeAllowed(t.Elem()) + if elemType != "" { + return elemType + "[]" + } + case reflect.Struct: + return api.OBJECT + case reflect.Pointer: + return typeAllowed(t.Elem()) + default: + panic("type not allowed") + } + fmt.Println(t.Kind()) + return "" +} + +// Pointer returns the Pointer of a Type v +func Pointer[T any](v T) *T { + return &v +} diff --git a/typesense/fields_test.go b/typesense/fields_test.go new file mode 100644 index 0000000..6e98076 --- /dev/null +++ b/typesense/fields_test.go @@ -0,0 +1,80 @@ +package typesense + +import ( + "github.com/stretchr/testify/assert" + "github.com/typesense/typesense-go/v2/typesense/api" + "testing" +) + +type GenerationTest struct { + ID string `json:"id,index"` + Name string `json:"name,index,sort"` + UserId string `json:"user_id,index,join:user.id"` // creates a reference to the collection use + Birthdate int64 `json:"birthdate"` + LastTreatment int64 `json:"last_treatment,index,optional"` + LocationId string `json:"location_id,facet"` +} + +type User struct { + ID string `json:"id,index"` + Name string `json:"name,index"` + Type int32 `json:"type,facet"` +} + +func TestToFields_GenerationTest(t *testing.T) { + testStruct := GenerationTest{} + expectedFields := []api.Field{ + { + Name: "id", + Type: "string", + Index: Pointer(true), + }, + { + Name: "name", + Type: "string", + Index: Pointer(true), + }, + { + Name: "user_id", + Type: "string", + Index: Pointer(true), + Reference: Pointer("user.id"), + }, + { + Name: "birthdate", + Type: "int64", + }, + { + Name: "last_treatment", + Type: "int64", + Index: Pointer(true), + Optional: Pointer(true), + }, + { + Name: "location_id", + Type: "string", + Facet: Pointer(true), + }, + } + + fields, err := ToFields(testStruct) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + compareFields(t, expectedFields, fields) +} + +// Helper function to compare slices of api.Field +func compareFields(t *testing.T, expected, actual []api.Field) { + assert.Equal(t, len(expected), len(actual)) + + for i, exp := range expected { + act := actual[i] + assert.Equal(t, exp.Type, act.Type) + assert.Equal(t, exp.Name, act.Name) + if exp.Index != nil && act.Index != nil { + assert.Equal(t, *exp.Index, *act.Index) + } + } +}