blob: f7d0b49bb06cd554648ce992eed697c6c50f7b86 [file] [log] [blame]
package search
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/shared"
"go.skia.org/infra/golden/go/types"
)
const (
// SORT_FIELD_COUNT indicates that the image counts should be used for sorting.
SORT_FIELD_COUNT = "count"
// SORT_FIELD_DIFF indicates that the diff field should be used for sorting.
SORT_FIELD_DIFF = "diff"
)
var (
// sortDirections are the valid options for any of the sort direction fields.
sortDirections = []string{SORT_ASC, SORT_DESC}
// rowSortFields are the valid options for the sort field for rows.
rowSortFields = []string{SORT_FIELD_COUNT, SORT_FIELD_DIFF}
// columnSortFields are the valid options for the sort field for columns.
columnSortFields = []string{SORT_FIELD_DIFF}
)
// ParseCTQuery parses JSON from the given ReadCloser into the given
// pointer to an instance of CTQuery. It will fill in values and validate key
// fields of the query. It will return an error if parsing failed
// for some reason and always close the ReadCloser. testName is the name of the
// test that should be compared and limitDefault is the default limit for the
// row and column queries.
func ParseCTQuery(r io.ReadCloser, limitDefault int32, ctQuery *CTQuery) error {
defer util.Close(r)
var err error
// Parse the body of the JSON request.
if err := json.NewDecoder(r).Decode(ctQuery); err != nil {
return err
}
if (ctQuery.RowQuery == nil) || (ctQuery.ColumnQuery == nil) {
return fmt.Errorf("Rowquery and columnquery must not be null.")
}
// Parse the query string into a url.Values instance.
if ctQuery.RowQuery.Query, err = url.ParseQuery(ctQuery.RowQuery.QueryStr); err != nil {
return err
}
if ctQuery.ColumnQuery.Query, err = url.ParseQuery(ctQuery.ColumnQuery.QueryStr); err != nil {
return err
}
rowCorpus := ctQuery.RowQuery.Query.Get(types.CORPUS_FIELD)
colCorpus := ctQuery.ColumnQuery.Query.Get(types.CORPUS_FIELD)
if (rowCorpus != colCorpus) || (rowCorpus == "") {
return fmt.Errorf("Corpus for row and column query need to match and be non-empty.")
}
// Make sure that the name is forced to match.
if !util.In(types.PRIMARY_KEY_FIELD, ctQuery.Match) {
ctQuery.Match = append(ctQuery.Match, types.PRIMARY_KEY_FIELD)
}
// TODO(stephana): Factor out setting default values and limiting values.
// Also factor this out together with the Validation type.
// Set the limit to a default if not set.
if ctQuery.RowQuery.Limit == 0 {
ctQuery.RowQuery.Limit = int32(limitDefault)
}
ctQuery.RowQuery.Limit = util.MinInt32(ctQuery.RowQuery.Limit, MAX_LIMIT)
if ctQuery.ColumnQuery.Limit == 0 {
ctQuery.ColumnQuery.Limit = limitDefault
}
ctQuery.ColumnQuery.Limit = util.MinInt32(ctQuery.ColumnQuery.Limit, MAX_LIMIT)
validate := shared.Validation{}
// Parse the patchsets.
ctQuery.ColumnQuery.Patchsets = validate.Int64SliceValue("patchsets", ctQuery.ColumnQuery.PatchsetsStr, nil)
ctQuery.RowQuery.Patchsets = validate.Int64SliceValue("patchsets", ctQuery.RowQuery.PatchsetsStr, nil)
ctQuery.ColumnQuery.Issue = validate.Int64Value("column.issue", ctQuery.ColumnQuery.IssueStr, 0)
ctQuery.RowQuery.Issue = validate.Int64Value("row.issue", ctQuery.RowQuery.IssueStr, 0)
// Parse the general parameters of the query.
validate.StrValue("sortRows", &ctQuery.SortRows, rowSortFields, SORT_FIELD_COUNT)
validate.StrValue("rowsDir", &ctQuery.RowsDir, sortDirections, SORT_DESC)
validate.StrValue("sortColumns", &ctQuery.SortColumns, columnSortFields, SORT_FIELD_DIFF)
validate.StrValue("columnsDir", &ctQuery.ColumnsDir, sortDirections, SORT_ASC)
validate.StrValue("metrics", &ctQuery.Metric, diff.GetDiffMetricIDs(), diff.METRIC_PERCENT)
return validate.Errors()
}
// TODO(stephana): Validation should be factored out into a separate package.
// ParseQuery parses the request parameters from the URL query string or from the
// form parameters and stores the parsed and validated values in query.
func ParseQuery(r *http.Request, query *Query) error {
if err := r.ParseForm(); err != nil {
return err
}
// Parse the list of fields that need to match and ensure the
// test name is in it.
var ok bool
if query.Match, ok = r.Form["match"]; ok {
if !util.In(types.PRIMARY_KEY_FIELD, query.Match) {
query.Match = append(query.Match, types.PRIMARY_KEY_FIELD)
}
} else {
query.Match = []string{types.PRIMARY_KEY_FIELD}
}
validate := shared.Validation{}
// Parse the query strings. Note Query and RQuery have different types, but the
// same underlying type: map[string][]string
query.Query = validate.QueryFormValue(r, "query")
query.RQuery = validate.QueryFormValue(r, "rquery")
// TODO(stephan) Add range limiting to the validation of limit and offset.
query.Limit = int32(validate.Int64FormValue(r, "limit", 50))
query.Offset = int32(validate.Int64FormValue(r, "offset", 0))
query.Offset = util.MaxInt32(query.Offset, 0)
validate.StrFormValue(r, "metric", &query.Metric, diff.GetDiffMetricIDs(), diff.METRIC_COMBINED)
validate.StrFormValue(r, "sort", &query.Sort, []string{SORT_DESC, SORT_ASC}, SORT_DESC)
// Parse and validate the filter values.
query.FRGBAMin = int32(validate.Int64FormValue(r, "frgbamin", 0))
query.FRGBAMax = int32(validate.Int64FormValue(r, "frgbamax", 255))
query.FDiffMax = float32(validate.Float64FormValue(r, "fdiffmax", -1.0))
// Parse out the issue and patchsets.
query.Patchsets = validate.Int64SliceFormValue(r, "patchsets", nil)
query.Issue = validate.Int64FormValue(r, "issue", 0)
// Check wether any of the validations failed.
if err := validate.Errors(); err != nil {
return err
}
query.BlameGroupID = r.FormValue("blame")
query.Pos = r.FormValue("pos") == "true"
query.Neg = r.FormValue("neg") == "true"
query.Unt = r.FormValue("unt") == "true"
query.Head = r.FormValue("head") == "true"
query.IncludeIgnores = r.FormValue("include") == "true"
query.IncludeMaster = r.FormValue("master") == "true"
// Extract the filter values.
query.FCommitBegin = r.FormValue("fbegin")
query.FCommitEnd = r.FormValue("fend")
query.FGroupTest = r.FormValue("fgrouptest")
query.FRef = r.FormValue("fref") == "true"
// Check if we want diffs.
query.NoDiff = r.FormValue("nodiff") == "true"
return nil
}