| 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 |
| } |