mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
QueryService: Return application/json and better errors (#84234)
This commit is contained in:
79
pkg/registry/apis/query/errors.go
Normal file
79
pkg/registry/apis/query/errors.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
var QueryError = errutil.BadRequest("query.error").MustTemplate(
|
||||
"failed to execute query [{{ .Public.refId }}]: {{ .Error }}",
|
||||
errutil.WithPublic(
|
||||
"failed to execute query [{{ .Public.refId }}]: {{ .Public.error }}",
|
||||
))
|
||||
|
||||
func MakeQueryError(refID, err error) error {
|
||||
var pErr error
|
||||
var utilErr errutil.Error
|
||||
// See if this is grafana error, if so, grab public message
|
||||
if errors.As(err, &utilErr) {
|
||||
pErr = utilErr.Public()
|
||||
} else {
|
||||
pErr = err
|
||||
}
|
||||
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]any{
|
||||
"refId": refID,
|
||||
"error": pErr.Error(),
|
||||
},
|
||||
Error: err,
|
||||
}
|
||||
|
||||
return QueryError.Build(data)
|
||||
}
|
||||
|
||||
func MakePublicQueryError(refID, err string) error {
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]any{
|
||||
"refId": refID,
|
||||
"error": err,
|
||||
},
|
||||
}
|
||||
return QueryError.Build(data)
|
||||
}
|
||||
|
||||
var depErrStr = "did not execute expression [{{ .Public.refId }}] due to a failure to of the dependent expression or query [{{.Public.depRefId}}]"
|
||||
|
||||
var dependencyError = errutil.BadRequest("sse.dependencyError").MustTemplate(
|
||||
depErrStr,
|
||||
errutil.WithPublic(depErrStr))
|
||||
|
||||
func makeDependencyError(refID, depRefID string) error {
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"refId": refID,
|
||||
"depRefId": depRefID,
|
||||
},
|
||||
Error: fmt.Errorf("did not execute expression %v due to a failure to of the dependent expression or query %v", refID, depRefID),
|
||||
}
|
||||
|
||||
return dependencyError.Build(data)
|
||||
}
|
||||
|
||||
var cyclicErrStr = "cyclic reference in expression [{{ .Public.refId }}]"
|
||||
|
||||
var cyclicErr = errutil.BadRequest("sse.cyclic").MustTemplate(
|
||||
cyclicErrStr,
|
||||
errutil.WithPublic(cyclicErrStr))
|
||||
|
||||
func makeCyclicError(refID string) error {
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"refId": refID,
|
||||
},
|
||||
Error: fmt.Errorf("cyclic reference in %s", refID),
|
||||
}
|
||||
return cyclicErr.Build(data)
|
||||
}
|
||||
21
pkg/registry/apis/query/errors_test.go
Normal file
21
pkg/registry/apis/query/errors_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
func TestQueryErrorType(t *testing.T) {
|
||||
qet := expr.QueryError
|
||||
utilError := errutil.Error{}
|
||||
qe := expr.MakeQueryError("A", "", fmt.Errorf("not work"))
|
||||
|
||||
require.True(t, errors.Is(qe, qet))
|
||||
require.True(t, errors.As(qe, &utilError))
|
||||
}
|
||||
@@ -81,11 +81,11 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
|
||||
for _, q := range input.Queries {
|
||||
_, found := queryRefIDs[q.RefID]
|
||||
if found {
|
||||
return rsp, fmt.Errorf("multiple queries found for refId: %s", q.RefID)
|
||||
return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId")
|
||||
}
|
||||
_, found = expressions[q.RefID]
|
||||
if found {
|
||||
return rsp, fmt.Errorf("multiple queries found for refId: %s", q.RefID)
|
||||
return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId")
|
||||
}
|
||||
|
||||
ds, err := p.getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID)
|
||||
@@ -161,7 +161,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
|
||||
if !ok {
|
||||
target, ok = expressions[refId]
|
||||
if !ok {
|
||||
return rsp, fmt.Errorf("expression [%s] is missing variable [%s]", exp.RefID, refId)
|
||||
return rsp, makeDependencyError(exp.RefID, refId)
|
||||
}
|
||||
}
|
||||
// Do not hide queries used in variables
|
||||
@@ -169,7 +169,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
|
||||
q.Hide = false
|
||||
}
|
||||
if target.ID() == exp.ID() {
|
||||
return rsp, fmt.Errorf("expression [%s] can not depend on itself", exp.RefID)
|
||||
return rsp, makeCyclicError(refId)
|
||||
}
|
||||
dg.SetEdge(dg.NewEdge(target, exp))
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
|
||||
// Add the sorted expressions
|
||||
sortedNodes, err := topo.SortStabilized(dg, nil)
|
||||
if err != nil {
|
||||
return rsp, fmt.Errorf("cyclic references in query")
|
||||
return rsp, makeCyclicError("")
|
||||
}
|
||||
for _, v := range sortedNodes {
|
||||
if v.ID() > 0 {
|
||||
|
||||
@@ -75,39 +75,41 @@ func TestQuerySplitting(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
fpath := path.Join("testdata", file.Name())
|
||||
// nolint:gosec
|
||||
body, err := os.ReadFile(fpath)
|
||||
require.NoError(t, err)
|
||||
harness := &parserTestObject{}
|
||||
err = json.Unmarshal(body, harness)
|
||||
require.NoError(t, err)
|
||||
t.Run(file.Name(), func(t *testing.T) {
|
||||
fpath := path.Join("testdata", file.Name())
|
||||
// nolint:gosec
|
||||
body, err := os.ReadFile(fpath)
|
||||
require.NoError(t, err)
|
||||
harness := &parserTestObject{}
|
||||
err = json.Unmarshal(body, harness)
|
||||
require.NoError(t, err)
|
||||
|
||||
changed := false
|
||||
parsed, err := parser.parseRequest(ctx, &harness.Request)
|
||||
if err != nil {
|
||||
if !assert.Equal(t, harness.Error, err.Error(), "File %s", file) {
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
x, _ := json.Marshal(parsed)
|
||||
y, _ := json.Marshal(harness.Expect)
|
||||
if !assert.JSONEq(t, string(y), string(x), "File %s", file) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
harness.Error = ""
|
||||
harness.Expect = parsed
|
||||
changed := false
|
||||
parsed, err := parser.parseRequest(ctx, &harness.Request)
|
||||
if err != nil {
|
||||
harness.Error = err.Error()
|
||||
if !assert.Equal(t, harness.Error, err.Error(), "File %s", file) {
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
x, _ := json.Marshal(parsed)
|
||||
y, _ := json.Marshal(harness.Expect)
|
||||
if !assert.JSONEq(t, string(y), string(x), "File %s", file) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
jj, err := json.MarshalIndent(harness, "", " ")
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(fpath, jj, 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if changed {
|
||||
harness.Error = ""
|
||||
harness.Expect = parsed
|
||||
if err != nil {
|
||||
harness.Error = err.Error()
|
||||
}
|
||||
jj, err := json.MarshalIndent(harness, "", " ")
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(fpath, jj, 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,10 +46,7 @@ func (b *QueryAPIBuilder) doQuery(w http.ResponseWriter, r *http.Request) {
|
||||
errutil.WithPublicMessage(err.Error())), w)
|
||||
return
|
||||
}
|
||||
errhttp.Write(ctx, errutil.BadRequest(
|
||||
"query.parse",
|
||||
errutil.WithPublicMessage("Error parsing query")).
|
||||
Errorf("error parsing: %w", err), w)
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,6 +60,7 @@ func (b *QueryAPIBuilder) doQuery(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(query.GetResponseCode(rsp))
|
||||
_ = json.NewEncoder(w).Encode(rsp)
|
||||
}
|
||||
@@ -112,7 +110,7 @@ func (b *QueryAPIBuilder) handleQuerySingleDatasource(ctx context.Context, req d
|
||||
return &backend.QueryDataResponse{}, nil
|
||||
}
|
||||
|
||||
// headers?
|
||||
// Add user headers... here or in client.QueryData
|
||||
client, err := b.client.GetDataSourceClient(ctx, v0alpha1.DataSourceRef{
|
||||
Type: req.PluginId,
|
||||
UID: req.UID,
|
||||
@@ -121,9 +119,8 @@ func (b *QueryAPIBuilder) handleQuerySingleDatasource(ctx context.Context, req d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// headers?
|
||||
_, rsp, err := client.QueryData(ctx, *req.Request)
|
||||
if err == nil {
|
||||
if err == nil && rsp != nil {
|
||||
for _, q := range req.Request.Queries {
|
||||
if q.ResultAssertions != nil {
|
||||
result, ok := rsp.Responses[q.RefID]
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
]
|
||||
},
|
||||
"expect": {},
|
||||
"error": "cyclic references in query"
|
||||
"error": "[sse.cyclic] cyclic reference in expression []"
|
||||
}
|
||||
@@ -16,5 +16,5 @@
|
||||
]
|
||||
},
|
||||
"expect": {},
|
||||
"error": "expression [A] can not depend on itself"
|
||||
"error": "[sse.cyclic] cyclic reference in expression [A]"
|
||||
}
|
||||
Reference in New Issue
Block a user