package qs import ( "errors" "net/http" "net/url" "regexp" ) // Query error. var ( ErrInvalidSort = errors.New("invalid sort") ErrTooManySorts = errors.New("too many sorts") ) var sortRegexp = regexp.MustCompile("^([A-z0-9]+) (asc|desc)$") // ReadSortsOptions configures the behaviour of ReadSorts. type ReadSortsOptions struct { Key string // Query string key. The default value is "sort" MaxSorts int // If this is > 0, a maximum number of sorts is imposed } // Sort represents a sort order for, most likely, a database query. type Sort struct { Field string `json:"field"` // Field by which to sort. Direction string `json:"direction"` // Direction in which to sort, namely asc or desc. } // Sorts is a slice of Sort structs. type Sorts []Sort // Field returns a new Sorts slice containing only sorts for the specified field. // The original order of sorts is preserved. func (sorts Sorts) Field(field string) Sorts { ff := Sorts{} for _, sort := range sorts { if sort.Field == field { ff = append(ff, sort) } } return ff } // Fields returns a new Sorts slice containing sorts for any of the specified fields. // The original order of sorts is preserved. func (sorts Sorts) Fields(fields ...string) Sorts { ff := Sorts{} for _, sort := range sorts { for _, field := range fields { if sort.Field == field { ff = append(ff, sort) } } } return ff } // HasField returns true if the Sorts slice includes any sorts for the specified field. func (sorts Sorts) HasField(field string) bool { for _, sort := range sorts { if sort.Field == field { return true } } return false } // ReadRequestSorts parses a request's query string into a slice of sorts. // This function returns nil if no sorts are found. func ReadRequestSorts(req *http.Request, opt *ReadSortsOptions) (Sorts, error) { return ReadSorts(req.URL.Query(), opt) } // ReadSorts parses URL values into a slice of sorts. // This function returns nil if no sorts are found. func ReadSorts(values url.Values, opt *ReadSortsOptions) (Sorts, error) { opt = initSortsOptions(opt) if !values.Has(opt.Key) { return nil, nil } if opt.MaxSorts > 0 && len(values[opt.Key]) > opt.MaxSorts { return nil, ErrTooManySorts } sorts := []Sort{} for _, sortStr := range values[opt.Key] { match := sortRegexp.FindStringSubmatch(sortStr) if match == nil { return nil, ErrInvalidSort } sort := Sort{ Field: match[1], Direction: match[2], } sorts = append(sorts, sort) } return sorts, nil } // ReadStringSorts parses a query string literal into a slice of sorts. // This function returns nil if no sorts are found. func ReadStringSorts(qs string, opt *ReadSortsOptions) (Sorts, error) { values, err := url.ParseQuery(qs) if err != nil { return nil, err } return ReadSorts(values, opt) } func initSortsOptions(opt *ReadSortsOptions) *ReadSortsOptions { def := &ReadSortsOptions{ Key: "sort", } if opt != nil { if len(opt.Key) > 0 { def.Key = opt.Key } if opt.MaxSorts > def.MaxSorts { def.MaxSorts = opt.MaxSorts } } return def }