filterparams 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +8 -0
- data/README.md +200 -0
- data/lib/filterparams.rb +1 -0
- data/lib/filterparams/api.rb +49 -0
- data/lib/filterparams/binding.rb +2 -0
- data/lib/filterparams/binding/binding_parser.rb +47 -0
- data/lib/filterparams/binding/binding_transform.rb +46 -0
- data/lib/filterparams/obj.rb +6 -0
- data/lib/filterparams/obj/and.rb +9 -0
- data/lib/filterparams/obj/binding_operation.rb +14 -0
- data/lib/filterparams/obj/not.rb +13 -0
- data/lib/filterparams/obj/or.rb +9 -0
- data/lib/filterparams/obj/order.rb +12 -0
- data/lib/filterparams/obj/parameter.rb +28 -0
- data/lib/filterparams/obj/query.rb +40 -0
- data/lib/filterparams/order_extractor.rb +42 -0
- data/lib/filterparams/param_extractor.rb +60 -0
- metadata +378 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eccbf86937a65862760d9b9856f638b9fbcfba53
|
4
|
+
data.tar.gz: 9421ba18a6af48111c5da4983d1339afde31dc61
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc6bcb9a1600115563e86a1da14c7238fe284284875c90d624a8982428c01fa9ddc040e78a3b51bb09f2f81042d6338b2de7502814ba63040995dc3b7d448add
|
7
|
+
data.tar.gz: 9823903da6ba5dfea2c80a6fc3a00a54778dc361d7bf955930df967f408c5e67e9d5b6a601c48e8ce4dd8872893de12cd8b9f27fb07b3382b62eac12c4f2f8f6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2016 Christoph Brand
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
|
+
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# Ruby Filterparams #
|
2
|
+
|
3
|
+
Filterparams is a library for specifying filters through a REST API
|
4
|
+
on collection resources on top of HTTP. It provides the capability
|
5
|
+
to specify SQL-like syntax on top of query parameters.
|
6
|
+
|
7
|
+
Due to it's intended use of building [JSONAPI](http://jsonapi.org/)
|
8
|
+
libraries with this definition, it is compatible with the definition
|
9
|
+
and encapsulates it's syntax through the prefixed `filter` parameters.
|
10
|
+
|
11
|
+
## Installation ##
|
12
|
+
|
13
|
+
The library is available through the `filterparams` gem on [RubyGems](https://rubygems.org/gems/filterparams)
|
14
|
+
and thus can be installed through the command line or by adding the package to
|
15
|
+
your Gemfile.
|
16
|
+
|
17
|
+
```
|
18
|
+
gem install filterparams
|
19
|
+
```
|
20
|
+
|
21
|
+
## Example ##
|
22
|
+
|
23
|
+
Given the URL (non URL escaped for better readability):
|
24
|
+
|
25
|
+
```
|
26
|
+
/users?filter[param][name][like][no_brand_name]=doe&filter[param][first_name]=doe%&filter[binding]=(!no_brand_name&first_name)&filter[order]=name&filter[order]=desc(first_name)
|
27
|
+
```
|
28
|
+
|
29
|
+
It can be parsed with the following code:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'uri'
|
33
|
+
require 'cgi'
|
34
|
+
require 'filterparams'
|
35
|
+
|
36
|
+
url = 'http://www.example.com/users?' +
|
37
|
+
'filter[param][name][like][no_brand_name]=doe' +
|
38
|
+
'&filter[param][first_name]=doe%' +
|
39
|
+
'&filter[binding]=%28%21no_brand_name%26first_name%29' +
|
40
|
+
'&filter[order]=name&filter[order]=desc(first_name)'
|
41
|
+
|
42
|
+
data = CGI.parse(URI.parse(url).query)
|
43
|
+
|
44
|
+
Filterparams::extract_query(data)
|
45
|
+
```
|
46
|
+
|
47
|
+
This would result into a query instance being produced with the
|
48
|
+
following data:
|
49
|
+
|
50
|
+
```
|
51
|
+
#<Filterparams::Query:0x007ff78b1a51c8
|
52
|
+
@filters=
|
53
|
+
#<Filterparams::And:0x007ff78b1a57e0
|
54
|
+
@left=
|
55
|
+
#<Filterparams::Not:0x007ff78b1a6ed8
|
56
|
+
@inner=
|
57
|
+
#<Filterparams::Parameter:0x007ff78b1ee940
|
58
|
+
@alias="no_brand_name",
|
59
|
+
@filter="like",
|
60
|
+
@name="name",
|
61
|
+
@value="doe">>,
|
62
|
+
@right=
|
63
|
+
#<Filterparams::Parameter:0x007ff78b1ee918
|
64
|
+
@alias=nil,
|
65
|
+
@filter=nil,
|
66
|
+
@name="first_name",
|
67
|
+
@value="doe%">>,
|
68
|
+
@orders=
|
69
|
+
[#<Filterparams::Order:0x007ff78b1ee5f8 @direction="asc", @name="name">,
|
70
|
+
#<Filterparams::Order:0x007ff78b1ee3a0
|
71
|
+
@direction="desc",
|
72
|
+
@name="first_name">]>
|
73
|
+
```
|
74
|
+
|
75
|
+
The orders can be accessed through the `.orders` method and the filter
|
76
|
+
binding through `.filters`.
|
77
|
+
|
78
|
+
## Syntax ##
|
79
|
+
|
80
|
+
All arguments must be prefixed by "filter". It is possible to
|
81
|
+
query for specific data with filters, apply orders to the result
|
82
|
+
and to combine filters through AND, NOT and OR bindings.
|
83
|
+
|
84
|
+
The syntax builds under the filter parameter a virtual object.
|
85
|
+
The keys of the object are simulated through specifying `[{key}]`
|
86
|
+
in the passed query parameter. Thus `filter[param]` would point
|
87
|
+
to the param key in the filter object.
|
88
|
+
|
89
|
+
### Filter specification ###
|
90
|
+
|
91
|
+
The solution supports to query data through the `param` subkey.
|
92
|
+
|
93
|
+
```
|
94
|
+
filter[param][{parameter_name}][{operation}][{alias}] = {to_query_value}
|
95
|
+
```
|
96
|
+
|
97
|
+
The `operation` and `alias` parameters may be omitted. If no
|
98
|
+
`alias` is provided the given parameter name is used for it.
|
99
|
+
If no `operation` is given, the default one is used (in the
|
100
|
+
example this would be equal).
|
101
|
+
|
102
|
+
Example:
|
103
|
+
```
|
104
|
+
filter[param][phone_number][like]=001%
|
105
|
+
```
|
106
|
+
|
107
|
+
This would add a filter to all phone numbers which start with "001".
|
108
|
+
|
109
|
+
### Filter binding ###
|
110
|
+
|
111
|
+
Per default all filters are combined through AND clauses.
|
112
|
+
You can change that by specifying the `filter[binding]` argument.
|
113
|
+
|
114
|
+
This is where the aliases which you can define come into place.
|
115
|
+
The binding provides means to combine filters with AND and OR.
|
116
|
+
Also you are able to negate filters here.
|
117
|
+
|
118
|
+
The filters are addressed by their alias or name, if no alias is
|
119
|
+
provided.
|
120
|
+
|
121
|
+
If you have a filter `search_for_name`, `search_for_phone_number`
|
122
|
+
and `search_for_account_number` defined you can say
|
123
|
+
`search_for_name OR NOT search_for_number AND search_for_account_number`
|
124
|
+
by specifying the following filter:
|
125
|
+
|
126
|
+
```
|
127
|
+
filter[binding]=search_for_name|(!search_for_phone_number&search_for_account_number)
|
128
|
+
```
|
129
|
+
|
130
|
+
Even though the brackets are useless here, you can use them in
|
131
|
+
more complex filters.
|
132
|
+
|
133
|
+
The following table summarizes the possible configuration options:
|
134
|
+
<table>
|
135
|
+
<thead>
|
136
|
+
<tr>
|
137
|
+
<th>Type</th>
|
138
|
+
<th>Symbol</th>
|
139
|
+
<th>Example</th>
|
140
|
+
</tr>
|
141
|
+
</thead>
|
142
|
+
<tbody>
|
143
|
+
<tr>
|
144
|
+
<td>AND</td>
|
145
|
+
<td>&</td>
|
146
|
+
<td>a&b</td>
|
147
|
+
</tr>
|
148
|
+
<tr>
|
149
|
+
<td>OR</td>
|
150
|
+
<td>|</td>
|
151
|
+
<td>a|b</td>
|
152
|
+
</tr>
|
153
|
+
<tr>
|
154
|
+
<td>NOT</td>
|
155
|
+
<td>!</td>
|
156
|
+
<td>!a</td>
|
157
|
+
</tr>
|
158
|
+
<tr>
|
159
|
+
<td>Bracket</td>
|
160
|
+
<td>()</td>
|
161
|
+
<td>(a|b)&c</td>
|
162
|
+
</tr>
|
163
|
+
</tbody>
|
164
|
+
</table>
|
165
|
+
|
166
|
+
### Ordering ###
|
167
|
+
|
168
|
+
To specify a sort order of the results the `filter[order]` parameter
|
169
|
+
may be used. The value can be specified multiple times. To add
|
170
|
+
ordering you have to provide the name of the parameter which should
|
171
|
+
be ordered, not its alias!
|
172
|
+
|
173
|
+
If you want to order by `name`, `first_name` and in reverse order
|
174
|
+
`balance` you can do so by specifying the following query url
|
175
|
+
parameters:
|
176
|
+
|
177
|
+
```
|
178
|
+
filter[order]=name&filter[order]=first_name&filter[order]=desc(balance)
|
179
|
+
```
|
180
|
+
|
181
|
+
As you can see the `desc()` definition can be used to indicate
|
182
|
+
reverse ordering.
|
183
|
+
|
184
|
+
## License ##
|
185
|
+
|
186
|
+
The project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
187
|
+
|
188
|
+
## Used libraries ##
|
189
|
+
|
190
|
+
For parsing the query parameters the [`parslet`](https://kschiess.github.io/parslet/)
|
191
|
+
library is used. It is released under the [MIT License](https://github.com/kschiess/parslet/blob/master/LICENSE).
|
192
|
+
|
193
|
+
## Other Languages ##
|
194
|
+
|
195
|
+
This is a list of projects implementing the same API for other languages.
|
196
|
+
Currently this list only has one entry.
|
197
|
+
|
198
|
+
- Go - [go-filterparams](https://github.com/cbrand/go-filterparams)
|
199
|
+
- Python - [python-filterparams](https://github.com/cbrand/python-filterparams)
|
200
|
+
- JavaScript (Client) - [filterparams](https://github.com/cbrand/js-filterparams-client)
|
data/lib/filterparams.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './filterparams/api'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative './obj'
|
2
|
+
require_relative './param_extractor'
|
3
|
+
require_relative './order_extractor'
|
4
|
+
require_relative './binding'
|
5
|
+
|
6
|
+
module Filterparams
|
7
|
+
class << self
|
8
|
+
def extract_query(data)
|
9
|
+
params = extract_params_hash data
|
10
|
+
orders = extract_orders data
|
11
|
+
filter = if data.key? FILTER_BINDING_KEY
|
12
|
+
extract_filter data[FILTER_BINDING_KEY], params
|
13
|
+
else
|
14
|
+
auto_filter_for params
|
15
|
+
end
|
16
|
+
|
17
|
+
query = Filterparams::Query.new
|
18
|
+
query.filter(filter).add_order_obj(*orders)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
FILTER_BINDING_KEY = 'filter[binding]'.freeze
|
24
|
+
ORDER_KEY = 'filter[order]'.freeze
|
25
|
+
|
26
|
+
def extract_filter(filter_string, params)
|
27
|
+
if filter_string.is_a? Array
|
28
|
+
filter_string = filter_string[0]
|
29
|
+
end
|
30
|
+
|
31
|
+
parsed = Filterparams::BindingParser.new.parse(filter_string)
|
32
|
+
Filterparams::BindingTransform.new(params).apply(parsed)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_params_hash(params)
|
36
|
+
Filterparams::ParamExtractor.new(params).params_hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_orders(params)
|
40
|
+
Filterparams::OrderExtractor.new(params[ORDER_KEY] || []).orders
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_filter_for(params)
|
44
|
+
params.values.inject do |left, right|
|
45
|
+
Filterparams::And.new(left, right)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module Filterparams
|
4
|
+
class BindingParser < Parslet::Parser
|
5
|
+
rule(:space) { match('\s').repeat(1) }
|
6
|
+
rule(:space?) { space.maybe }
|
7
|
+
|
8
|
+
rule(:lparen) { str('(') >> space? }
|
9
|
+
rule(:rparen) { str(')') >> space? }
|
10
|
+
|
11
|
+
rule(:and_operator) { str('&') }
|
12
|
+
rule(:or_operator) { str('|') }
|
13
|
+
|
14
|
+
rule(:parameter) { match('[\w\-\_]').repeat(1).as(:parameter) }
|
15
|
+
|
16
|
+
rule(:bracket) do
|
17
|
+
lparen >> space? >> clause.as(:clause) >> space? >> rparen
|
18
|
+
end
|
19
|
+
|
20
|
+
rule(:and_clause_element) { parameter | not_operation | bracket }
|
21
|
+
rule(:and_clause) do
|
22
|
+
and_clause_element.as(:left) >> space? >>
|
23
|
+
and_operator.as(:operator) >> space? >>
|
24
|
+
(and_clause | and_clause_element).as(:right)
|
25
|
+
end
|
26
|
+
rule(:or_clause_element) do
|
27
|
+
and_clause | parameter | not_operation | bracket
|
28
|
+
end
|
29
|
+
rule(:or_clause) do
|
30
|
+
or_clause_element.as(:left) >> space? >>
|
31
|
+
or_operator.as(:operator) >> space? >>
|
32
|
+
clause.as(:right)
|
33
|
+
end
|
34
|
+
rule(:inner_not) { bracket | parameter | not_operation }
|
35
|
+
rule(:not_operation) do
|
36
|
+
str('!').as(:operator) >> space? >> inner_not.as(:clause)
|
37
|
+
end
|
38
|
+
|
39
|
+
rule(:clause) do
|
40
|
+
or_clause | and_clause | parameter | bracket | not_operation
|
41
|
+
end
|
42
|
+
|
43
|
+
rule(:binding) { space? >> clause >> space? }
|
44
|
+
|
45
|
+
root(:binding)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require_relative '../obj'
|
3
|
+
|
4
|
+
module Filterparams
|
5
|
+
class BindingTransform < Parslet::Transform
|
6
|
+
rule(operator: '&', left: simple(:left), right: simple(:right)) do
|
7
|
+
Filterparams::And.new(left, right)
|
8
|
+
end
|
9
|
+
rule(operator: '|', left: simple(:left), right: simple(:right)) do
|
10
|
+
Filterparams::Or.new(left, right)
|
11
|
+
end
|
12
|
+
rule(operator: '!', clause: simple(:clause)) do
|
13
|
+
Filterparams::Not.new(clause)
|
14
|
+
end
|
15
|
+
rule(clause: simple(:clause)) do
|
16
|
+
clause
|
17
|
+
end
|
18
|
+
rule(parameter: simple(:param_name)) do |dictionary|
|
19
|
+
param_with(dictionary[:param_name])
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(params)
|
23
|
+
@params = params
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def param_with(name)
|
30
|
+
name = name.to_s
|
31
|
+
if @params[name].nil?
|
32
|
+
raise StandardError, "Param with name #{name} does not exist"
|
33
|
+
end
|
34
|
+
@params[name]
|
35
|
+
end
|
36
|
+
|
37
|
+
def call_on_match(bindings, block)
|
38
|
+
if block
|
39
|
+
return instance_exec(bindings, &block) if block.arity == 1
|
40
|
+
|
41
|
+
context = Context.new(bindings)
|
42
|
+
return context.instance_eval(&block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative './binding_operation'
|
2
|
+
|
3
|
+
module Filterparams
|
4
|
+
class Order < Filterparams::BindingOperation
|
5
|
+
attr_accessor :name, :direction
|
6
|
+
|
7
|
+
def initialize(name, is_desc = false)
|
8
|
+
self.name = name
|
9
|
+
self.direction = is_desc ? 'desc' : 'asc'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Filterparams
|
2
|
+
class Parameter
|
3
|
+
attr_accessor :name, :filter, :value, :alias
|
4
|
+
|
5
|
+
def initialize(name, params = {})
|
6
|
+
self.name = name
|
7
|
+
self.filter = params[:filter] || nil
|
8
|
+
self.value = params[:value] || nil
|
9
|
+
self.alias = params[:alias] || nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def identification
|
13
|
+
if self.alias.nil?
|
14
|
+
name
|
15
|
+
else
|
16
|
+
self.alias
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def equal?(other)
|
21
|
+
if other.is_a? Parameter
|
22
|
+
name == other.name && self.alias == other.alias
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative './and'
|
2
|
+
require_relative './order'
|
3
|
+
|
4
|
+
module Filterparams
|
5
|
+
class Query
|
6
|
+
attr_accessor :filters, :orders
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.filters = nil
|
10
|
+
self.orders = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def clone
|
14
|
+
query = Filterparams::Query.new
|
15
|
+
query.filters = filters
|
16
|
+
query.orders.push(*orders)
|
17
|
+
query
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_order(name, descending = false)
|
21
|
+
add_order_obj Filterparams::Order.new(name, descending)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_order_obj(*order_obj)
|
25
|
+
query = clone
|
26
|
+
query.orders.push(*order_obj)
|
27
|
+
query
|
28
|
+
end
|
29
|
+
|
30
|
+
def filter(filter_obj)
|
31
|
+
query = clone
|
32
|
+
query.filters = if query.filters.nil?
|
33
|
+
filter_obj
|
34
|
+
else
|
35
|
+
Filterparams::And.new(query.filters, filter_obj)
|
36
|
+
end
|
37
|
+
query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative './obj/order'
|
2
|
+
|
3
|
+
module Filterparams
|
4
|
+
class OrderExtractor
|
5
|
+
ORDER_MATCHER = /(
|
6
|
+
(?<direction>\w+)\((?<directed_param>\w+)\)|
|
7
|
+
(?<param>\w+)
|
8
|
+
)/x
|
9
|
+
|
10
|
+
def initialize(orders)
|
11
|
+
orders = [orders] unless orders.is_a? Array
|
12
|
+
|
13
|
+
@orders = orders
|
14
|
+
end
|
15
|
+
|
16
|
+
def orders
|
17
|
+
@order_objs ||= extract_orders
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def extract_orders
|
23
|
+
@orders.map { |order| generate_order(order) }
|
24
|
+
.reject(&:nil?)
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_order(order_string)
|
28
|
+
match = ORDER_MATCHER.match(order_string)
|
29
|
+
return nil if match.nil?
|
30
|
+
|
31
|
+
if match['param'].nil?
|
32
|
+
direction = match['direction']
|
33
|
+
name = match['directed_param']
|
34
|
+
else
|
35
|
+
direction = 'asc'
|
36
|
+
name = match['param']
|
37
|
+
end
|
38
|
+
|
39
|
+
Filterparams::Order.new(name, direction == 'desc')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative './obj'
|
2
|
+
|
3
|
+
module Filterparams
|
4
|
+
class ParamExtractor
|
5
|
+
FILTER_MATCHES = /filter\[param\]\[(?<name>\w+)\]
|
6
|
+
(\[(?<filter>\w+)\](\[(?<alias>\w+)\])?)?/x
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
match_hashes.map { |map| generate_param map }
|
14
|
+
end
|
15
|
+
|
16
|
+
def params_hash
|
17
|
+
filter_args = params.map do |parameter|
|
18
|
+
[parameter.identification, parameter]
|
19
|
+
end.flatten
|
20
|
+
Hash[*filter_args]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def generate_param(param_hash)
|
26
|
+
Filterparams::Parameter.new(param_hash[:name], param_hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
def match_hashes
|
30
|
+
matches = @params.map do |key, value|
|
31
|
+
if value.is_a? Array
|
32
|
+
value = if value.size > 0
|
33
|
+
value[0]
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
{
|
40
|
+
match: FILTER_MATCHES.match(key),
|
41
|
+
value: value
|
42
|
+
}
|
43
|
+
end
|
44
|
+
matches = matches.reject { |map| map[:match].nil? }
|
45
|
+
matches.map do |map|
|
46
|
+
create_match_lookup(map)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_match_lookup(map)
|
51
|
+
match = map[:match]
|
52
|
+
{
|
53
|
+
name: match['name'],
|
54
|
+
value: map[:value],
|
55
|
+
filter: match['filter'],
|
56
|
+
alias: match['alias']
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,378 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: filterparams
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Brand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.4'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov-json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov-rcov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-checkstyle_formatter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ci_reporter_rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: parslet
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.6'
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 1.6.0
|
121
|
+
type: :runtime
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '1.6'
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 1.6.0
|
131
|
+
description: |
|
132
|
+
# Ruby Filterparams #
|
133
|
+
|
134
|
+
Filterparams is a library for specifying filters through a REST API
|
135
|
+
on collection resources on top of HTTP. It provides the capability
|
136
|
+
to specify SQL-like syntax on top of query parameters.
|
137
|
+
|
138
|
+
Due to it's intended use of building [JSONAPI](http://jsonapi.org/)
|
139
|
+
libraries with this definition, it is compatible with the definition
|
140
|
+
and encapsulates it's syntax through the prefixed `filter` parameters.
|
141
|
+
|
142
|
+
## Installation ##
|
143
|
+
|
144
|
+
The library is available through the `filterparams` gem on [RubyGems](https://rubygems.org/gems/filterparams)
|
145
|
+
and thus can be installed through the command line or by adding the package to
|
146
|
+
your Gemfile.
|
147
|
+
|
148
|
+
```
|
149
|
+
gem install filterparams
|
150
|
+
```
|
151
|
+
|
152
|
+
## Example ##
|
153
|
+
|
154
|
+
Given the URL (non URL escaped for better readability):
|
155
|
+
|
156
|
+
```
|
157
|
+
/users?filter[param][name][like][no_brand_name]=doe&filter[param][first_name]=doe%&filter[binding]=(!no_brand_name&first_name)&filter[order]=name&filter[order]=desc(first_name)
|
158
|
+
```
|
159
|
+
|
160
|
+
It can be parsed with the following code:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
require 'uri'
|
164
|
+
require 'cgi'
|
165
|
+
require 'filterparams'
|
166
|
+
|
167
|
+
url = 'http://www.example.com/users?' +
|
168
|
+
'filter[param][name][like][no_brand_name]=doe' +
|
169
|
+
'&filter[param][first_name]=doe%' +
|
170
|
+
'&filter[binding]=%28%21no_brand_name%26first_name%29' +
|
171
|
+
'&filter[order]=name&filter[order]=desc(first_name)'
|
172
|
+
|
173
|
+
data = CGI.parse(URI.parse(url).query)
|
174
|
+
|
175
|
+
Filterparams::extract_query(data)
|
176
|
+
```
|
177
|
+
|
178
|
+
This would result into a query instance being produced with the
|
179
|
+
following data:
|
180
|
+
|
181
|
+
```
|
182
|
+
#<Filterparams::Query:0x007ff78b1a51c8
|
183
|
+
@filters=
|
184
|
+
#<Filterparams::And:0x007ff78b1a57e0
|
185
|
+
@left=
|
186
|
+
#<Filterparams::Not:0x007ff78b1a6ed8
|
187
|
+
@inner=
|
188
|
+
#<Filterparams::Parameter:0x007ff78b1ee940
|
189
|
+
@alias="no_brand_name",
|
190
|
+
@filter="like",
|
191
|
+
@name="name",
|
192
|
+
@value="doe">>,
|
193
|
+
@right=
|
194
|
+
#<Filterparams::Parameter:0x007ff78b1ee918
|
195
|
+
@alias=nil,
|
196
|
+
@filter=nil,
|
197
|
+
@name="first_name",
|
198
|
+
@value="doe%">>,
|
199
|
+
@orders=
|
200
|
+
[#<Filterparams::Order:0x007ff78b1ee5f8 @direction="asc", @name="name">,
|
201
|
+
#<Filterparams::Order:0x007ff78b1ee3a0
|
202
|
+
@direction="desc",
|
203
|
+
@name="first_name">]>
|
204
|
+
```
|
205
|
+
|
206
|
+
The orders can be accessed through the `.orders` method and the filter
|
207
|
+
binding through `.filters`.
|
208
|
+
|
209
|
+
## Syntax ##
|
210
|
+
|
211
|
+
All arguments must be prefixed by "filter". It is possible to
|
212
|
+
query for specific data with filters, apply orders to the result
|
213
|
+
and to combine filters through AND, NOT and OR bindings.
|
214
|
+
|
215
|
+
The syntax builds under the filter parameter a virtual object.
|
216
|
+
The keys of the object are simulated through specifying `[{key}]`
|
217
|
+
in the passed query parameter. Thus `filter[param]` would point
|
218
|
+
to the param key in the filter object.
|
219
|
+
|
220
|
+
### Filter specification ###
|
221
|
+
|
222
|
+
The solution supports to query data through the `param` subkey.
|
223
|
+
|
224
|
+
```
|
225
|
+
filter[param][{parameter_name}][{operation}][{alias}] = {to_query_value}
|
226
|
+
```
|
227
|
+
|
228
|
+
The `operation` and `alias` parameters may be omitted. If no
|
229
|
+
`alias` is provided the given parameter name is used for it.
|
230
|
+
If no `operation` is given, the default one is used (in the
|
231
|
+
example this would be equal).
|
232
|
+
|
233
|
+
Example:
|
234
|
+
```
|
235
|
+
filter[param][phone_number][like]=001%
|
236
|
+
```
|
237
|
+
|
238
|
+
This would add a filter to all phone numbers which start with "001".
|
239
|
+
|
240
|
+
### Filter binding ###
|
241
|
+
|
242
|
+
Per default all filters are combined through AND clauses.
|
243
|
+
You can change that by specifying the `filter[binding]` argument.
|
244
|
+
|
245
|
+
This is where the aliases which you can define come into place.
|
246
|
+
The binding provides means to combine filters with AND and OR.
|
247
|
+
Also you are able to negate filters here.
|
248
|
+
|
249
|
+
The filters are addressed by their alias or name, if no alias is
|
250
|
+
provided.
|
251
|
+
|
252
|
+
If you have a filter `search_for_name`, `search_for_phone_number`
|
253
|
+
and `search_for_account_number` defined you can say
|
254
|
+
`search_for_name OR NOT search_for_number AND search_for_account_number`
|
255
|
+
by specifying the following filter:
|
256
|
+
|
257
|
+
```
|
258
|
+
filter[binding]=search_for_name|(!search_for_phone_number&search_for_account_number)
|
259
|
+
```
|
260
|
+
|
261
|
+
Even though the brackets are useless here, you can use them in
|
262
|
+
more complex filters.
|
263
|
+
|
264
|
+
The following table summarizes the possible configuration options:
|
265
|
+
<table>
|
266
|
+
<thead>
|
267
|
+
<tr>
|
268
|
+
<th>Type</th>
|
269
|
+
<th>Symbol</th>
|
270
|
+
<th>Example</th>
|
271
|
+
</tr>
|
272
|
+
</thead>
|
273
|
+
<tbody>
|
274
|
+
<tr>
|
275
|
+
<td>AND</td>
|
276
|
+
<td>&</td>
|
277
|
+
<td>a&b</td>
|
278
|
+
</tr>
|
279
|
+
<tr>
|
280
|
+
<td>OR</td>
|
281
|
+
<td>|</td>
|
282
|
+
<td>a|b</td>
|
283
|
+
</tr>
|
284
|
+
<tr>
|
285
|
+
<td>NOT</td>
|
286
|
+
<td>!</td>
|
287
|
+
<td>!a</td>
|
288
|
+
</tr>
|
289
|
+
<tr>
|
290
|
+
<td>Bracket</td>
|
291
|
+
<td>()</td>
|
292
|
+
<td>(a|b)&c</td>
|
293
|
+
</tr>
|
294
|
+
</tbody>
|
295
|
+
</table>
|
296
|
+
|
297
|
+
### Ordering ###
|
298
|
+
|
299
|
+
To specify a sort order of the results the `filter[order]` parameter
|
300
|
+
may be used. The value can be specified multiple times. To add
|
301
|
+
ordering you have to provide the name of the parameter which should
|
302
|
+
be ordered, not its alias!
|
303
|
+
|
304
|
+
If you want to order by `name`, `first_name` and in reverse order
|
305
|
+
`balance` you can do so by specifying the following query url
|
306
|
+
parameters:
|
307
|
+
|
308
|
+
```
|
309
|
+
filter[order]=name&filter[order]=first_name&filter[order]=desc(balance)
|
310
|
+
```
|
311
|
+
|
312
|
+
As you can see the `desc()` definition can be used to indicate
|
313
|
+
reverse ordering.
|
314
|
+
|
315
|
+
## License ##
|
316
|
+
|
317
|
+
The project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
318
|
+
|
319
|
+
## Used libraries ##
|
320
|
+
|
321
|
+
For parsing the query parameters the [`parslet`](https://kschiess.github.io/parslet/)
|
322
|
+
library is used. It is released under the [MIT License](https://github.com/kschiess/parslet/blob/master/LICENSE).
|
323
|
+
|
324
|
+
## Other Languages ##
|
325
|
+
|
326
|
+
This is a list of projects implementing the same API for other languages.
|
327
|
+
Currently this list only has one entry.
|
328
|
+
|
329
|
+
- Go - [go-filterparams](https://github.com/cbrand/go-filterparams)
|
330
|
+
- Python - [python-filterparams](https://github.com/cbrand/python-filterparams)
|
331
|
+
- JavaScript (Client) - [filterparams](https://github.com/cbrand/js-filterparams-client)
|
332
|
+
email: christoph@brand.rest
|
333
|
+
executables: []
|
334
|
+
extensions: []
|
335
|
+
extra_rdoc_files: []
|
336
|
+
files:
|
337
|
+
- LICENSE.txt
|
338
|
+
- README.md
|
339
|
+
- lib/filterparams.rb
|
340
|
+
- lib/filterparams/api.rb
|
341
|
+
- lib/filterparams/binding.rb
|
342
|
+
- lib/filterparams/binding/binding_parser.rb
|
343
|
+
- lib/filterparams/binding/binding_transform.rb
|
344
|
+
- lib/filterparams/obj.rb
|
345
|
+
- lib/filterparams/obj/and.rb
|
346
|
+
- lib/filterparams/obj/binding_operation.rb
|
347
|
+
- lib/filterparams/obj/not.rb
|
348
|
+
- lib/filterparams/obj/or.rb
|
349
|
+
- lib/filterparams/obj/order.rb
|
350
|
+
- lib/filterparams/obj/parameter.rb
|
351
|
+
- lib/filterparams/obj/query.rb
|
352
|
+
- lib/filterparams/order_extractor.rb
|
353
|
+
- lib/filterparams/param_extractor.rb
|
354
|
+
homepage: https://github.com/cbrand/ruby-filterparams
|
355
|
+
licenses:
|
356
|
+
- MIT
|
357
|
+
metadata: {}
|
358
|
+
post_install_message:
|
359
|
+
rdoc_options: []
|
360
|
+
require_paths:
|
361
|
+
- lib
|
362
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
363
|
+
requirements:
|
364
|
+
- - ">="
|
365
|
+
- !ruby/object:Gem::Version
|
366
|
+
version: '0'
|
367
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
368
|
+
requirements:
|
369
|
+
- - ">="
|
370
|
+
- !ruby/object:Gem::Version
|
371
|
+
version: '0'
|
372
|
+
requirements: []
|
373
|
+
rubyforge_project:
|
374
|
+
rubygems_version: 2.5.1
|
375
|
+
signing_key:
|
376
|
+
specification_version: 4
|
377
|
+
summary: Parser for filterparams query parameters
|
378
|
+
test_files: []
|