From 2a142f36286edfc9d789274c7c8ff1f903163a67 Mon Sep 17 00:00:00 2001 From: Ankhit Bala Venkata <57641537+abalaven@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:13:33 -0700 Subject: [PATCH] Feature/search-by (#331) * Feature/search-by * Update Searching * FTS Fixes * Update READMEs * Go v1.19 changes * Added Examples to README * Spelling fix Co-authored-by: Caleb Horst Co-authored-by: Caleb Horst --- gateway/field_presence.go | 2 +- gateway/fields.go | 8 +-- gateway/gateway.go | 9 ++++ gateway/middleware.go | 9 ++++ gateway/operator.go | 1 + gorm/README.md | 28 ++++++++++ gorm/collection_operators.go | 45 ++++++++++++++++ gorm/converter.go | 11 ++++ gorm/filtering.go | 2 +- gorm/searching.go | 58 +++++++++++++++++++++ gorm/utilities.go | 25 ++++++++- query/README.md | 37 ++++++++++++++ query/collection_operators.pb.go | 88 ++++++++++++++++++++++++++++---- query/collection_operators.proto | 13 +++++ query/fields.go | 20 ++++---- query/filtering_lexer.go | 4 +- query/searching.go | 15 ++++++ requestid/interceptor.go | 1 - rpc/resource/jsonpb.go | 8 ++- rpc/resource/string.go | 5 +- server/server.go | 2 +- tracing/annotator.go | 4 +- tracing/exporter.go | 4 +- tracing/grpc.go | 26 +++++----- tracing/http.go | 26 +++++----- tracing/span.go | 16 +++--- 26 files changed, 393 insertions(+), 74 deletions(-) create mode 100644 gorm/searching.go create mode 100644 query/searching.go diff --git a/gateway/field_presence.go b/gateway/field_presence.go index 967131e9..e765ddc0 100644 --- a/gateway/field_presence.go +++ b/gateway/field_presence.go @@ -152,7 +152,7 @@ type presenceInterceptorOptionsDecorator struct { type presenceInterceptorOption func(*presenceInterceptorOptionsDecorator) -//WithOverrideFieldMask represent an option to override field mask generated by grpc-gateway +// WithOverrideFieldMask represent an option to override field mask generated by grpc-gateway func WithOverrideFieldMask(d *presenceInterceptorOptionsDecorator) { d.overrideFieldMask = true } diff --git a/gateway/fields.go b/gateway/fields.go index 9fa73fa8..e112a947 100644 --- a/gateway/fields.go +++ b/gateway/fields.go @@ -7,10 +7,10 @@ import ( "github.com/infobloxopen/atlas-app-toolkit/query" ) -//retainFields function extracts the configuration for fields that -//need to be ratained either from gRPC response or from original testRequest -//(in case when gRPC side didn't set any preferences) and retains only -//this fields on outgoing response (dynmap). +// retainFields function extracts the configuration for fields that +// need to be ratained either from gRPC response or from original testRequest +// (in case when gRPC side didn't set any preferences) and retains only +// this fields on outgoing response (dynmap). func retainFields(ctx context.Context, req *http.Request, dynmap map[string]interface{}) { fieldsStr := "" if req != nil { diff --git a/gateway/gateway.go b/gateway/gateway.go index 5d7a1c43..b2ed2b5d 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -74,6 +74,15 @@ func ClientUnaryInterceptor(parentCtx context.Context, method string, req, reply } } + // extracts "_fts" parameters from request + if v := vals.Get(searchQueryKey); v != "" { + s := query.ParseSearching(v) + err = SetCollectionOps(req, s) + if err != nil { + return err + } + } + // extracts "_limit", "_offset", "_page_token" parameters from request var p *query.Pagination l := vals.Get(limitQueryKey) diff --git a/gateway/middleware.go b/gateway/middleware.go index cc7e78c5..98e73caa 100644 --- a/gateway/middleware.go +++ b/gateway/middleware.go @@ -171,3 +171,12 @@ func GetFieldSelection(req proto.Message) (fieldName string, fs *query.FieldSele } return } + +func GetSearching(req proto.Message) (fieldName string, s *query.Searching, err error) { + s = new(query.Searching) + fieldName, err = getAndUnsetOp(req, s, false) + if fieldName == "" { + s = nil + } + return +} diff --git a/gateway/operator.go b/gateway/operator.go index 3c58f72c..19c58867 100644 --- a/gateway/operator.go +++ b/gateway/operator.go @@ -17,6 +17,7 @@ const ( limitQueryKey = "_limit" offsetQueryKey = "_offset" pageTokenQueryKey = "_page_token" + searchQueryKey = "_fts" pageInfoSizeMetaKey = "status-page-info-size" pageInfoOffsetMetaKey = "status-page-info-offset" pageInfoPageTokenMetaKey = "status-page-info-page_token" diff --git a/gorm/README.md b/gorm/README.md index c78c1aca..593b6a4c 100644 --- a/gorm/README.md +++ b/gorm/README.md @@ -65,6 +65,22 @@ var people []Person db.Find(&people) ... ``` +### Applying query.Searching + +```golang +... +db, assoc, err = gorm.ApplySearchingEx(ctx, db, searching, &PersonORM{}, fieldsForFTS, &Person{}) +if err != nil { + ... +} +db, err = gorm.JoinAssociations(ctx, db, assoc, &PersonORM{}) +if err != nil { + ... +} +var people []Person +db.Find(&people) +... +``` ### Applying everything @@ -78,6 +94,18 @@ var people []Person db.Find(&people) ... ``` +### Applying everything with Searching + +```golang +... +db, err = gorm.ApplyCollectionOperatorsWithSearchingEx(ctx, db, &PersonORM{}, &Person{}, filtering, sorting, pagination, fields, searching, fieldsForFTS) +if err != nil { + ... +} +var people []Person +db.Find(&people) +... +``` ## Transaction Management diff --git a/gorm/collection_operators.go b/gorm/collection_operators.go index 50d5d151..7182d0c4 100644 --- a/gorm/collection_operators.go +++ b/gorm/collection_operators.go @@ -23,11 +23,16 @@ type PaginationConverter interface { PaginationToGorm(ctx context.Context, p *query.Pagination) (offset, limit int32) } +type SearchingConverter interface { + SearchingToGorm(ctx context.Context, s *query.Searching, fieldsForFTS []string, obj interface{}) (string, error) +} + type CollectionOperatorsConverter interface { FilteringConditionConverter SortingCriteriaConverter FieldSelectionConverter PaginationConverter + SearchingConverter } func ApplyCollectionOperatorsEx(ctx context.Context, db *gorm.DB, obj interface{}, c CollectionOperatorsConverter, f *query.Filtering, s *query.Sorting, p *query.Pagination, fs *query.FieldSelection) (*gorm.DB, error) { @@ -62,6 +67,46 @@ func ApplyCollectionOperatorsEx(ctx context.Context, db *gorm.DB, obj interface{ return db, nil } +func ApplyCollectionOperatorsWithSearchingEx(ctx context.Context, db *gorm.DB, obj interface{}, c CollectionOperatorsConverter, f *query.Filtering, s *query.Sorting, p *query.Pagination, fs *query.FieldSelection, sc *query.Searching, fieldsForFTS []string) (*gorm.DB, error) { + db, err := ApplyCollectionOperatorsEx(ctx, db, obj, c, f, s, p, fs) + if err != nil { + return nil, err + } + + db, err = ApplySearchingEx(ctx, db, sc, obj, fieldsForFTS, c) + if err != nil { + return nil, err + } + + return db, nil +} + +// ApplySearchingEx applies searching operator s to gorm instance db. +func ApplySearchingEx(ctx context.Context, db *gorm.DB, s *query.Searching, obj interface{}, fieldsForFTS []string, c SearchingConverter) (*gorm.DB, error) { + str, err := c.SearchingToGorm(ctx, s, fieldsForFTS, obj) + if err != nil { + return nil, err + } + if s != nil && s.Query != "" { + s.Query = strings.TrimSpace(s.Query) + s.Query = strings.ReplaceAll(s.Query, ":", " ") + splChar := []string{"(", ")", "|", "+", "<", "'", "&", "!", "%", ";"} + for _, spl := range splChar { + if strings.Contains(s.Query, spl) { + s.Query = "" + return db.Where(str, s.Query), nil + } + } + s.Query = strings.Join(strings.Fields(s.Query), " ") + if s.Query != "" { + s.Query = strings.ReplaceAll(s.Query, " ", " & ") + s.Query = s.Query + ":*" + } + return db.Where(str, s.Query), nil + } + return db, nil +} + // ApplyFiltering applies filtering operator f to gorm instance db. func ApplyFilteringEx(ctx context.Context, db *gorm.DB, f *query.Filtering, obj interface{}, c FilteringConditionConverter) (*gorm.DB, map[string]struct{}, error) { str, args, assocToJoin, err := FilteringToGormEx(ctx, f, obj, c) diff --git a/gorm/converter.go b/gorm/converter.go index 7579c22b..2bd30e6f 100644 --- a/gorm/converter.go +++ b/gorm/converter.go @@ -30,12 +30,16 @@ type DefaultSortingCriteriaConverter struct{} // DefaultPaginationConverter performs default convertion for Paging collection operator type DefaultPaginationConverter struct{} +// DefaultSearchingConverter performs default convertion for Searching operator +type DefaultSearchingConverter struct{} + // DefaultPbToOrmConverter performs default convertion for all collection operators type DefaultPbToOrmConverter struct { DefaultFilteringConditionConverter DefaultSortingCriteriaConverter DefaultFieldSelectionConverter DefaultPaginationConverter + DefaultSearchingConverter } // NewDefaultPbToOrmConverter creates default converter for all collection operators @@ -45,6 +49,7 @@ func NewDefaultPbToOrmConverter(pb proto.Message) CollectionOperatorsConverter { DefaultSortingCriteriaConverter{}, DefaultFieldSelectionConverter{}, DefaultPaginationConverter{}, + DefaultSearchingConverter{}, } } @@ -360,3 +365,9 @@ func (converter *DefaultPaginationConverter) PaginationToGorm(ctx context.Contex } return 0, 0 } + +func (converter *DefaultSearchingConverter) SearchingToGorm(ctx context.Context, s *query.Searching, fieldsForFTS []string, obj interface{}) (string, error) { + mask := GetFullTextSearchDBMask(obj, fieldsForFTS, " ") + fullTextSearchQuery := FormFullTextSearchQuery(mask) + return fullTextSearchQuery, nil +} diff --git a/gorm/filtering.go b/gorm/filtering.go index 776ddcca..8d4d2060 100644 --- a/gorm/filtering.go +++ b/gorm/filtering.go @@ -57,7 +57,7 @@ func FilterStringToGorm(ctx context.Context, filter string, obj interface{}, pb return FilteringToGormEx(ctx, f, obj, c) } -//Deprecated: Use FilteringToGormEx instead +// Deprecated: Use FilteringToGormEx instead // FilteringToGorm returns GORM Plain SQL representation of the filtering expression. func FilteringToGorm(ctx context.Context, m *query.Filtering, obj interface{}, pb proto.Message) (string, []interface{}, map[string]struct{}, error) { c := &DefaultFilteringConditionConverter{&DefaultFilteringConditionProcessor{pb}} diff --git a/gorm/searching.go b/gorm/searching.go new file mode 100644 index 00000000..5a7ff8f3 --- /dev/null +++ b/gorm/searching.go @@ -0,0 +1,58 @@ +package gorm + +import ( + "reflect" + "time" +) + +// GetFullTextSearchDBMask ... +func GetFullTextSearchDBMask(object interface{}, fields []string, separator string) string { + mask := "" + objectVal := indirectValue(reflect.ValueOf(object)) + if objectVal.Kind() != reflect.Struct { + return mask + } + fieldsSize := len(fields) + for i, fieldName := range fields { + fieldVal := objectVal.FieldByName(camelCase(fieldName)) + if !fieldVal.IsValid() { + continue + } + underlyingVal := indirectValue(fieldVal) + if !underlyingVal.IsValid() { + switch fieldVal.Interface().(type) { + case *time.Time: + underlyingVal = fieldVal + default: + continue + } + } + switch underlyingVal.Interface().(type) { + case int32: + mask += fieldName + case string: + mask += fieldName + mask += " || '" + separator + "' || " + mask += "replace(" + fieldName + ", '@', ' ')" + mask += " || '" + separator + "' || " + mask += "replace(" + fieldName + ", '.', ' ')" + case *time.Time: + mask += "coalesce(to_char(" + fieldName + ", 'MM/DD/YY HH:MI pm'), '')" + case bool: + mask += fieldName + default: + continue + } + if i != fieldsSize-1 { + mask += " || '" + separator + "' || " + } + } + + return mask +} + +// FormFullTextSearchQuery ... +func FormFullTextSearchQuery(mask string) string { + fullTextSearchQuery := "to_tsvector('simple', " + mask + ") @@ to_tsquery('simple', ?)" + return fullTextSearchQuery +} diff --git a/gorm/utilities.go b/gorm/utilities.go index eeb9d229..fd14a2d9 100644 --- a/gorm/utilities.go +++ b/gorm/utilities.go @@ -41,7 +41,7 @@ func HandleFieldPath(ctx context.Context, fieldPath []string, obj interface{}) ( return dbPath, "", nil } -//HandleJSONFiledPath translate field path to JSONB path for postgres jsonb +// HandleJSONFiledPath translate field path to JSONB path for postgres jsonb func HandleJSONFieldPath(ctx context.Context, fieldPath []string, obj interface{}, values ...string) (string, string, error) { operator := "#>>" if isRawJSON(values...) { @@ -82,7 +82,7 @@ func isRawJSON(values ...string) bool { return true } -//TODO: add supprt for embeded objects +// TODO: add supprt for embeded objects func IsJSONCondition(ctx context.Context, fieldPath []string, obj interface{}) bool { fieldName := util.Camel(fieldPath[0]) objType := indirectType(reflect.TypeOf(obj)) @@ -189,6 +189,17 @@ func indirectType(t reflect.Type) reflect.Type { } } +func indirectValue(val reflect.Value) reflect.Value { + for { + switch val.Kind() { + case reflect.Ptr, reflect.Slice, reflect.Array: + val = val.Elem() + default: + return val + } + } +} + func isModel(t reflect.Type) bool { kind := t.Kind() _, isValuer := reflect.Zero(t).Interface().(driver.Valuer) @@ -214,3 +225,13 @@ type EmptyFieldPathError struct { func (e *EmptyFieldPathError) Error() string { return fmt.Sprintf("Empty field path is not allowed") } + +func camelCase(v string) string { + sp := strings.Split(v, "_") + r := make([]string, len(sp)) + for i, v := range sp { + r[i] = strings.ToUpper(v[:1]) + v[1:] + } + + return strings.Join(r, "") +} diff --git a/query/README.md b/query/README.md index 07b12056..f20e0db0 100644 --- a/query/README.md +++ b/query/README.md @@ -7,6 +7,7 @@ These types are: - `infoblox.api.Pagination` - `infoblox.api.PageInfo`(used in response) - `infoblox.api.FieldSelection` +- `infoblox.api.Searching` ## Enabling *collection operators* in your application @@ -22,6 +23,7 @@ message MyListRequest { infoblox.api.Sorting sorting = 2; infoblox.api.Pagination pagination = 3; infoblox.api.FieldSelection fields = 4; + infoblox.api.Searching searching = 5; } ``` @@ -162,3 +164,38 @@ server.WithGateway( ) ) ``` +## Searching + +The syntax of REST representation of `infoblox.api.Searching` is the following. + +| Request Parameter | Description | +| ----------------- |------------------------------------------| +| _fts | A string expression which performs a full-text-search on the DB | + +Full-text-search is an optimized mechanism to retrieve data from the DB efficiently when the user is only aware of some portion of the data they are searching. + +#### Example: +Consider that you have the following objects: +``` +{ + name: "my first object", + city: "Santa Clara", + country: "USA" +}, +{ + name: "my second object", + city: "Tacoma", + country: "USA" +} +``` +If "name" is provided as a field for FTS, the Searching query will look like: +``` +...?_fts=my first object +``` +In this case, only the first object would be returned. + +If "country" is provided as a field for FTS, the Searching query will look like: +``` +...?_fts=USA +``` +In this case, both the objects would be returned. \ No newline at end of file diff --git a/query/collection_operators.pb.go b/query/collection_operators.pb.go index 3ea1f011..3d14f9ab 100644 --- a/query/collection_operators.pb.go +++ b/query/collection_operators.pb.go @@ -1431,6 +1431,54 @@ func (x *PageInfo) GetOffset() int32 { return 0 } +// Searching represents search by. +type Searching struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *Searching) Reset() { + *x = Searching{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Searching) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Searching) ProtoMessage() {} + +func (x *Searching) ProtoReflect() protoreflect.Message { + mi := &file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Searching.ProtoReflect.Descriptor instead. +func (*Searching) Descriptor() ([]byte, []int) { + return file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_rawDescGZIP(), []int{13} +} + +func (x *Searching) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + var File_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto protoreflect.FileDescriptor var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_rawDesc = []byte{ @@ -1655,11 +1703,16 @@ var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_pr 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x42, - 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6e, - 0x66, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x6f, 0x70, 0x65, 0x6e, 0x2f, 0x61, 0x74, 0x6c, 0x61, 0x73, - 0x2d, 0x61, 0x70, 0x70, 0x2d, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x2f, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x3b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, + 0x41, 0x0a, 0x09, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x3a, 0x1e, 0x92, 0x41, 0x1b, 0x0a, 0x19, 0x32, 0x13, 0x61, 0x74, 0x6c, 0x61, 0x73, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x62, 0x79, 0x9a, 0x02, + 0x01, 0x07, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x6f, 0x70, 0x65, 0x6e, 0x2f, 0x61, 0x74, + 0x6c, 0x61, 0x73, 0x2d, 0x61, 0x70, 0x70, 0x2d, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x2f, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x3b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1675,7 +1728,7 @@ func file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_p } var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_goTypes = []interface{}{ (SortCriteria_Order)(0), // 0: infoblox.api.SortCriteria.Order (LogicalOperator_Type)(0), // 1: infoblox.api.LogicalOperator.Type @@ -1696,14 +1749,15 @@ var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_pr (*NumberArrayCondition)(nil), // 16: infoblox.api.NumberArrayCondition (*Pagination)(nil), // 17: infoblox.api.Pagination (*PageInfo)(nil), // 18: infoblox.api.PageInfo - nil, // 19: infoblox.api.FieldSelection.FieldsEntry - nil, // 20: infoblox.api.Field.SubsEntry + (*Searching)(nil), // 19: infoblox.api.Searching + nil, // 20: infoblox.api.FieldSelection.FieldsEntry + nil, // 21: infoblox.api.Field.SubsEntry } var file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_depIdxs = []int32{ 0, // 0: infoblox.api.SortCriteria.order:type_name -> infoblox.api.SortCriteria.Order 6, // 1: infoblox.api.Sorting.criterias:type_name -> infoblox.api.SortCriteria - 19, // 2: infoblox.api.FieldSelection.fields:type_name -> infoblox.api.FieldSelection.FieldsEntry - 20, // 3: infoblox.api.Field.subs:type_name -> infoblox.api.Field.SubsEntry + 20, // 2: infoblox.api.FieldSelection.fields:type_name -> infoblox.api.FieldSelection.FieldsEntry + 21, // 3: infoblox.api.Field.subs:type_name -> infoblox.api.Field.SubsEntry 11, // 4: infoblox.api.Filtering.operator:type_name -> infoblox.api.LogicalOperator 12, // 5: infoblox.api.Filtering.string_condition:type_name -> infoblox.api.StringCondition 13, // 6: infoblox.api.Filtering.number_condition:type_name -> infoblox.api.NumberCondition @@ -1898,6 +1952,18 @@ func file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_p return nil } } + file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Searching); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_msgTypes[4].OneofWrappers = []interface{}{ (*Filtering_Operator)(nil), @@ -1927,7 +1993,7 @@ func file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_p GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_github_com_infobloxopen_atlas_app_toolkit_query_collection_operators_proto_rawDesc, NumEnums: 6, - NumMessages: 15, + NumMessages: 16, NumExtensions: 0, NumServices: 0, }, diff --git a/query/collection_operators.proto b/query/collection_operators.proto index 84832d84..191806b5 100644 --- a/query/collection_operators.proto +++ b/query/collection_operators.proto @@ -221,3 +221,16 @@ message PageInfo { // A null value indicates no more pages. int32 offset = 3; } + + +// Searching represents search by. +message Searching { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + type: STRING; + description: "atlas.api.search_by"; + }; + }; + + string query = 1; +} diff --git a/query/fields.go b/query/fields.go index 872c083f..b1af86d7 100644 --- a/query/fields.go +++ b/query/fields.go @@ -9,8 +9,8 @@ const ( opCommonInnerDelimiter = "." ) -//FieldSelectionMap is a convenience type that represents map[string]*Field -//used in FieldSelection and Field structs +// FieldSelectionMap is a convenience type that represents map[string]*Field +// used in FieldSelection and Field structs type FieldSelectionMap map[string]*Field func innerDelimiter(delimiter ...string) string { @@ -26,9 +26,9 @@ func toParts(input string, delimiter ...string) []string { return strings.Split(input, split) } -//ParseFieldSelection transforms a string with comma-separated fields that comes -//from client to FieldSelection struct. For complex fields dot is used as a delimeter by -//default, but it is also possible to specify a different delimiter. +// ParseFieldSelection transforms a string with comma-separated fields that comes +// from client to FieldSelection struct. For complex fields dot is used as a delimeter by +// default, but it is also possible to specify a different delimiter. func ParseFieldSelection(input string, delimiter ...string) *FieldSelection { if len(input) == 0 { return nil @@ -44,8 +44,8 @@ func ParseFieldSelection(input string, delimiter ...string) *FieldSelection { return result } -//GoString converts FieldSelection to a string representation -//It implements fmt.GoStringer interface and returns dot-notated fields separated by commas +// GoString converts FieldSelection to a string representation +// It implements fmt.GoStringer interface and returns dot-notated fields separated by commas func (f *FieldSelection) GoString() string { return strings.Join(f.AllFieldStrings(), opCommonDelimiter) } @@ -70,7 +70,7 @@ func addChildFieldString(result *[]string, parent string, field *Field) { } } -//Add allows to add new fields to FieldSelection +// Add allows to add new fields to FieldSelection func (f *FieldSelection) Add(field string, delimiter ...string) { if len(field) == 0 { return @@ -97,7 +97,7 @@ func (f *FieldSelection) Add(field string, delimiter ...string) { } } -//Delete allows to remove fields from FieldSelection +// Delete allows to remove fields from FieldSelection func (f *FieldSelection) Delete(field string, delimiter ...string) bool { if len(field) == 0 || f.Fields == nil { return false @@ -122,7 +122,7 @@ func (f *FieldSelection) Delete(field string, delimiter ...string) bool { return true } -//Get allows to get specified field from FieldSelection +// Get allows to get specified field from FieldSelection func (f *FieldSelection) Get(field string, delimiter ...string) *Field { if len(field) == 0 || f.Fields == nil { return nil diff --git a/query/filtering_lexer.go b/query/filtering_lexer.go index 974b0145..c3874ca6 100644 --- a/query/filtering_lexer.go +++ b/query/filtering_lexer.go @@ -221,7 +221,7 @@ func (t InToken) String() string { return "in" } -//NumberArrayToken represent number array e.g. [1,2,5] +// NumberArrayToken represent number array e.g. [1,2,5] type StringArrayToken struct { TokenBase Values []string @@ -231,7 +231,7 @@ func (t StringArrayToken) String() string { return fmt.Sprintf("%v", t.Values) } -//NumberArrayToken represent number array e.g. [1,2,5] +// NumberArrayToken represent number array e.g. [1,2,5] type NumberArrayToken struct { TokenBase Values []float64 diff --git a/query/searching.go b/query/searching.go new file mode 100644 index 00000000..82216c9b --- /dev/null +++ b/query/searching.go @@ -0,0 +1,15 @@ +package query + +// GoString implements fmt.GoStringer interface +// return string representation of searching in next form: +func (s *Searching) GoString() string { + return s.Query +} + +// ParseSearching parses raw string that represent search criteria into a Searching +// data structure. +func ParseSearching(s string) *Searching { + var searching Searching + searching.Query = s + return &searching +} diff --git a/requestid/interceptor.go b/requestid/interceptor.go index 2b8d140c..6218283a 100644 --- a/requestid/interceptor.go +++ b/requestid/interceptor.go @@ -11,7 +11,6 @@ import ( // that should be used as a middleware to generate/include Request-Id in headers and context // for tracing and tracking user's request. // -// // Returned middleware populates Request-Id from gRPC metadata if // they defined in a testRequest message else creates a new one. func UnaryServerInterceptor() grpc.UnaryServerInterceptor { diff --git a/rpc/resource/jsonpb.go b/rpc/resource/jsonpb.go index 32098c31..275ada9e 100644 --- a/rpc/resource/jsonpb.go +++ b/rpc/resource/jsonpb.go @@ -9,7 +9,9 @@ import ( // MarshalJSONPB implements jsonpb.JSONPBMarshaler interface by marshal // Identifier from a JSON string in accordance with Atlas Reference format -// // +// +// // +// // Support "null" value. func (m Identifier) MarshalJSONPB(*jsonpb.Marshaler) ([]byte, error) { v := BuildString(m.GetApplicationName(), m.GetResourceType(), m.GetResourceId()) @@ -28,7 +30,9 @@ var _ json.Marshaler = &Identifier{} // UnmarshalJSONPB implements jsonpb.JSONPBUnmarshaler interface by unmarshal // Identifier to a JSON string in accordance with Atlas Reference format -// // +// +// // +// // Support "null" value. func (m *Identifier) UnmarshalJSONPB(_ *jsonpb.Unmarshaler, data []byte) error { v := strings.Trim(string(data), "\"") diff --git a/rpc/resource/string.go b/rpc/resource/string.go index 444fd91f..c3d0f272 100644 --- a/rpc/resource/string.go +++ b/rpc/resource/string.go @@ -8,7 +8,8 @@ const ( ) // BuildString builds string id according to Atlas Reference format: -// // +// +// // func BuildString(aname, rtype, rid string) string { var l []string @@ -26,7 +27,9 @@ func BuildString(aname, rtype, rid string) string { } // ParseString parses id according to Atlas Reference format: +// // // +// // All leading and trailing Delimiter are removed. // The resource_id is parsed first, then resource type and last application name. // The id "/a/b/c/" will be converted to "a/b/c" and returned as (a, b, c). diff --git a/server/server.go b/server/server.go index e274fe89..f3217a42 100644 --- a/server/server.go +++ b/server/server.go @@ -43,7 +43,7 @@ type Server struct { isAutomaticStop bool } -//Middleware wrapper +// Middleware wrapper type Middleware func(handler http.Handler) http.Handler // Option is a functional option for creating a Server diff --git a/tracing/annotator.go b/tracing/annotator.go index f86dc18e..8e613883 100644 --- a/tracing/annotator.go +++ b/tracing/annotator.go @@ -16,8 +16,8 @@ const ( var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{} -//SpanContextAnnotator retrieve information about current span from context or HTTP headers -//and propagate in binary format to gRPC service +// SpanContextAnnotator retrieve information about current span from context or HTTP headers +// and propagate in binary format to gRPC service func SpanContextAnnotator(ctx context.Context, req *http.Request) metadata.MD { md := make(metadata.MD) diff --git a/tracing/exporter.go b/tracing/exporter.go index bbc548cd..b979174f 100644 --- a/tracing/exporter.go +++ b/tracing/exporter.go @@ -7,7 +7,7 @@ import ( "go.opencensus.io/trace" ) -//Exporter creates a new OC Agent exporter and configure for tracing +// Exporter creates a new OC Agent exporter and configure for tracing func Exporter(address string, serviceName string, sampler trace.Sampler) error { // TRACE: Setup OC agent for tracing exporter, err := ocagent.NewExporter( @@ -25,7 +25,7 @@ func Exporter(address string, serviceName string, sampler trace.Sampler) error { return nil } -//SamplerForFraction init sampler for specified fraction +// SamplerForFraction init sampler for specified fraction func SamplerForFraction(fraction float64) trace.Sampler { return trace.ProbabilitySampler(fraction) } diff --git a/tracing/grpc.go b/tracing/grpc.go index 5db4e8a2..023620bb 100644 --- a/tracing/grpc.go +++ b/tracing/grpc.go @@ -19,7 +19,7 @@ var sensitiveMetadata = map[string]struct{}{ "authorization": struct{}{}, } -//GRPCOption allows extending handler with additional functionality +// GRPCOption allows extending handler with additional functionality type GRPCOption func(*gRPCOptions) type metadataMatcher func(string) (string, bool) @@ -42,48 +42,48 @@ func defaultGRPCOptions() *gRPCOptions { } } -//WithMetadataAnnotation annotate span with request metadata +// WithMetadataAnnotation annotate span with request metadata func WithMetadataAnnotation(f func(context.Context, stats.RPCStats) bool) GRPCOption { return func(ops *gRPCOptions) { ops.spanWithMetadata = f } } -//WithMetadataMatcher set metadata matcher to filterout or preprocess metadata +// WithMetadataMatcher set metadata matcher to filterout or preprocess metadata func WithMetadataMatcher(f func(string) (string, bool)) GRPCOption { return func(ops *gRPCOptions) { ops.metadataMatcher = f } } -//WithGRPCPayloadAnnotation add Inbound/Outbound payload as an attribute to span if f returns true +// WithGRPCPayloadAnnotation add Inbound/Outbound payload as an attribute to span if f returns true func WithGRPCPayloadAnnotation(f func(context.Context, stats.RPCStats) bool) GRPCOption { return func(ops *gRPCOptions) { ops.spanWithPayload = f } } -//WithGRPCPayloadLimit limit payload size propogated to span -//in case payload exceeds limit, payload truncated and -//annotation payload.truncated=true added into span +// WithGRPCPayloadLimit limit payload size propogated to span +// in case payload exceeds limit, payload truncated and +// annotation payload.truncated=true added into span func WithGRPCPayloadLimit(limit int) GRPCOption { return func(ops *gRPCOptions) { ops.maxPayloadSize = limit } } -//Check that &ServerHandler{} comply stats.Handler interface +// Check that &ServerHandler{} comply stats.Handler interface var _ stats.Handler = &ServerHandler{} -//ServerHandler is a wrapper over ocgrpc.ServerHandler -//wrapper extends metadata added into the span +// ServerHandler is a wrapper over ocgrpc.ServerHandler +// wrapper extends metadata added into the span type ServerHandler struct { ocgrpc.ServerHandler options *gRPCOptions } -//NewServerHandler returns wrapper over ocgrpc.ServerHandler +// NewServerHandler returns wrapper over ocgrpc.ServerHandler func NewServerHandler(ops ...GRPCOption) *ServerHandler { options := defaultGRPCOptions() for _, op := range ops { @@ -197,12 +197,12 @@ func payloadToAttributes(key string, value interface{}, limit int) ([]trace.Attr return attrs, truncated, nil } -//defaultHeaderMatcher is a header matcher which just accept all headers +// defaultHeaderMatcher is a header matcher which just accept all headers func defaultMetadataMatcher(h string) (string, bool) { return h, true } -//AlwaysGRPC for each call returns true +// AlwaysGRPC for each call returns true func AlwaysGRPC(_ context.Context, _ stats.RPCStats) bool { return true } diff --git a/tracing/http.go b/tracing/http.go index aad117d9..0a9db2ce 100644 --- a/tracing/http.go +++ b/tracing/http.go @@ -62,7 +62,7 @@ type httpOptions struct { maxPayloadSize int } -//HTTPOption allows extending handler with additional functionality +// HTTPOption allows extending handler with additional functionality type HTTPOption func(*httpOptions) func defaultHTTPOptions() *httpOptions { @@ -75,37 +75,37 @@ func defaultHTTPOptions() *httpOptions { } } -//WithHeadersAnnotation annotate span with http headers +// WithHeadersAnnotation annotate span with http headers func WithHeadersAnnotation(f func(*http.Request) bool) HTTPOption { return func(ops *httpOptions) { ops.spanWithHeaders = f } } -//WithHeaderMatcher set header matcher to filterout or preprocess headers +// WithHeaderMatcher set header matcher to filterout or preprocess headers func WithHeaderMatcher(f func(string) (string, bool)) HTTPOption { return func(ops *httpOptions) { ops.headerMatcher = f } } -//WithPayloadAnnotation add request/response body as an attribute to span if f returns true +// WithPayloadAnnotation add request/response body as an attribute to span if f returns true func WithPayloadAnnotation(f func(*http.Request) bool) HTTPOption { return func(ops *httpOptions) { ops.spanWithPayload = f } } -//WithHTTPPayloadSize limit payload size propagated to span -//in case payload exceeds limit, payload truncated and -//annotation payload.truncated=true added into span +// WithHTTPPayloadSize limit payload size propagated to span +// in case payload exceeds limit, payload truncated and +// annotation payload.truncated=true added into span func WithHTTPPayloadSize(maxSize int) HTTPOption { return func(ops *httpOptions) { ops.maxPayloadSize = maxSize } } -//NewMiddleware wrap handler +// NewMiddleware wrap handler func NewMiddleware(ops ...HTTPOption) func(http.Handler) http.Handler { options := defaultHTTPOptions() for _, op := range ops { @@ -124,10 +124,10 @@ func NewMiddleware(ops ...HTTPOption) func(http.Handler) http.Handler { } } -//Check that &Handler comply with http.Handler interface +// Check that &Handler comply with http.Handler interface var _ http.Handler = &Handler{} -//Handler is a opencensus http plugin wrapper which do some useful things to reach traces +// Handler is a opencensus http plugin wrapper which do some useful things to reach traces type Handler struct { child http.Handler @@ -213,7 +213,7 @@ func newResponseWrapper(w http.ResponseWriter) *responseBodyWrapper { } } -//responseBodyWrapper duplicate all bytes written to it to buffer +// responseBodyWrapper duplicate all bytes written to it to buffer type responseBodyWrapper struct { http.ResponseWriter @@ -249,12 +249,12 @@ func obfuscate(x string) string { return x[:countChars] + "..." } -//defaultHeaderMatcher is a header matcher which just accept all headers +// defaultHeaderMatcher is a header matcher which just accept all headers func defaultHeaderMatcher(h string) (string, bool) { return h, true } -//AlwaysHTTP for each request returns true +// AlwaysHTTP for each request returns true func AlwaysHTTP(_ *http.Request) bool { return true } diff --git a/tracing/span.go b/tracing/span.go index 0a5559cc..8e781229 100644 --- a/tracing/span.go +++ b/tracing/span.go @@ -14,7 +14,7 @@ var ( ErrSpanNotFound = errors.New("there is no currently active spans") ) -//CurrentSpan returns current span +// CurrentSpan returns current span func CurrentSpan(ctx context.Context) (*trace.Span, error) { span := trace.FromContext(ctx) if span == nil { @@ -24,17 +24,17 @@ func CurrentSpan(ctx context.Context) (*trace.Span, error) { return span, nil } -//StartSpan starts span with name +// StartSpan starts span with name func StartSpan(ctx context.Context, name string) (context.Context, *trace.Span) { return trace.StartSpan(ctx, name) } -//TagSpan tags span +// TagSpan tags span func TagSpan(span *trace.Span, attrs ...trace.Attribute) { span.AddAttributes(attrs...) } -//TagCurrentSpan get current span from context and tag it +// TagCurrentSpan get current span from context and tag it func TagCurrentSpan(ctx context.Context, attrs ...trace.Attribute) error { span, err := CurrentSpan(ctx) if err != nil { @@ -45,12 +45,12 @@ func TagCurrentSpan(ctx context.Context, attrs ...trace.Attribute) error { return nil } -//AddMessageSpan adds message into span +// AddMessageSpan adds message into span func AddMessageSpan(span *trace.Span, message string, attrs ...trace.Attribute) { span.Annotate(attrs, message) } -//AddMessageCurrentSpan get current span from context and adds message into it +// AddMessageCurrentSpan get current span from context and adds message into it func AddMessageCurrentSpan(ctx context.Context, message string, attrs ...trace.Attribute) error { span, err := CurrentSpan(ctx) if err != nil { @@ -61,7 +61,7 @@ func AddMessageCurrentSpan(ctx context.Context, message string, attrs ...trace.A return nil } -//AddErrorCurrentSpan get current span from context and adds error into it +// AddErrorCurrentSpan get current span from context and adds error into it func AddErrorCurrentSpan(ctx context.Context, err error) error { if err != nil { return nil @@ -76,7 +76,7 @@ func AddErrorCurrentSpan(ctx context.Context, err error) error { return AddErrorSpan(span, err) } -//AddErrorSpan adds error into span +// AddErrorSpan adds error into span func AddErrorSpan(span *trace.Span, err error) error { var code int32 = trace.StatusCodeOK status, ok := status.FromError(err)