Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions filter_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,52 @@
package rqp

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_Where(t *testing.T) {
t.Run("ErrUnknownMethod", func(t *testing.T) {
filter := Filter{
Key: "id[not]",
Name: "id",
Method: NOT,
Or: true,
}
_, err := filter.Where()
assert.Equal(t, err, ErrUnknownMethod)

filter = Filter{
Key: "id[fake]",
Name: "id",
Method: "fake",
Or: true,
}
_, err = filter.Where()
assert.Equal(t, err, ErrUnknownMethod)
})
}

func Test_Args(t *testing.T) {
t.Run("ErrUnknownMethod", func(t *testing.T) {
filter := Filter{
Key: "id[not]",
Name: "id",
Method: NOT,
Or: true,
Value: "id",
}
_, err := filter.Args()
assert.Equal(t, err, ErrUnknownMethod)

filter = Filter{
Key: "id[fake]",
Name: "id",
Method: "fake",
Or: true,
}
_, err = filter.Args()
assert.Equal(t, err, ErrUnknownMethod)
})
}
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ module github.com/timsolov/rest-query-parser
go 1.13

require (
github.com/josharian/impl v0.0.0-20191119165012-6b9658ad00c7 // indirect
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.5.1
golang.org/x/mod v0.3.0 // indirect
golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 // indirect
)
26 changes: 0 additions & 26 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/josharian/impl v0.0.0-20191119165012-6b9658ad00c7 h1:dhk1N6iuFDo1Lcew31sAQxrh8GVWaw0xu0MWNnMc6ao=
github.com/josharian/impl v0.0.0-20191119165012-6b9658ad00c7/go.mod h1:t4Tr0tn92eq5ISef4cS5plFAMYAqZlAXtgUcKE6y8nw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 h1:vmsb6v0zUdmUlXfwKaYrHPPRCV0lHq/IwNIf0ASGjyQ=
golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
Expand Down
174 changes: 110 additions & 64 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Query struct {
Offset int
Limit int
Sorts []Sort
Filters []*Filter
Filters [][]*Filter

delimiterIN string
delimiterOR string
Expand Down Expand Up @@ -219,9 +219,11 @@ func (q *Query) AddSortBy(by string, desc bool) *Query {
// HaveFilter returns true if request contains some filter
func (q *Query) HaveFilter(name string) bool {

for _, v := range q.Filters {
if v.Name == name {
return true
for _, filters := range q.Filters {
for _, v := range filters {
if v.Name == name {
return true
}
}
}

Expand All @@ -230,27 +232,31 @@ func (q *Query) HaveFilter(name string) bool {

// AddFilter adds a filter to Query
func (q *Query) AddFilter(name string, m Method, value interface{}) *Query {
q.Filters = append(q.Filters, &Filter{
Name: name,
Method: m,
Value: value,
q.Filters = append(q.Filters, []*Filter{
{
Name: name,
Method: m,
Value: value,
},
})
return q
}

// RemoveFilter removes the filter by name
func (q *Query) RemoveFilter(name string) error {

for i, v := range q.Filters {
if v.Name == name {
// safe remove element from slice
if i < len(q.Filters)-1 {
copy(q.Filters[i:], q.Filters[i+1:])
}
q.Filters[len(q.Filters)-1] = nil
q.Filters = q.Filters[:len(q.Filters)-1]
for key, filters := range q.Filters {
for i, v := range filters {
if v.Name == name {
// safe remove element from slice
if i < len(q.Filters[key])-1 {
copy(q.Filters[key][i:], q.Filters[key][i+1:])
}
q.Filters[key][len(q.Filters[key])-1] = nil
q.Filters[key] = q.Filters[key][:len(q.Filters[key])-1]

return nil
return nil
}
}
}

Expand Down Expand Up @@ -289,9 +295,11 @@ func (q *Query) RemoveValidation(NameAndOrTags string) error {
// GetFilter returns filter by name
func (q *Query) GetFilter(name string) (*Filter, error) {

for _, v := range q.Filters {
if v.Name == name {
return v, nil
for _, filters := range q.Filters {
for _, v := range filters {
if v.Name == name {
return v, nil
}
}
}

Expand All @@ -313,9 +321,11 @@ type Replacer map[string]string
func (q *Query) ReplaceNames(r Replacer) {

for name, newname := range r {
for i, v := range q.Filters {
if v.Name == name {
q.Filters[i].Name = newname
for key, filters := range q.Filters {
for i, v := range filters {
if v.Name == name {
q.Filters[key][i].Name = newname
}
}
}
for i, v := range q.Fields {
Expand All @@ -340,55 +350,74 @@ func (q *Query) Where() string {
return ""
}

var whereList []string

var where string
var OR bool = false
var OR bool

for i := 0; i < len(q.Filters); i++ {
filter := q.Filters[i]
for key := range q.Filters {
where = ""
OR = false
if len(q.Filters[key]) == 0 {
continue
}
for i := 0; i < len(q.Filters[key]); i++ {
filter := q.Filters[key][i]

prefix := ""
suffix := ""
prefix := ""
suffix := ""

if filter.Or && !OR {
if i == 0 {
prefix = "("
if filter.Or && !OR {
if i == 0 {
prefix = "("
} else {
prefix = " AND ("
}
OR = true
} else if filter.Or && OR {
prefix = " OR "
// if last element of next element not OR method
if i+1 == len(q.Filters[key]) || (i+1 < len(q.Filters[key]) && !q.Filters[key][i+1].Or) {
suffix = ")"
OR = false
}
} else {
prefix = " AND ("
}
OR = true
} else if filter.Or && OR {
prefix = " OR "
// if last element of next element not OR method
if i+1 == len(q.Filters) || (i+1 < len(q.Filters) && !q.Filters[i+1].Or) {
suffix = ")"
OR = false
if i > 0 {
prefix = " AND "
}
}
} else {
if i > 0 {
prefix = " AND "

if a, err := filter.Where(); err == nil {
where += fmt.Sprintf("%s%s%s", prefix, a, suffix)
} else {
continue
}
}

if a, err := filter.Where(); err == nil {
where += fmt.Sprintf("%s%s%s", prefix, a, suffix)
} else {
continue
if where != "" {
whereList = append(whereList, where)
}
}

if len(whereList) == 0 {
return ""
}

return where
return strings.Join(whereList, " AND ")
}

// WHERE returns list of filters for WHERE SQL statement with `WHERE` word
// return example: `WHERE id > 0 AND email LIKE 'some@email.com'`
func (q *Query) WHERE() string {

if len(q.Filters) == 0 {
return ""
}

return " WHERE " + q.Where()
where := q.Where()
if where == "" {
return ""
}

return " WHERE " + where
}

// Args returns slice of arguments for WHERE statement
Expand All @@ -400,14 +429,20 @@ func (q *Query) Args() []interface{} {
return args
}

for i := 0; i < len(q.Filters); i++ {
filter := q.Filters[i]

if a, err := filter.Args(); err == nil {
args = append(args, a...)
} else {
for key := range q.Filters {
if len(q.Filters[key]) == 0 {
continue
}

for i := 0; i < len(q.Filters[key]); i++ {
filter := q.Filters[key][i]

if a, err := filter.Args(); err == nil {
args = append(args, a...)
} else {
continue
}
}
}

return args
Expand Down Expand Up @@ -503,11 +538,14 @@ func (q *Query) Parse() (err error) {
err = q.parseSort(values, q.validations[low])
delete(requiredNames, low)
default:
if len(values) != 1 {
if len(values) == 0 {
return errors.Wrap(ErrBadFormat, key)
}
if err = q.parseFilter(key, values[0]); err != nil {
return err

for i := 0; i < len(values); i++ {
if err = q.parseFilter(key, values[i]); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -576,6 +614,8 @@ func (q *Query) parseFilter(key, value string) error {
return errors.Wrap(ErrEmptyValue, key)
}

filters := make([]*Filter, 0)

if strings.Contains(value, q.delimiterOR) { // OR multiple filter
parts := strings.Split(value, q.delimiterOR)
for i, v := range parts {
Expand Down Expand Up @@ -609,7 +649,7 @@ func (q *Query) parseFilter(key, value string) error {
// set OR
filter.Or = true

q.Filters = append(q.Filters, filter)
filters = append(filters, filter)
}
} else { // Single filter
filter, err := newFilter(key, value, q.delimiterIN, q.validations)
Expand All @@ -623,20 +663,26 @@ func (q *Query) parseFilter(key, value string) error {
return errors.Wrap(err, key)
}

q.Filters = append(q.Filters, filter)
filters = append(filters, filter)
}

q.Filters = append(q.Filters, filters)

return nil
}

// clean the filters slice
func (q *Query) cleanFilters() {
if len(q.Filters) > 0 {
for i := range q.Filters {
q.Filters[i] = nil
for key := range q.Filters {
for i := range q.Filters[key] {
q.Filters[key][i] = nil
}
q.Filters[key] = nil
}
q.Filters = nil
}
q.Filters = make([][]*Filter, 0)
}

func (q *Query) parseSort(value []string, validate ValidationFunc) error {
Expand Down
Loading