diff --git a/pkg/api/http.go b/pkg/api/http.go index 926c921ba9a..d2163d7ca65 100644 --- a/pkg/api/http.go +++ b/pkg/api/http.go @@ -123,7 +123,9 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { SpansPerSpanSet: defaultSpansPerSpanSet, } - if s, ok := extractQueryParam(r, urlParamStart); ok { + vals := r.URL.Query() + + if s, ok := extractQueryParam(vals, urlParamStart); ok { start, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid start: %w", err) @@ -131,7 +133,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { req.Start = uint32(start) } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { end, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid end: %w", err) @@ -139,7 +141,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { req.End = uint32(end) } - query, queryFound := extractQueryParam(r, urlParamQuery) + query, queryFound := extractQueryParam(vals, urlParamQuery) if queryFound { // TODO hacky fix: we don't validate {} since this isn't handled correctly yet if query != "{}" { @@ -151,7 +153,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { req.Query = query } - encodedTags, tagsFound := extractQueryParam(r, urlParamTags) + encodedTags, tagsFound := extractQueryParam(vals, urlParamTags) if tagsFound { // tags and traceQL API are mutually exclusive if queryFound { @@ -186,7 +188,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { // Passing tags as individual query parameters is not supported anymore, clients should use the tags // query parameter instead. We still parse these tags since the initial Grafana implementation uses this. // As Grafana gets updated and/or versions using this get old we can remove this section. - for k, v := range r.URL.Query() { + for k, v := range vals { // Skip reserved keywords if k == urlParamQuery || k == urlParamTags || k == urlParamMinDuration || k == urlParamMaxDuration || k == urlParamLimit || k == urlParamSpansPerSpanSet || k == urlParamStart || k == urlParamEnd { continue @@ -198,7 +200,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { } } - if s, ok := extractQueryParam(r, urlParamMinDuration); ok { + if s, ok := extractQueryParam(vals, urlParamMinDuration); ok { dur, err := time.ParseDuration(s) if err != nil { return nil, fmt.Errorf("invalid minDuration: %w", err) @@ -206,7 +208,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { req.MinDurationMs = uint32(dur.Milliseconds()) } - if s, ok := extractQueryParam(r, urlParamMaxDuration); ok { + if s, ok := extractQueryParam(vals, urlParamMaxDuration); ok { dur, err := time.ParseDuration(s) if err != nil { return nil, fmt.Errorf("invalid maxDuration: %w", err) @@ -218,7 +220,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { } } - if s, ok := extractQueryParam(r, urlParamLimit); ok { + if s, ok := extractQueryParam(vals, urlParamLimit); ok { limit, err := strconv.Atoi(s) if err != nil { return nil, fmt.Errorf("invalid limit: %w", err) @@ -229,7 +231,7 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { req.Limit = uint32(limit) } - if s, ok := extractQueryParam(r, urlParamSpansPerSpanSet); ok { + if s, ok := extractQueryParam(vals, urlParamSpansPerSpanSet); ok { spansPerSpanSet, err := strconv.Atoi(s) if err != nil { return nil, fmt.Errorf("invalid spss: %w", err) @@ -254,14 +256,15 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { func ParseSpanMetricsRequest(r *http.Request) (*tempopb.SpanMetricsRequest, error) { req := &tempopb.SpanMetricsRequest{} + vals := r.URL.Query() - groupBy := r.URL.Query().Get(urlParamGroupBy) + groupBy := vals.Get(urlParamGroupBy) req.GroupBy = groupBy - query := r.URL.Query().Get(urlParamQuery) + query := vals.Get(urlParamQuery) req.Query = query - l := r.URL.Query().Get(urlParamLimit) + l := vals.Get(urlParamLimit) if l != "" { limit, err := strconv.Atoi(l) if err != nil { @@ -270,7 +273,7 @@ func ParseSpanMetricsRequest(r *http.Request) (*tempopb.SpanMetricsRequest, erro req.Limit = uint64(limit) } - if s, ok := extractQueryParam(r, urlParamStart); ok { + if s, ok := extractQueryParam(vals, urlParamStart); ok { start, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid start: %w", err) @@ -278,7 +281,7 @@ func ParseSpanMetricsRequest(r *http.Request) (*tempopb.SpanMetricsRequest, erro req.Start = uint32(start) } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { end, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid end: %w", err) @@ -291,14 +294,15 @@ func ParseSpanMetricsRequest(r *http.Request) (*tempopb.SpanMetricsRequest, erro func ParseSpanMetricsSummaryRequest(r *http.Request) (*tempopb.SpanMetricsSummaryRequest, error) { req := &tempopb.SpanMetricsSummaryRequest{} + vals := r.URL.Query() - groupBy := r.URL.Query().Get(urlParamGroupBy) + groupBy := vals.Get(urlParamGroupBy) req.GroupBy = groupBy - query := r.URL.Query().Get(urlParamQuery) + query := vals.Get(urlParamQuery) req.Query = query - l := r.URL.Query().Get(urlParamLimit) + l := vals.Get(urlParamLimit) if l != "" { limit, err := strconv.Atoi(l) if err != nil { @@ -307,7 +311,7 @@ func ParseSpanMetricsSummaryRequest(r *http.Request) (*tempopb.SpanMetricsSummar req.Limit = uint64(limit) } - if s, ok := extractQueryParam(r, urlParamStart); ok { + if s, ok := extractQueryParam(vals, urlParamStart); ok { start, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid start: %w", err) @@ -315,7 +319,7 @@ func ParseSpanMetricsSummaryRequest(r *http.Request) (*tempopb.SpanMetricsSummar req.Start = uint32(start) } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { end, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid end: %w", err) @@ -328,18 +332,19 @@ func ParseSpanMetricsSummaryRequest(r *http.Request) (*tempopb.SpanMetricsSummar func ParseQueryInstantRequest(r *http.Request) (*tempopb.QueryInstantRequest, error) { req := &tempopb.QueryInstantRequest{} + vals := r.URL.Query() // check "query" first. this was originally added for prom compatibility and Grafana still uses it. - if s, ok := extractQueryParam(r, "query"); ok { + if s, ok := extractQueryParam(vals, "query"); ok { req.Query = s } // also check the `q` parameter. this is what all other Tempo endpoints take for a TraceQL query. - if s, ok := extractQueryParam(r, urlParamQuery); ok { + if s, ok := extractQueryParam(vals, urlParamQuery); ok { req.Query = s } - start, end, _ := bounds(r) + start, end, _ := bounds(vals) req.Start = uint64(start.UnixNano()) req.End = uint64(end.UnixNano()) @@ -348,73 +353,74 @@ func ParseQueryInstantRequest(r *http.Request) (*tempopb.QueryInstantRequest, er func ParseQueryRangeRequest(r *http.Request) (*tempopb.QueryRangeRequest, error) { req := &tempopb.QueryRangeRequest{} + vals := r.URL.Query() // check "query" first. this was originally added for prom compatibility and Grafana still uses it. - if s, ok := extractQueryParam(r, "query"); ok { + if s, ok := extractQueryParam(vals, "query"); ok { req.Query = s } // also check the `q` parameter. this is what all other Tempo endpoints take for a TraceQL query. - if s, ok := extractQueryParam(r, urlParamQuery); ok { + if s, ok := extractQueryParam(vals, urlParamQuery); ok { req.Query = s } - if s, ok := extractQueryParam(r, QueryModeKey); ok { + if s, ok := extractQueryParam(vals, QueryModeKey); ok { req.QueryMode = s } - start, end, _ := bounds(r) + start, end, _ := bounds(vals) req.Start = uint64(start.UnixNano()) req.End = uint64(end.UnixNano()) - step, err := step(r, start, end) + step, err := step(vals, start, end) if err != nil { return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) } req.Step = uint64(step.Nanoseconds()) - shardCount, _ := extractQueryParam(r, urlParamShardCount) + shardCount, _ := extractQueryParam(vals, urlParamShardCount) if shardCount, err := strconv.Atoi(shardCount); err == nil { req.ShardCount = uint32(shardCount) } - shard, _ := extractQueryParam(r, urlParamShard) + shard, _ := extractQueryParam(vals, urlParamShard) if shard, err := strconv.Atoi(shard); err == nil { req.ShardID = uint32(shard) } // New RF1 params - blockID, _ := extractQueryParam(r, urlParamBlockID) + blockID, _ := extractQueryParam(vals, urlParamBlockID) if blockID, err := uuid.Parse(blockID); err == nil { req.BlockID = blockID.String() } - startPage, _ := extractQueryParam(r, urlParamStartPage) + startPage, _ := extractQueryParam(vals, urlParamStartPage) if startPage, err := strconv.Atoi(startPage); err == nil { req.StartPage = uint32(startPage) } - pagesToSearch, _ := extractQueryParam(r, urlParamPagesToSearch) + pagesToSearch, _ := extractQueryParam(vals, urlParamPagesToSearch) if of, err := strconv.Atoi(pagesToSearch); err == nil { req.PagesToSearch = uint32(of) } - version, _ := extractQueryParam(r, urlParamVersion) + version, _ := extractQueryParam(vals, urlParamVersion) req.Version = version - encoding, _ := extractQueryParam(r, urlParamEncoding) + encoding, _ := extractQueryParam(vals, urlParamEncoding) req.Encoding = encoding - size, _ := extractQueryParam(r, urlParamSize) + size, _ := extractQueryParam(vals, urlParamSize) if size, err := strconv.Atoi(size); err == nil { req.Size_ = uint64(size) } - footerSize, _ := extractQueryParam(r, urlParamFooterSize) + footerSize, _ := extractQueryParam(vals, urlParamFooterSize) if footerSize, err := strconv.Atoi(footerSize); err == nil { req.FooterSize = uint32(footerSize) } - dedicatedColumns, _ := extractQueryParam(r, urlParamDedicatedColumns) + dedicatedColumns, _ := extractQueryParam(vals, urlParamDedicatedColumns) if len(dedicatedColumns) > 0 { err := json.Unmarshal([]byte(dedicatedColumns), &req.DedicatedColumns) if err != nil { @@ -436,12 +442,12 @@ func BuildQueryInstantRequest(req *http.Request, searchReq *tempopb.QueryInstant return req } - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(searchReq.Start, 10)) - q.Set(urlParamEnd, strconv.FormatUint(searchReq.End, 10)) - q.Set(urlParamQuery, searchReq.Query) + qb := newQueryBuilder("") + qb.addParam(urlParamStart, strconv.FormatUint(searchReq.Start, 10)) + qb.addParam(urlParamEnd, strconv.FormatUint(searchReq.End, 10)) + qb.addParam(urlParamQuery, searchReq.Query) - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req } @@ -457,41 +463,41 @@ func BuildQueryRangeRequest(req *http.Request, searchReq *tempopb.QueryRangeRequ return req } - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(searchReq.Start, 10)) - q.Set(urlParamEnd, strconv.FormatUint(searchReq.End, 10)) - q.Set(urlParamStep, time.Duration(searchReq.Step).String()) - q.Set(urlParamShard, strconv.FormatUint(uint64(searchReq.ShardID), 10)) - q.Set(urlParamShardCount, strconv.FormatUint(uint64(searchReq.ShardCount), 10)) - q.Set(QueryModeKey, searchReq.QueryMode) + qb := newQueryBuilder("") + qb.addParam(urlParamStart, strconv.FormatUint(searchReq.Start, 10)) + qb.addParam(urlParamEnd, strconv.FormatUint(searchReq.End, 10)) + qb.addParam(urlParamStep, time.Duration(searchReq.Step).String()) + qb.addParam(urlParamShard, strconv.FormatUint(uint64(searchReq.ShardID), 10)) + qb.addParam(urlParamShardCount, strconv.FormatUint(uint64(searchReq.ShardCount), 10)) + qb.addParam(QueryModeKey, searchReq.QueryMode) // New RF1 params - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.Itoa(int(searchReq.StartPage))) - q.Set(urlParamPagesToSearch, strconv.Itoa(int(searchReq.PagesToSearch))) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamSize, strconv.Itoa(int(searchReq.Size_))) - q.Set(urlParamFooterSize, strconv.Itoa(int(searchReq.FooterSize))) + qb.addParam(urlParamBlockID, searchReq.BlockID) + qb.addParam(urlParamStartPage, strconv.Itoa(int(searchReq.StartPage))) + qb.addParam(urlParamPagesToSearch, strconv.Itoa(int(searchReq.PagesToSearch))) + qb.addParam(urlParamVersion, searchReq.Version) + qb.addParam(urlParamEncoding, searchReq.Encoding) + qb.addParam(urlParamSize, strconv.Itoa(int(searchReq.Size_))) + qb.addParam(urlParamFooterSize, strconv.Itoa(int(searchReq.FooterSize))) if len(searchReq.DedicatedColumns) > 0 { columnsJSON, _ := json.Marshal(searchReq.DedicatedColumns) - q.Set(urlParamDedicatedColumns, string(columnsJSON)) + qb.addParam(urlParamDedicatedColumns, string(columnsJSON)) } if len(searchReq.Query) > 0 { - q.Set(urlParamQuery, searchReq.Query) + qb.addParam(urlParamQuery, searchReq.Query) } - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req } -func bounds(r *http.Request) (time.Time, time.Time, error) { +func bounds(vals url.Values) (time.Time, time.Time, error) { var ( now = time.Now() - start, _ = extractQueryParam(r, urlParamStart) - end, _ = extractQueryParam(r, urlParamEnd) - since, _ = extractQueryParam(r, urlParamSince) + start, _ = extractQueryParam(vals, urlParamStart) + end, _ = extractQueryParam(vals, urlParamEnd) + since, _ = extractQueryParam(vals, urlParamSince) ) return determineBounds(now, start, end, since) @@ -555,8 +561,8 @@ func parseTimestamp(value string, def time.Time) (time.Time, error) { return time.Unix(0, nanos), nil } -func step(r *http.Request, start, end time.Time) (time.Duration, error) { - value, _ := extractQueryParam(r, urlParamStep) +func step(vals url.Values, start, end time.Time) (time.Duration, error) { + value, _ := extractQueryParam(vals, urlParamStep) if value == "" { return time.Duration(traceql.DefaultQueryRangeStep(uint64(start.UnixNano()), uint64(end.UnixNano()))), nil } @@ -590,24 +596,24 @@ func BuildSearchRequest(req *http.Request, searchReq *tempopb.SearchRequest) (*h return req, nil } - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) - q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) + qb := newQueryBuilder("") + qb.addParam(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) + qb.addParam(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) if searchReq.Limit != 0 { - q.Set(urlParamLimit, strconv.FormatUint(uint64(searchReq.Limit), 10)) + qb.addParam(urlParamLimit, strconv.FormatUint(uint64(searchReq.Limit), 10)) } if searchReq.MaxDurationMs != 0 { - q.Set(urlParamMaxDuration, strconv.FormatUint(uint64(searchReq.MaxDurationMs), 10)+"ms") + qb.addParam(urlParamMaxDuration, strconv.FormatUint(uint64(searchReq.MaxDurationMs), 10)+"ms") } if searchReq.MinDurationMs != 0 { - q.Set(urlParamMinDuration, strconv.FormatUint(uint64(searchReq.MinDurationMs), 10)+"ms") + qb.addParam(urlParamMinDuration, strconv.FormatUint(uint64(searchReq.MinDurationMs), 10)+"ms") } if searchReq.SpansPerSpanSet != 0 { - q.Set(urlParamSpansPerSpanSet, strconv.FormatUint(uint64(searchReq.SpansPerSpanSet), 10)) + qb.addParam(urlParamSpansPerSpanSet, strconv.FormatUint(uint64(searchReq.SpansPerSpanSet), 10)) } if len(searchReq.Query) > 0 { - q.Set(urlParamQuery, searchReq.Query) + qb.addParam(urlParamQuery, searchReq.Query) } if len(searchReq.Tags) > 0 { @@ -621,10 +627,10 @@ func BuildSearchRequest(req *http.Request, searchReq *tempopb.SearchRequest) (*h } } - q.Set(urlParamTags, builder.String()) + qb.addParam(urlParamTags, builder.String()) } - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req, nil } @@ -643,26 +649,26 @@ func BuildSearchBlockRequest(req *http.Request, searchReq *tempopb.SearchBlockRe return nil, err } - q := req.URL.Query() - q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) - q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) - q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) - q.Set(urlParamDataEncoding, searchReq.DataEncoding) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) + qb := newQueryBuilder(req.URL.RawQuery) + qb.addParam(urlParamBlockID, searchReq.BlockID) + qb.addParam(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) + qb.addParam(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) + qb.addParam(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) + qb.addParam(urlParamEncoding, searchReq.Encoding) + qb.addParam(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) + qb.addParam(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) + qb.addParam(urlParamDataEncoding, searchReq.DataEncoding) + qb.addParam(urlParamVersion, searchReq.Version) + qb.addParam(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) if len(searchReq.DedicatedColumns) > 0 { columnsJSON, err := json.Marshal(searchReq.DedicatedColumns) if err != nil { return nil, err } - q.Set(urlParamDedicatedColumns, string(columnsJSON)) + qb.addParam(urlParamDedicatedColumns, string(columnsJSON)) } - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req, nil } @@ -676,9 +682,9 @@ func AddServerlessParams(req *http.Request, maxBytes int) *http.Request { } } - q := req.URL.Query() - q.Set(urlParamMaxBytes, strconv.FormatInt(int64(maxBytes), 10)) - req.URL.RawQuery = q.Encode() + qb := newQueryBuilder(req.URL.RawQuery) + qb.addParam(urlParamMaxBytes, strconv.FormatInt(int64(maxBytes), 10)) + req.URL.RawQuery = qb.query() return req } @@ -686,7 +692,7 @@ func AddServerlessParams(req *http.Request, maxBytes int) *http.Request { // ExtractServerlessParams extracts params for the serverless functions from // an http.Request func ExtractServerlessParams(req *http.Request) (int, error) { - s, exists := extractQueryParam(req, urlParamMaxBytes) + s, exists := extractQueryParam(req.URL.Query(), urlParamMaxBytes) if !exists { return 0, nil } @@ -698,15 +704,17 @@ func ExtractServerlessParams(req *http.Request) (int, error) { return int(maxBytes), nil } -func extractQueryParam(r *http.Request, param string) (string, bool) { - value := r.URL.Query().Get(param) +func extractQueryParam(v url.Values, param string) (string, bool) { + value := v.Get(param) return value, value != "" } // ValidateAndSanitizeRequest validates params for trace by id api // return values are (blockStart, blockEnd, queryMode, start, end, error) func ValidateAndSanitizeRequest(r *http.Request) (string, string, string, int64, int64, error) { - q, _ := extractQueryParam(r, QueryModeKey) + vals := r.URL.Query() + + q, _ := extractQueryParam(vals, QueryModeKey) // validate queryMode. it should either be empty or one of (QueryModeIngesters|QueryModeBlocks|QueryModeAll) var queryMode string @@ -729,7 +737,7 @@ func ValidateAndSanitizeRequest(r *http.Request) (string, string, string, int64, return "", "", queryMode, 0, 0, nil } - if start, ok := extractQueryParam(r, BlockStartKey); ok { + if start, ok := extractQueryParam(vals, BlockStartKey); ok { _, err := uuid.Parse(start) if err != nil { return "", "", "", 0, 0, fmt.Errorf("invalid value for blockstart: %w", err) @@ -739,7 +747,7 @@ func ValidateAndSanitizeRequest(r *http.Request) (string, string, string, int64, blockStart = tempodb.BlockIDMin } - if end, ok := extractQueryParam(r, BlockEndKey); ok { + if end, ok := extractQueryParam(vals, BlockEndKey); ok { _, err := uuid.Parse(end) if err != nil { return "", "", "", 0, 0, fmt.Errorf("invalid value for blockEnd: %w", err) @@ -749,7 +757,7 @@ func ValidateAndSanitizeRequest(r *http.Request) (string, string, string, int64, blockEnd = tempodb.BlockIDMax } - if s, ok := extractQueryParam(r, urlParamStart); ok { + if s, ok := extractQueryParam(vals, urlParamStart); ok { var err error startTime, err = strconv.ParseInt(s, 10, 64) if err != nil { @@ -759,7 +767,7 @@ func ValidateAndSanitizeRequest(r *http.Request) (string, string, string, int64, startTime = 0 } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { var err error endTime, err = strconv.ParseInt(s, 10, 64) if err != nil { diff --git a/pkg/api/http_test.go b/pkg/api/http_test.go index 2365ca658d3..c3387192b01 100644 --- a/pkg/api/http_test.go +++ b/pkg/api/http_test.go @@ -418,7 +418,7 @@ func TestBuildSearchBlockRequest(t *testing.T) { Size_: 1000, FooterSize: 2000, }, - query: "?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&dataEncoding=v1&encoding=s2&footerSize=2000&indexPageSize=10&pagesToSearch=10&size=1000&startPage=0&totalRecords=11&version=v2", + query: "?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&pagesToSearch=10&size=1000&startPage=0&encoding=s2&indexPageSize=10&totalRecords=11&dataEncoding=v1&version=v2&footerSize=2000", }, { req: &tempopb.SearchBlockRequest{ @@ -434,7 +434,7 @@ func TestBuildSearchBlockRequest(t *testing.T) { FooterSize: 2000, }, httpReq: httptest.NewRequest("GET", "/test/path", nil), - query: "/test/path?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&dataEncoding=v1&encoding=s2&footerSize=2000&indexPageSize=10&pagesToSearch=10&size=1000&startPage=0&totalRecords=11&version=v2", + query: "/test/path?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&pagesToSearch=10&size=1000&startPage=0&encoding=s2&indexPageSize=10&totalRecords=11&dataEncoding=v1&version=v2&footerSize=2000", }, { req: &tempopb.SearchBlockRequest{ @@ -459,7 +459,7 @@ func TestBuildSearchBlockRequest(t *testing.T) { Size_: 1000, FooterSize: 2000, }, - query: "?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&dataEncoding=v1&encoding=s2&end=20&footerSize=2000&indexPageSize=10&limit=50&maxDuration=40ms&minDuration=30ms&pagesToSearch=10&size=1000&start=10&startPage=0&tags=foo%3Dbar&totalRecords=11&version=v2", + query: "?start=10&end=20&limit=50&maxDuration=40ms&minDuration=30ms&tags=foo%3Dbar&blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&pagesToSearch=10&size=1000&startPage=0&encoding=s2&indexPageSize=10&totalRecords=11&dataEncoding=v1&version=v2&footerSize=2000", }, { req: &tempopb.SearchBlockRequest{ @@ -477,7 +477,7 @@ func TestBuildSearchBlockRequest(t *testing.T) { }, }, httpReq: httptest.NewRequest("GET", "/test/path", nil), - query: "/test/path?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&dataEncoding=&dc=%5B%7B%22scope%22%3A1%2C%22name%22%3A%22net.sock.host.addr%22%7D%5D&encoding=none&footerSize=2000&indexPageSize=0&pagesToSearch=10&size=1000&startPage=0&totalRecords=2&version=vParquet3", + query: "/test/path?blockID=b92ec614-3fd7-4299-b6db-f657e7025a9b&pagesToSearch=10&size=1000&startPage=0&encoding=none&indexPageSize=0&totalRecords=2&dataEncoding=&version=vParquet3&footerSize=2000&dc=%5B%7B%22scope%22%3A1%2C%22name%22%3A%22net.sock.host.addr%22%7D%5D", }, } @@ -574,7 +574,7 @@ func TestBuildSearchRequest(t *testing.T) { Limit: 50, SpansPerSpanSet: 60, }, - query: "?end=20&limit=50&maxDuration=40ms&minDuration=30ms&spss=60&start=10&tags=foo%3Dbar", + query: "?start=10&end=20&limit=50&maxDuration=40ms&minDuration=30ms&spss=60&tags=foo%3Dbar", }, { req: &tempopb.SearchRequest{ @@ -586,7 +586,7 @@ func TestBuildSearchRequest(t *testing.T) { MaxDurationMs: 30, Limit: 50, }, - query: "?end=20&limit=50&maxDuration=30ms&start=10&tags=foo%3Dbar", + query: "?start=10&end=20&limit=50&maxDuration=30ms&tags=foo%3Dbar", }, { req: &tempopb.SearchRequest{ @@ -598,7 +598,7 @@ func TestBuildSearchRequest(t *testing.T) { MinDurationMs: 30, Limit: 50, }, - query: "?end=20&limit=50&minDuration=30ms&start=10&tags=foo%3Dbar", + query: "?start=10&end=20&limit=50&minDuration=30ms&tags=foo%3Dbar", }, { req: &tempopb.SearchRequest{ @@ -610,7 +610,7 @@ func TestBuildSearchRequest(t *testing.T) { MinDurationMs: 30, MaxDurationMs: 40, }, - query: "?end=20&maxDuration=40ms&minDuration=30ms&start=10&tags=foo%3Dbar", + query: "?start=10&end=20&maxDuration=40ms&minDuration=30ms&tags=foo%3Dbar", }, { req: &tempopb.SearchRequest{ @@ -620,7 +620,7 @@ func TestBuildSearchRequest(t *testing.T) { MinDurationMs: 30, MaxDurationMs: 40, }, - query: "?end=20&maxDuration=40ms&minDuration=30ms&start=10", + query: "?start=10&end=20&maxDuration=40ms&minDuration=30ms", }, { req: &tempopb.SearchRequest{ @@ -628,7 +628,7 @@ func TestBuildSearchRequest(t *testing.T) { Start: 10, End: 20, }, - query: "?end=20&q=%7B+foo+%3D+%60bar%60+%7D&start=10", + query: "?start=10&end=20&q=%7B+foo+%3D+%60bar%60+%7D", }, } diff --git a/pkg/api/query_builder.go b/pkg/api/query_builder.go new file mode 100644 index 00000000000..4addb11dea0 --- /dev/null +++ b/pkg/api/query_builder.go @@ -0,0 +1,37 @@ +package api + +import ( + "net/url" + "strings" +) + +type queryBuilder struct { + builder strings.Builder +} + +// newQueryBuilder creates a new queryBuilder +func newQueryBuilder(init string) *queryBuilder { + qb := &queryBuilder{ + builder: strings.Builder{}, + } + + qb.builder.WriteString(init) + return qb +} + +// jpe - test me + +// addParam adds a new key/val pair to the query +// like https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/net/url/url.go;l=972 +func (qb *queryBuilder) addParam(key, value string) { + if qb.builder.Len() > 0 { + qb.builder.WriteByte('&') + } + qb.builder.WriteString(url.QueryEscape(key)) + qb.builder.WriteByte('=') + qb.builder.WriteString(url.QueryEscape(value)) +} + +func (qb *queryBuilder) query() string { + return qb.builder.String() +} diff --git a/pkg/api/search.go b/pkg/api/search.go index 5113a023414..2a8455eb61a 100644 --- a/pkg/api/search.go +++ b/pkg/api/search.go @@ -6,12 +6,6 @@ import ( "github.com/grafana/tempo/pkg/tempopb" ) -// IsBackendSearch returns true if the request has a start, and end parameter and is the /api/search path -func IsBackendSearch(r *http.Request) bool { - q := r.URL.Query() - return q.Get(urlParamStart) != "" && q.Get(urlParamEnd) != "" -} - // IsSearchBlock returns true if the request appears to be for backend blocks. It is not exhaustive // and only looks for blockID func IsSearchBlock(r *http.Request) bool { diff --git a/pkg/api/search_tags.go b/pkg/api/search_tags.go index 530454daf4a..b7ca864e9ea 100644 --- a/pkg/api/search_tags.go +++ b/pkg/api/search_tags.go @@ -41,7 +41,9 @@ func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, erro SearchReq: searchReq, } - s := r.URL.Query().Get(urlParamStartPage) + vals := r.URL.Query() + + s := vals.Get(urlParamStartPage) startPage, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid startPage: %w", err) @@ -51,7 +53,7 @@ func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, erro } req.StartPage = uint32(startPage) - s = r.URL.Query().Get(urlParamPagesToSearch) + s = vals.Get(urlParamPagesToSearch) pagesToSearch64, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) @@ -61,28 +63,28 @@ func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, erro } req.PagesToSearch = uint32(pagesToSearch64) - s = r.URL.Query().Get(urlParamBlockID) + s = vals.Get(urlParamBlockID) blockID, err := uuid.Parse(s) if err != nil { return nil, fmt.Errorf("invalid blockID: %w", err) } req.BlockID = blockID.String() - s = r.URL.Query().Get(urlParamEncoding) + s = vals.Get(urlParamEncoding) encoding, err := backend.ParseEncoding(s) if err != nil { return nil, err } req.Encoding = encoding.String() - s = r.URL.Query().Get(urlParamIndexPageSize) + s = vals.Get(urlParamIndexPageSize) indexPageSize, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) } req.IndexPageSize = uint32(indexPageSize) - s = r.URL.Query().Get(urlParamTotalRecords) + s = vals.Get(urlParamTotalRecords) totalRecords, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) @@ -95,16 +97,16 @@ func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, erro // Data encoding can be blank for some block formats, therefore // no validation on the param here. Eventually we may be able // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + dataEncoding := vals.Get(urlParamDataEncoding) req.DataEncoding = dataEncoding - version := r.URL.Query().Get(urlParamVersion) + version := vals.Get(urlParamVersion) if version == "" { return nil, errors.New("version required") } req.Version = version - s = r.URL.Query().Get(urlParamSize) + s = vals.Get(urlParamSize) size, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, fmt.Errorf("invalid size %s: %w", s, err) @@ -113,14 +115,14 @@ func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, erro // Footer size can be 0 for some blocks, just ensure we // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) + f := vals.Get(urlParamFooterSize) footerSize, err := strconv.ParseUint(f, 10, 32) if err != nil { return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) } req.FooterSize = uint32(footerSize) - s = r.URL.Query().Get(urlParamDedicatedColumns) + s = vals.Get(urlParamDedicatedColumns) if s != "" { var dedicatedColumns []*tempopb.DedicatedColumn err = json.Unmarshal([]byte(s), &dedicatedColumns) @@ -163,7 +165,9 @@ func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*te SearchReq: tagSearchReq, } - s := r.URL.Query().Get(urlParamStartPage) + vals := r.URL.Query() + + s := vals.Get(urlParamStartPage) startPage, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid startPage: %w", err) @@ -173,7 +177,7 @@ func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*te } req.StartPage = uint32(startPage) - s = r.URL.Query().Get(urlParamPagesToSearch) + s = vals.Get(urlParamPagesToSearch) pagesToSearch64, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) @@ -183,28 +187,28 @@ func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*te } req.PagesToSearch = uint32(pagesToSearch64) - s = r.URL.Query().Get(urlParamBlockID) + s = vals.Get(urlParamBlockID) blockID, err := uuid.Parse(s) if err != nil { return nil, fmt.Errorf("invalid blockID: %w", err) } req.BlockID = blockID.String() - s = r.URL.Query().Get(urlParamEncoding) + s = vals.Get(urlParamEncoding) encoding, err := backend.ParseEncoding(s) if err != nil { return nil, err } req.Encoding = encoding.String() - s = r.URL.Query().Get(urlParamIndexPageSize) + s = vals.Get(urlParamIndexPageSize) indexPageSize, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) } req.IndexPageSize = uint32(indexPageSize) - s = r.URL.Query().Get(urlParamTotalRecords) + s = vals.Get(urlParamTotalRecords) totalRecords, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) @@ -217,16 +221,16 @@ func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*te // Data encoding can be blank for some block formats, therefore // no validation on the param here. Eventually we may be able // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + dataEncoding := vals.Get(urlParamDataEncoding) req.DataEncoding = dataEncoding - version := r.URL.Query().Get(urlParamVersion) + version := vals.Get(urlParamVersion) if version == "" { return nil, errors.New("version required") } req.Version = version - s = r.URL.Query().Get(urlParamSize) + s = vals.Get(urlParamSize) size, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, fmt.Errorf("invalid size %s: %w", s, err) @@ -235,14 +239,14 @@ func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*te // Footer size can be 0 for some blocks, just ensure we // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) + f := vals.Get(urlParamFooterSize) footerSize, err := strconv.ParseUint(f, 10, 32) if err != nil { return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) } req.FooterSize = uint32(footerSize) - s = r.URL.Query().Get(urlParamDedicatedColumns) + s = vals.Get(urlParamDedicatedColumns) if s != "" { var dedicatedColumns []*tempopb.DedicatedColumn err = json.Unmarshal([]byte(s), &dedicatedColumns) @@ -270,7 +274,9 @@ func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockReque SearchReq: tagSearchReq, } - s := r.URL.Query().Get(urlParamStartPage) + vals := r.URL.Query() + + s := vals.Get(urlParamStartPage) startPage, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid startPage: %w", err) @@ -280,7 +286,7 @@ func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockReque } req.StartPage = uint32(startPage) - s = r.URL.Query().Get(urlParamPagesToSearch) + s = vals.Get(urlParamPagesToSearch) pagesToSearch64, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) @@ -290,28 +296,28 @@ func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockReque } req.PagesToSearch = uint32(pagesToSearch64) - s = r.URL.Query().Get(urlParamBlockID) + s = vals.Get(urlParamBlockID) blockID, err := uuid.Parse(s) if err != nil { return nil, fmt.Errorf("invalid blockID: %w", err) } req.BlockID = blockID.String() - s = r.URL.Query().Get(urlParamEncoding) + s = vals.Get(urlParamEncoding) encoding, err := backend.ParseEncoding(s) if err != nil { return nil, err } req.Encoding = encoding.String() - s = r.URL.Query().Get(urlParamIndexPageSize) + s = vals.Get(urlParamIndexPageSize) indexPageSize, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) } req.IndexPageSize = uint32(indexPageSize) - s = r.URL.Query().Get(urlParamTotalRecords) + s = vals.Get(urlParamTotalRecords) totalRecords, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) @@ -324,16 +330,16 @@ func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockReque // Data encoding can be blank for some block formats, therefore // no validation on the param here. Eventually we may be able // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + dataEncoding := vals.Get(urlParamDataEncoding) req.DataEncoding = dataEncoding - version := r.URL.Query().Get(urlParamVersion) + version := vals.Get(urlParamVersion) if version == "" { return nil, errors.New("version required") } req.Version = version - s = r.URL.Query().Get(urlParamSize) + s = vals.Get(urlParamSize) size, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, fmt.Errorf("invalid size %s: %w", s, err) @@ -342,14 +348,14 @@ func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockReque // Footer size can be 0 for some blocks, just ensure we // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) + f := vals.Get(urlParamFooterSize) footerSize, err := strconv.ParseUint(f, 10, 32) if err != nil { return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) } req.FooterSize = uint32(footerSize) - s = r.URL.Query().Get(urlParamDedicatedColumns) + s = vals.Get(urlParamDedicatedColumns) if s != "" { var dedicatedColumns []*tempopb.DedicatedColumn err = json.Unmarshal([]byte(s), &dedicatedColumns) @@ -394,14 +400,15 @@ func parseSearchTagValuesRequest(r *http.Request, enforceTraceQL bool) (*tempopb } } - query, _ := extractQueryParam(r, urlParamQuery) + vals := r.URL.Query() + query, _ := extractQueryParam(vals, urlParamQuery) req := &tempopb.SearchTagValuesRequest{ TagName: tagName, Query: query, } - if s, ok := extractQueryParam(r, urlParamStart); ok { + if s, ok := extractQueryParam(vals, urlParamStart); ok { start, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid start: %w", err) @@ -409,7 +416,7 @@ func parseSearchTagValuesRequest(r *http.Request, enforceTraceQL bool) (*tempopb req.Start = uint32(start) } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { end, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid end: %w", err) @@ -421,8 +428,9 @@ func parseSearchTagValuesRequest(r *http.Request, enforceTraceQL bool) (*tempopb } func ParseSearchTagsRequest(r *http.Request) (*tempopb.SearchTagsRequest, error) { - scope, _ := extractQueryParam(r, urlParamScope) - query, _ := extractQueryParam(r, urlParamQuery) + vals := r.URL.Query() + scope, _ := extractQueryParam(vals, urlParamScope) + query, _ := extractQueryParam(vals, urlParamQuery) attScope := traceql.AttributeScopeFromString(scope) if attScope == traceql.AttributeScopeUnknown && scope != ParamScopeIntrinsic { @@ -434,7 +442,7 @@ func ParseSearchTagsRequest(r *http.Request) (*tempopb.SearchTagsRequest, error) Scope: scope, } - if s, ok := extractQueryParam(r, urlParamStart); ok { + if s, ok := extractQueryParam(vals, urlParamStart); ok { start, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid start: %w", err) @@ -442,7 +450,7 @@ func ParseSearchTagsRequest(r *http.Request) (*tempopb.SearchTagsRequest, error) req.Start = uint32(start) } - if s, ok := extractQueryParam(r, urlParamEnd); ok { + if s, ok := extractQueryParam(vals, urlParamEnd); ok { end, err := strconv.ParseInt(s, 10, 32) if err != nil { return nil, fmt.Errorf("invalid end: %w", err) @@ -463,13 +471,13 @@ func BuildSearchTagsRequest(req *http.Request, searchReq *tempopb.SearchTagsRequ return req, nil } - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) - q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) - q.Set(urlParamScope, searchReq.Scope) - q.Set(urlParamQuery, searchReq.Query) + qb := newQueryBuilder("") + qb.addParam(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) + qb.addParam(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) + qb.addParam(urlParamScope, searchReq.Scope) + qb.addParam(urlParamQuery, searchReq.Query) - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req, nil } @@ -486,19 +494,19 @@ func BuildSearchTagsBlockRequest(req *http.Request, searchReq *tempopb.SearchTag return nil, err } - q := req.URL.Query() - q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) - q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) - q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) - q.Set(urlParamDataEncoding, searchReq.DataEncoding) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) + q := newQueryBuilder(req.URL.RawQuery) + q.addParam(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) + q.addParam(urlParamBlockID, searchReq.BlockID) + q.addParam(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) + q.addParam(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) + q.addParam(urlParamEncoding, searchReq.Encoding) + q.addParam(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) + q.addParam(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) + q.addParam(urlParamDataEncoding, searchReq.DataEncoding) + q.addParam(urlParamVersion, searchReq.Version) + q.addParam(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = q.query() return req, nil } @@ -514,12 +522,12 @@ func BuildSearchTagValuesRequest(req *http.Request, searchReq *tempopb.SearchTag return req, nil } - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) - q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) - q.Set(urlParamQuery, searchReq.Query) + qb := newQueryBuilder("") + qb.addParam(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) + qb.addParam(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) + qb.addParam(urlParamQuery, searchReq.Query) - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req, nil } @@ -536,19 +544,19 @@ func BuildSearchTagValuesBlockRequest(req *http.Request, searchReq *tempopb.Sear return nil, err } - q := req.URL.Query() - q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) - q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) - q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) - q.Set(urlParamDataEncoding, searchReq.DataEncoding) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) + qb := newQueryBuilder(req.URL.RawQuery) + qb.addParam(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) + qb.addParam(urlParamBlockID, searchReq.BlockID) + qb.addParam(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) + qb.addParam(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) + qb.addParam(urlParamEncoding, searchReq.Encoding) + qb.addParam(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) + qb.addParam(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) + qb.addParam(urlParamDataEncoding, searchReq.DataEncoding) + qb.addParam(urlParamVersion, searchReq.Version) + qb.addParam(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) - req.URL.RawQuery = q.Encode() + req.URL.RawQuery = qb.query() return req, nil } diff --git a/pkg/api/search_test.go b/pkg/api/search_test.go index 3b76c79ffa1..cff56c1c771 100644 --- a/pkg/api/search_test.go +++ b/pkg/api/search_test.go @@ -7,20 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIsBackendSearch(t *testing.T) { - assert.False(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search", nil))) - assert.False(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search/?start=1", nil))) - assert.False(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search/?end=1", nil))) - assert.False(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search?tags=vulture-1%3DuxyWcCSQHOuRvM", nil))) - assert.False(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search/tag/vulture-2/values", nil))) - - assert.True(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search/?start=1&end=2", nil))) - assert.True(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search?start=1&end=2&tags=test", nil))) - assert.True(t, IsBackendSearch(httptest.NewRequest("GET", "/api/search/?start=1&end=2&tags=test", nil))) - assert.True(t, IsBackendSearch(httptest.NewRequest("GET", "/querier/api/search?start=1&end=2&tags=test", nil))) - assert.True(t, IsBackendSearch(httptest.NewRequest("GET", "/querier/api/search/?start=1&end=2&tags=test", nil))) -} - func TestIsSearchBlock(t *testing.T) { assert.False(t, IsSearchBlock(httptest.NewRequest("GET", "/api/search", nil))) assert.False(t, IsSearchBlock(httptest.NewRequest("GET", "/api/search/?start=1", nil)))