{"id":"5518d334f096b93e","repo":"go-redis/redis","slug":"redis-ft-aggregate-collect-empty-field-name-in-s","errorCode":null,"errorMessage":"redis: FT.AGGREGATE COLLECT: empty field name in SortBy","messagePattern":"redis: FT\\.AGGREGATE COLLECT: empty field name in SortBy","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_collect.go","lineNumber":113,"sourceCode":"\t\t\t}\n\t\t\targs = append(args, ensureAtPrefix(f))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empty Fields list\")\n\t}\n\n\t// DISTINCT (optional, forward-compatible).\n\tif o.Distinct {\n\t\targs = append(args, \"DISTINCT\")\n\t}\n\n\t// SORTBY (optional). sort_narg counts each field plus its optional\n\t// direction token.\n\tif len(o.SortBy) > 0 {\n\t\tsortTokens := make([]interface{}, 0, len(o.SortBy)*2)\n\t\tfor _, s := range o.SortBy {\n\t\t\tif strings.TrimLeft(s.FieldName, \"@\") == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"redis: FT.AGGREGATE COLLECT: empty field name in SortBy\")\n\t\t\t}\n\t\t\tif s.Asc && s.Desc {\n\t\t\t\treturn nil, fmt.Errorf(\"redis: FT.AGGREGATE COLLECT: ASC and DESC are mutually exclusive\")\n\t\t\t}\n\t\t\tsortTokens = append(sortTokens, ensureAtPrefix(s.FieldName))\n\t\t\tswitch {\n\t\t\tcase s.Desc:\n\t\t\t\tsortTokens = append(sortTokens, \"DESC\")\n\t\t\tcase s.Asc:\n\t\t\t\tsortTokens = append(sortTokens, \"ASC\")\n\t\t\t\t// neither set: ASC is the server default; emit nothing.\n\t\t\t}\n\t\t}\n\t\targs = append(args, \"SORTBY\", len(sortTokens))\n\t\targs = append(args, sortTokens...)\n\t}\n\n\t// LIMIT (optional).","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_collect.go#L95-L131","documentation":"Returned by NewCollectReducer (and AggregateBuilder.Collect) when a SortBy entry in FTAggregateCollect has a FieldName that is empty or consists only of '@' characters. The COLLECT reducer requires every SORTBY field to resolve to a concrete @<name> token, so the builder rejects names that normalize to the empty string before any bytes go to Redis. This is purely local API-misuse validation; the server is never contacted.","triggerScenarios":"Calling NewCollectReducer(FTAggregateCollect{SortBy: []FTAggregateSortBy{{FieldName: \"\"}}}) or with FieldName set to \"@\", \"@@@\", etc. Also reached via AggregateBuilder.Collect with the same misconfiguration. Triggered at argument-build time, before the FT.AGGREGATE command is serialized.","commonSituations":"Field name coming from an unmarshalled config/struct that left the value zero. Dynamically building SortBy from user input where a field was dropped. Copy-pasting a SortBy entry and forgetting to set FieldName. Mistakenly passing an alias instead of the source field name as an empty string.","solutions":["Ensure every FTAggregateSortBy entry in FTAggregateCollect.SortBy has a non-empty FieldName (e.g. \"price\", \"@timestamp\"); the builder tolerates any number of leading '@' but not an empty result.","If the field name is derived from input, validate it is non-empty and not only '@' before adding it to SortBy.","Grep for `.FieldName = \"\"` or unset SortBy structs in the call site constructing the COLLECT reducer."],"exampleFix":"// before\nNewCollectReducer(FTAggregateCollect{\n    Fields: []string{\"sku\"},\n    SortBy: []FTAggregateSortBy{{FieldName: \"\", Asc: true}},\n})\n// after\nNewCollectReducer(FTAggregateCollect{\n    Fields: []string{\"sku\"},\n    SortBy: []FTAggregateSortBy{{FieldName: \"price\", Asc: true}},\n})","handlingStrategy":"validation","validationCode":"func validCollectSortBy(sb []FTAggregateSortBy) error {\n\tfor i, s := range sb {\n\t\tif strings.TrimLeft(s.FieldName, \"@\") == \"\" {\n\t\t\treturn fmt.Errorf(\"SortBy[%d]: empty field name\", i)\n\t\t}\n\t}\n\treturn nil\n}\n\n// call before NewCollectReducer:\nif err := validCollectSortBy(c.SortBy); err != nil { return err }","typeGuard":"func hasNonEmptyFieldName(s FTAggregateSortBy) bool {\n\treturn strings.TrimLeft(s.FieldName, \"@\") != \"\"\n}","tryCatchPattern":null,"preventionTips":["Validate every SortBy.FieldName is non-empty (after stripping leading '@') before passing the COLLECT config to NewCollectReducer.","Treat field names from external input as untrusted: reject empty or '@'-only strings at the boundary.","Unit-test the COLLECT builder with the exact SortBy slice your app produces."],"tags":["ft-aggregate","collect","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}