diff --git a/cmd/cortex/main.go b/cmd/cortex/main.go index ae1b0c4a707..8c09f37931f 100644 --- a/cmd/cortex/main.go +++ b/cmd/cortex/main.go @@ -310,6 +310,7 @@ func setupQuerier( }).WithPrefix("/api/prom/api/v1") api.Register(promRouter) router.PathPrefix("/api/v1").Handler(promRouter) + router.Path("/validate_expr").Handler(http.HandlerFunc(distributor.ValidateExprHandler)) router.Path("/user_stats").Handler(http.HandlerFunc(distributor.UserStatsHandler)) router.Path("/graph").Handler(ui.GraphHandler()) router.PathPrefix("/static/").Handler(ui.StaticAssetsHandler("/api/prom/static/")) diff --git a/distributor/http_server.go b/distributor/http_server.go index 7592825e2e2..7119fbfc27f 100644 --- a/distributor/http_server.go +++ b/distributor/http_server.go @@ -1,9 +1,11 @@ package distributor import ( + "fmt" "net/http" "github.com/prometheus/common/log" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/remote" "github.com/weaveworks/cortex/util" @@ -54,3 +56,43 @@ func (d *Distributor) UserStatsHandler(w http.ResponseWriter, r *http.Request) { util.WriteJSONResponse(w, stats) } + +// ValidateExprHandler validates a PromQL expression. +func (d *Distributor) ValidateExprHandler(w http.ResponseWriter, r *http.Request) { + _, err := promql.ParseExpr(r.FormValue("expr")) + + // We mimick the response format of Prometheus's official API here for + // consistency, but unfortunately its private types (string consts etc.) + // aren't reusable. + if err == nil { + util.WriteJSONResponse(w, map[string]string{ + "status": "success", + }) + return + } + + parseErr, ok := err.(*promql.ParseErr) + if !ok { + // This should always be a promql.ParseErr. + http.Error(w, fmt.Sprintf("unexpected error returned from PromQL parser: %v", err), http.StatusInternalServerError) + return + } + + // If the parsing input was a single line, parseErr.Line is 0 + // and the generated error string omits the line entirely. But we + // want to report line numbers consistently, no matter how many + // lines there are (starting at 1). + if parseErr.Line == 0 { + parseErr.Line = 1 + } + w.WriteHeader(http.StatusBadRequest) + util.WriteJSONResponse(w, map[string]interface{}{ + "status": "error", + "errorType": "bad_data", + "error": err.Error(), + "location": map[string]int{ + "line": parseErr.Line, + "pos": parseErr.Pos, + }, + }) +}