flamingo.me/flamingo-commerce/v3@v3.11.0/search/domain/service.go (about) 1 package domain 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math" 8 "net/url" 9 "sort" 10 ) 11 12 const ( 13 // SuggestionTypeProduct represents product suggestions 14 SuggestionTypeProduct = "product" 15 // SuggestionTypeCategory represents category suggestions 16 SuggestionTypeCategory = "category" 17 ) 18 19 type ( 20 // SearchService defines how to access search 21 SearchService interface { 22 // Types() []string 23 Search(ctx context.Context, filter ...Filter) (results map[string]Result, err error) 24 SearchFor(ctx context.Context, typ string, filter ...Filter) (result *Result, err error) 25 } 26 27 // Result defines a search result for one type 28 Result struct { 29 SearchMeta SearchMeta 30 Hits []Document 31 Suggestion []Suggestion 32 Facets FacetCollection 33 Promotions []Promotion 34 Actions []Action 35 } 36 37 // Action might be considered on the frontend to be taken depending on search results 38 Action struct { 39 Type string 40 Content string 41 AdditionalAttributes map[string]interface{} 42 } 43 44 // SearchMeta data 45 SearchMeta struct { 46 Query string 47 OriginalQuery string 48 Page int 49 NumPages int 50 NumResults int 51 SelectedFacets []Facet 52 SortOptions []SortOption 53 } 54 55 // SortOption defines how sorting is possible, and which of them are activated with both an asc and desc option 56 SortOption struct { 57 // Label that you normally want to show in the frontend (e.g. "Price") 58 Label string 59 // Field that you need to use in SearchRequest>SortFilter 60 Field string 61 // SelectedAsc true if sorting by this field is active 62 SelectedAsc bool 63 // SelectedDesc true if sorting by this field is active 64 SelectedDesc bool 65 // Asc - represents the field that is used to trigger ascending search. 66 // Deprecated: use "Field" and "SelectedAsc" instead to set which field should be sortable 67 Asc string 68 // Desc - represents the field that is used to trigger descending search. 69 // Deprecated: use "Field" and "SelectedDesc" instead to set which field should be sortable 70 Desc string 71 } 72 73 // FacetType for type facets 74 FacetType string 75 76 // FacetItem contains information about a facet item 77 FacetItem struct { 78 Label string 79 Value string 80 Active bool 81 Selected bool 82 83 // Tree Facet 84 Items []*FacetItem 85 86 // Range Facet 87 Min, Max float64 88 SelectedMin, SelectedMax float64 89 90 Count int64 91 } 92 93 // Facet provided by the search backend 94 Facet struct { 95 Type FacetType 96 Name string 97 Label string 98 Items []*FacetItem 99 Position int 100 } 101 102 // FacetCollection for all available facets 103 FacetCollection map[string]Facet 104 facetSlice []Facet 105 106 // Suggestion hint 107 Suggestion struct { 108 Type string 109 Text string 110 Highlight string 111 AdditionalAttributes map[string]string 112 } 113 114 // Promotion result during search 115 Promotion struct { 116 Title string 117 Content string 118 URL string 119 Media []Media 120 AdditionalAttributes map[string]interface{} 121 } 122 123 // Media contains promotion media data 124 Media struct { 125 Type string 126 MimeType string 127 Usage string 128 Title string 129 Reference string 130 } 131 132 // Document holds a search result document 133 Document interface{} 134 135 // RedirectError suggests to redirect 136 RedirectError struct { 137 To string 138 } 139 140 // RequestQueryHook can be used to enforce redirect errors 141 RequestQueryHook interface { 142 Hook(ctx context.Context, path string, query *url.Values) error 143 } 144 ) 145 146 func (re *RedirectError) Error() string { 147 return "Error: enforced redirect to " + re.To 148 } 149 150 func (fs facetSlice) Len() int { 151 return len(fs) 152 } 153 154 func (fs facetSlice) Less(i, j int) bool { 155 return fs[i].Position < fs[j].Position 156 } 157 158 func (fs facetSlice) Swap(i, j int) { 159 fs[i], fs[j] = fs[j], fs[i] 160 } 161 162 // Order a facet collection 163 func (fc FacetCollection) Order() []string { 164 order := make(facetSlice, len(fc)) 165 i := 0 166 for _, k := range fc { 167 order[i] = k 168 i++ 169 } 170 171 sort.Stable(order) 172 173 strings := make([]string, len(order)) 174 for i, v := range order { 175 strings[i] = v.Name 176 } 177 178 return strings 179 } 180 181 // Facet types 182 const ( 183 ListFacet FacetType = "ListFacet" 184 TreeFacet FacetType = "TreeFacet" 185 RangeFacet FacetType = "RangeFacet" 186 ) 187 188 var ( 189 // ErrNotFound error 190 ErrNotFound = errors.New("search not found") 191 ) 192 193 // ValidatePageSize checks if the pageSize is logical for current result 194 func (sm *SearchMeta) ValidatePageSize(pageSize int) error { 195 if pageSize == 0 { 196 return errors.New("cannot validate - no expected pageSize given") 197 } 198 expectedNumPages := math.Ceil(float64(sm.NumResults) / float64(pageSize)) 199 if expectedNumPages != float64(sm.NumPages) { 200 return fmt.Errorf("pagesize not valid expected %f / given in result: %d", expectedNumPages, sm.NumPages) 201 } 202 return nil 203 }