223 lines
5.7 KiB
Go
223 lines
5.7 KiB
Go
package qs
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Query error.
|
|
var (
|
|
ErrInvalidFilter = errors.New("invalid filter")
|
|
ErrTooManyFilters = errors.New("too many filters")
|
|
)
|
|
|
|
var (
|
|
filterRegexp = regexp.MustCompile("^([A-z0-9]+) (eq|neq|gt|gte|lt|lte|in|not in|like|not like) (.+)$")
|
|
|
|
sliceSeparator = ","
|
|
)
|
|
|
|
// Filter represents a filter as used in, most likely, a database query.
|
|
type Filter struct {
|
|
Field string `json:"field"` // Field to filter on.
|
|
Operator string `json:"operator"` // Filter operator, e.g. eq, gt...
|
|
Value string `json:"value"` // Value to filter by.
|
|
}
|
|
|
|
// BoolSlice retrieves the filter value as a slice of bools.
|
|
func (filter Filter) BoolSlice() ([]bool, error) {
|
|
values := strings.Split(filter.Value, sliceSeparator)
|
|
bools := []bool{}
|
|
for _, value := range values {
|
|
boolValue, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bools = append(bools, boolValue)
|
|
}
|
|
return bools, nil
|
|
}
|
|
|
|
// BoolValue retrieves the filter value as a bool.
|
|
func (filter Filter) BoolValue() (bool, error) {
|
|
return strconv.ParseBool(filter.Value)
|
|
}
|
|
|
|
// Float32Slice retrieves the filter value as a slice of float32s.
|
|
func (filter Filter) Float32Slice() ([]float32, error) {
|
|
values := strings.Split(filter.Value, sliceSeparator)
|
|
floats := []float32{}
|
|
for _, value := range values {
|
|
floatValue, err := strconv.ParseFloat(value, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
floats = append(floats, float32(floatValue))
|
|
}
|
|
return floats, nil
|
|
}
|
|
|
|
// Float32Value retrieves the filter value as a float32.
|
|
func (filter Filter) Float32Value() (float32, error) {
|
|
value, err := strconv.ParseFloat(filter.Value, 32)
|
|
return float32(value), err
|
|
}
|
|
|
|
// Float64Slice retrieves the filter value as a slice of float64s.
|
|
func (filter Filter) Float64Slice() ([]float64, error) {
|
|
values := strings.Split(filter.Value, sliceSeparator)
|
|
floats := []float64{}
|
|
for _, value := range values {
|
|
floatValue, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
floats = append(floats, float64(floatValue))
|
|
}
|
|
return floats, nil
|
|
}
|
|
|
|
// Float64Value retrieves the filter value as a float64.
|
|
func (filter Filter) Float64Value() (float64, error) {
|
|
return strconv.ParseFloat(filter.Value, 64)
|
|
}
|
|
|
|
// IntSlice retrieves the filter value as a slice of ints.
|
|
func (filter Filter) IntSlice() ([]int, error) {
|
|
values := strings.Split(filter.Value, sliceSeparator)
|
|
ints := []int{}
|
|
for _, value := range values {
|
|
intValue, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ints = append(ints, int(intValue))
|
|
}
|
|
return ints, nil
|
|
}
|
|
|
|
// IntValue retrieves the filter value as an int.
|
|
func (filter Filter) IntValue() (int, error) {
|
|
return strconv.Atoi(filter.Value)
|
|
}
|
|
|
|
// StringSlice retrieves the filter value as a slice of strings.
|
|
func (filter Filter) StringSlice() ([]string, error) {
|
|
strings := strings.Split(filter.Value, sliceSeparator)
|
|
return strings, nil
|
|
}
|
|
|
|
// Filters is a slice of Filter structs.
|
|
type Filters []Filter
|
|
|
|
// Field returns a new Filters slice containing only filters for the specified field.
|
|
// The original order of filters is preserved.
|
|
func (filters Filters) Field(field string) Filters {
|
|
ff := Filters{}
|
|
for _, filter := range filters {
|
|
if filter.Field == field {
|
|
ff = append(ff, filter)
|
|
}
|
|
}
|
|
return ff
|
|
}
|
|
|
|
// Fields returns a new Filters slice containing filters for any of the specified fields.
|
|
// The original order of filters is preserved.
|
|
func (filters Filters) Fields(fields ...string) Filters {
|
|
ff := Filters{}
|
|
for _, filter := range filters {
|
|
for _, field := range fields {
|
|
if filter.Field == field {
|
|
ff = append(ff, filter)
|
|
}
|
|
}
|
|
}
|
|
return ff
|
|
}
|
|
|
|
// HasField returns true if the Filters slice includes any filters for the specified field.
|
|
func (filters Filters) HasField(field string) bool {
|
|
for _, filter := range filters {
|
|
if filter.Field == field {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ReadFiltersOptions configures the behaviour of ReadFilters.
|
|
type ReadFiltersOptions struct {
|
|
Key string // Query string key. The default value is "filter"
|
|
MaxFilters int // If this is > 0, a maximum number of filters is imposed
|
|
}
|
|
|
|
// ReadFilters parses URL values into a slice of filters.
|
|
// This function returns nil if no filters are found.
|
|
func ReadFilters(values url.Values, opt *ReadFiltersOptions) (Filters, error) {
|
|
opt = initFiltersOptions(opt)
|
|
|
|
if !values.Has(opt.Key) {
|
|
return nil, nil
|
|
}
|
|
|
|
if opt.MaxFilters > 0 && len(values[opt.Key]) > opt.MaxFilters {
|
|
return nil, ErrTooManyFilters
|
|
}
|
|
|
|
filters := Filters{}
|
|
for _, filterStr := range values[opt.Key] {
|
|
match := filterRegexp.FindStringSubmatch(filterStr)
|
|
if match == nil {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
|
|
filter := Filter{
|
|
Field: match[1],
|
|
Operator: match[2],
|
|
Value: match[3],
|
|
}
|
|
filters = append(filters, filter)
|
|
}
|
|
|
|
return filters, nil
|
|
}
|
|
|
|
// ReadRequestFilters parses a request's query string into a slice of filters.
|
|
// This function returns nil if no filters are found.
|
|
func ReadRequestFilters(req *http.Request, opt *ReadFiltersOptions) (Filters, error) {
|
|
return ReadFilters(req.URL.Query(), opt)
|
|
}
|
|
|
|
// ReadStringFilters parses a query string literal into a slice of filters.
|
|
// This function returns nil if no filters are found.
|
|
func ReadStringFilters(qs string, opt *ReadFiltersOptions) (Filters, error) {
|
|
values, err := url.ParseQuery(qs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ReadFilters(values, opt)
|
|
}
|
|
|
|
func initFiltersOptions(opt *ReadFiltersOptions) *ReadFiltersOptions {
|
|
def := &ReadFiltersOptions{
|
|
Key: "filter",
|
|
}
|
|
|
|
if opt != nil {
|
|
if len(opt.Key) > 0 {
|
|
def.Key = opt.Key
|
|
}
|
|
|
|
if opt.MaxFilters > def.MaxFilters {
|
|
def.MaxFilters = opt.MaxFilters
|
|
}
|
|
}
|
|
|
|
return def
|
|
}
|