Skip to content

Commit 908b704

Browse files
authored
fix(apiv3): Accept camelCase HTTP query params, keep snake_case as deprecated aliases (jaegertracing#8625)
## Summary Part 2 of jaegertracing#8619. - Moves all query param constants and `parseFindTracesQuery` into a new `query_parser.go` - Canonical param names are now camelCase (e.g. `query.serviceName`, `startTime`, `spanKind`) matching proto3 JSON encoding convention used by gRPC-gateway - All previous snake_case names (`query.service_name`, `start_time`, `span_kind`, etc.) are kept as deprecated fallbacks via a `getQueryParam` helper that prefers the canonical name - `query.num_traces` remains a deprecated alias for `query.searchDepth` (on top of the snake_case alias `query.search_depth`) No response body changes — the server already returns camelCase via `jsonpb.Marshaler`. ## Test plan - [ ] All existing tests pass - [ ] New `TestHTTPGatewayFindTracesDeprecatedParams` verifies all snake_case fallbacks for FindTraces - [ ] `TestHTTPGatewayGetTrace` has canonical + deprecated subtests for time window params - [ ] `TestHTTPGatewayGetOperationsErrors` tests both `spanKind` (canonical) and `span_kind` (deprecated) - [ ] Error messages include the actual param name used (canonical or deprecated), so clients get actionable feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Yuri Shkuro <github@ysh.us>
1 parent 92c7b3b commit 908b704

4 files changed

Lines changed: 290 additions & 150 deletions

File tree

cmd/jaeger/internal/extension/jaegerquery/internal/apiv3/gateway_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (gw *testGateway) runGatewayGetOperations(t *testing.T) {
131131
On("GetOperations", matchContext, qp).
132132
Return([]tracestore.Operation{{Name: "get_users", SpanKind: "server"}}, nil).Once()
133133

134-
body, statusCode := gw.execRequest(t, "/api/v3/operations?service=foo&span_kind=server")
134+
body, statusCode := gw.execRequest(t, "/api/v3/operations?service=foo&spanKind=server")
135135
require.Equal(t, http.StatusOK, statusCode)
136136
body = gw.verifySnapshot(t, body)
137137

cmd/jaeger/internal/extension/jaegerquery/internal/apiv3/http_gateway.go

Lines changed: 12 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import (
88
"errors"
99
"fmt"
1010
"net/http"
11-
"net/url"
1211
"strconv"
1312
"time"
1413

1514
"github.com/gogo/protobuf/jsonpb"
1615
"github.com/gogo/protobuf/proto"
17-
"go.opentelemetry.io/collector/pdata/pcommon"
1816
"go.opentelemetry.io/collector/pdata/ptrace"
1917
"go.opentelemetry.io/otel/trace"
2018
"go.uber.org/zap"
@@ -29,29 +27,6 @@ import (
2927
"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter"
3028
)
3129

32-
const (
33-
paramTraceID = "trace_id" // get trace by ID
34-
paramStartTime = "start_time"
35-
paramEndTime = "end_time"
36-
paramRawTraces = "raw_traces"
37-
paramServiceName = "query.service_name" // find traces
38-
paramOperationName = "query.operation_name"
39-
paramTimeMin = "query.start_time_min"
40-
paramTimeMax = "query.start_time_max"
41-
// paramSearchDepth is the canonical name matching the proto field and the future gRPC-gateway generated binding.
42-
paramSearchDepth = "query.search_depth"
43-
// paramNumTraces is a deprecated alias for paramSearchDepth kept for backwards compatibility.
44-
paramNumTraces = "query.num_traces"
45-
paramDurationMin = "query.duration_min"
46-
paramDurationMax = "query.duration_max"
47-
paramQueryRawTraces = "query.raw_traces"
48-
49-
routeGetTrace = "/api/v3/traces/{" + paramTraceID + "}"
50-
routeFindTraces = "/api/v3/traces"
51-
routeGetServices = "/api/v3/services"
52-
routeGetOperations = "/api/v3/operations"
53-
)
54-
5530
// HTTPGateway exposes APIv3 HTTP endpoints.
5631
type HTTPGateway struct {
5732
QueryService *querysvc.QueryService
@@ -166,26 +141,24 @@ func (h *HTTPGateway) getTrace(w http.ResponseWriter, r *http.Request) {
166141
},
167142
},
168143
}
169-
http_query := r.URL.Query()
170-
startTime := http_query.Get(paramStartTime)
171-
if startTime != "" {
144+
q := r.URL.Query()
145+
if startTime, paramName := getQueryParam(q, paramStartTime, paramStartTimeDeprecated); startTime != "" {
172146
timeParsed, err := time.Parse(time.RFC3339Nano, startTime)
173-
if h.tryParamError(w, err, paramStartTime) {
147+
if h.tryParamError(w, err, paramName) {
174148
return
175149
}
176150
request.TraceIDs[0].Start = timeParsed.UTC()
177151
}
178-
endTime := http_query.Get(paramEndTime)
179-
if endTime != "" {
152+
if endTime, paramName := getQueryParam(q, paramEndTime, paramEndTimeDeprecated); endTime != "" {
180153
timeParsed, err := time.Parse(time.RFC3339Nano, endTime)
181-
if h.tryParamError(w, err, paramEndTime) {
154+
if h.tryParamError(w, err, paramName) {
182155
return
183156
}
184157
request.TraceIDs[0].End = timeParsed.UTC()
185158
}
186-
if r := http_query.Get(paramRawTraces); r != "" {
187-
rawTraces, err := strconv.ParseBool(r)
188-
if h.tryParamError(w, err, paramRawTraces) {
159+
if rawStr, paramName := getQueryParam(q, paramRawTraces, paramRawTracesDeprecated); rawStr != "" {
160+
rawTraces, err := strconv.ParseBool(rawStr)
161+
if h.tryParamError(w, err, paramName) {
189162
return
190163
}
191164
request.RawTraces = rawTraces
@@ -206,71 +179,6 @@ func (h *HTTPGateway) findTraces(w http.ResponseWriter, r *http.Request) {
206179
h.returnTraces(traces, err, w)
207180
}
208181

209-
func (h *HTTPGateway) parseFindTracesQuery(q url.Values, w http.ResponseWriter) (*querysvc.TraceQueryParams, bool) {
210-
queryParams := &querysvc.TraceQueryParams{
211-
TraceQueryParams: tracestore.TraceQueryParams{
212-
ServiceName: q.Get(paramServiceName),
213-
OperationName: q.Get(paramOperationName),
214-
Attributes: pcommon.NewMap(), // most curiously not supported by grpc-gateway
215-
},
216-
}
217-
218-
timeMin := q.Get(paramTimeMin)
219-
timeMax := q.Get(paramTimeMax)
220-
if timeMin == "" || timeMax == "" {
221-
err := fmt.Errorf("%s and %s are required", paramTimeMin, paramTimeMax)
222-
h.tryHandleError(w, err, http.StatusBadRequest)
223-
return nil, true
224-
}
225-
timeMinParsed, err := time.Parse(time.RFC3339Nano, timeMin)
226-
if h.tryParamError(w, err, paramTimeMin) {
227-
return nil, true
228-
}
229-
timeMaxParsed, err := time.Parse(time.RFC3339Nano, timeMax)
230-
if h.tryParamError(w, err, paramTimeMax) {
231-
return nil, true
232-
}
233-
queryParams.StartTimeMin = timeMinParsed
234-
queryParams.StartTimeMax = timeMaxParsed
235-
236-
searchDepthParam := paramSearchDepth
237-
n := q.Get(paramSearchDepth)
238-
if n == "" {
239-
n = q.Get(paramNumTraces)
240-
searchDepthParam = paramNumTraces
241-
}
242-
if n != "" {
243-
searchDepth, err := strconv.Atoi(n)
244-
if h.tryParamError(w, err, searchDepthParam) {
245-
return nil, true
246-
}
247-
queryParams.SearchDepth = searchDepth
248-
}
249-
250-
if d := q.Get(paramDurationMin); d != "" {
251-
dur, err := time.ParseDuration(d)
252-
if h.tryParamError(w, err, paramDurationMin) {
253-
return nil, true
254-
}
255-
queryParams.DurationMin = dur
256-
}
257-
if d := q.Get(paramDurationMax); d != "" {
258-
dur, err := time.ParseDuration(d)
259-
if h.tryParamError(w, err, paramDurationMax) {
260-
return nil, true
261-
}
262-
queryParams.DurationMax = dur
263-
}
264-
if r := q.Get(paramQueryRawTraces); r != "" {
265-
rawTraces, err := strconv.ParseBool(r)
266-
if h.tryParamError(w, err, paramQueryRawTraces) {
267-
return nil, true
268-
}
269-
queryParams.RawTraces = rawTraces
270-
}
271-
return queryParams, false
272-
}
273-
274182
func (h *HTTPGateway) getServices(w http.ResponseWriter, r *http.Request) {
275183
services, err := h.QueryService.GetServices(r.Context())
276184
if h.tryHandleError(w, err, http.StatusInternalServerError) {
@@ -285,10 +193,11 @@ func (h *HTTPGateway) getServices(w http.ResponseWriter, r *http.Request) {
285193
}
286194

287195
func (h *HTTPGateway) getOperations(w http.ResponseWriter, r *http.Request) {
288-
query := r.URL.Query()
196+
q := r.URL.Query()
197+
spanKind, _ := getQueryParam(q, paramSpanKind, paramSpanKindDeprecated)
289198
queryParams := tracestore.OperationQueryParams{
290-
ServiceName: query.Get("service"),
291-
SpanKind: query.Get("span_kind"),
199+
ServiceName: q.Get("service"),
200+
SpanKind: spanKind,
292201
}
293202
operations, err := h.QueryService.GetOperations(r.Context(), queryParams)
294203
if h.tryHandleError(w, err, http.StatusInternalServerError) {

0 commit comments

Comments
 (0)