{"id":"cc3e15685467facb","repo":"go-redis/redis","slug":"redis-invalid-timeseries-aggregator-at-index-d-cc3e15","errorCode":null,"errorMessage":"redis: invalid timeseries aggregator at index %d[%d]: %d","messagePattern":"redis: invalid timeseries aggregator at index (.+?)\\[(.+?)\\]: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"timeseries_commands.go","lineNumber":1391,"sourceCode":"// lists one or more aggregators for its key and is emitted as a single comma-joined wire\n// token; specs for different keys are separate wire tokens.\nfunc buildNRangeAggregationArgs(keys []string, aggregators [][]Aggregator) ([]string, error) {\n\tif len(aggregators) != len(keys) {\n\t\treturn nil, fmt.Errorf(\"redis: TS.NRANGE/TS.NREVRANGE requires exactly %d aggregator spec(s), got %d\", len(keys), len(aggregators))\n\t}\n\tparts := make([]string, len(aggregators))\n\tfor i, spec := range aggregators {\n\t\tif len(spec) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"redis: empty timeseries aggregator spec at index %d\", i)\n\t\t}\n\t\tnames := make([]string, len(spec))\n\t\tfor j, agg := range spec {\n\t\t\tif agg == Invalid {\n\t\t\t\treturn nil, fmt.Errorf(\"redis: invalid timeseries aggregator at index %d[%d]: Invalid (%d)\", i, j, agg)\n\t\t\t}\n\t\t\ts := agg.String()\n\t\t\tif s == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"redis: invalid timeseries aggregator at index %d[%d]: %d\", i, j, agg)\n\t\t\t}\n\t\t\tnames[j] = s\n\t\t}\n\t\tparts[i] = strings.Join(names, \",\")\n\t}\n\treturn parts, nil\n}\n\n// appendNRangeOptions appends optional TS.NRANGE / TS.NREVRANGE arguments to args.\nfunc appendNRangeOptions(\n\targs []interface{},\n\tkeys []string,\n\tlatest bool,\n\tfilterByTS []int,\n\tfilterByValue []float64,\n\tcount int,\n\talign interface{},\n\taggregators [][]Aggregator,","sourceCodeStart":1373,"sourceCodeEnd":1409,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/timeseries_commands.go#L1373-L1409","documentation":"Returned by buildNRangeAggregationArgs when an inner aggregator's String() returns empty, i.e. the value is outside the defined iota range (greater than CountAll). Reports the outer index, inner index, and raw int value to locate the bad cast.","triggerScenarios":"Passing Aggregators containing an Aggregator produced by an unchecked cast such as redis.Aggregator(20), or deserialised from an out-of-range index.","commonSituations":"Mapping an external/config aggregator id into the Aggregator type without bounds-checking, or a lookup table with a wrong ordinal.","solutions":["Validate each aggregator is within [redis.Avg, redis.CountAll] before building specs.","Use named constants instead of casting raw ints.","Reject unknown aggregator ids when parsing external input."],"exampleFix":"// before\nopts := &redis.TSNRangeOptions{\n    Aggregators: [][]redis.Aggregator{{redis.Aggregator(20)}},\n}\n\n// after\nopts := &redis.TSNRangeOptions{\n    Aggregators: [][]redis.Aggregator{{redis.Avg}},\n}","handlingStrategy":"validation","validationCode":"func validateNRangeSpecs(aggs [][]redis.Aggregator) error {\n    for i, spec := range aggs {\n        for j, a := range spec {\n            if a < redis.Avg || a > redis.CountAll {\n                return fmt.Errorf(\"aggregator [%d][%d] out of range: %d\", i, j, a)\n            }\n        }\n    }\n    return nil\n}","typeGuard":"func isValidAggregator(a redis.Aggregator) bool { return a >= redis.Avg && a <= redis.CountAll }","tryCatchPattern":null,"preventionTips":["Bounds-check cast aggregators against the valid iota range before use.","Never cast untrusted ints to redis.Aggregator without validation.","Use named constants instead of raw casts."],"tags":["timeseries","nrange","aggregator","validation","enum-out-of-range"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}