Skip to content

Commit

Permalink
Merge pull request #1233 from kbrock/filter_regex
Browse files Browse the repository at this point in the history
Filter uses a regular expression
  • Loading branch information
Fryguy authored Oct 3, 2023
2 parents dfca21a + cdcb7f1 commit 8ac251c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 14 deletions.
21 changes: 7 additions & 14 deletions lib/api/filter.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Api
class Filter
OPERATOR_REGEXP = /^ *([^=!>< ]+) *(!==?|[=!]~|[<>=]=?) *(.*?) *$/.freeze
OPERATORS = {
"!=" => {:default => "!=", :regex => "REGULAR EXPRESSION DOES NOT MATCH", :null => "IS NOT NULL"},
"<=" => {:default => "<="},
Expand Down Expand Up @@ -68,7 +69,6 @@ def parse_filter(filter)
methods = OPERATORS[operator]

is_regex = filter_value =~ /%|\*/ && methods[:regex]
str_method = is_regex ? methods[:regex] : methods[:default]

filter_value, method = case filter_value
when /^\[(.*)\]$/
Expand All @@ -82,8 +82,10 @@ def parse_filter(filter)
unquoted_filter_value = $1
if filter_field.column_type == :string_set && methods[:string_set]
[unquoted_filter_value, methods[:string_set]]
elsif is_regex
[unquoted_filter_value, methods[:regex]]
else
[unquoted_filter_value, str_method]
[unquoted_filter_value, methods[:default]]
end
when /^(NULL|nil)$/i
[nil, methods[:null] || methods[:default]]
Expand Down Expand Up @@ -120,22 +122,13 @@ def single_expression(field, operator, value)
end

def split_filter_string(filter)
operator = nil
operators_from_longest_to_shortest = OPERATORS.keys.sort_by(&:size).reverse
filter.size.times do |i|
operator = operators_from_longest_to_shortest.detect do |o|
o == filter[(i..(i + o.size - 1))]
end
break if operator
end
filter_match = OPERATOR_REGEXP.match(filter)
filter_attr, operator, filter_value = filter_match&.captures

if operator.blank?
unless OPERATORS.key?(operator)
raise BadRequestError, "Unknown operator specified in filter #{filter}"
end

filter_attr, _op, filter_value = filter.partition(operator)
filter_value.strip!
filter_attr.strip!
*associations, attr_name = filter_attr.split(".")
[MiqExpression::Field.new(model, associations, attr_name), operator, filter_value]
end
Expand Down
49 changes: 49 additions & 0 deletions spec/lib/api/filter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -406,4 +406,53 @@
expect(actual.exp).to eq(expected)
end
end

describe "#split_filter_string (private)" do
let(:name_field) { MiqExpression::Field.new(Vm, [], "name") }

Api::Filter::OPERATORS.each_key do |operator|
it "matches operator #{operator}" do
expect(split_filter_string("name#{operator}x")).to eq([name_field, operator, "x"])
expect(split_filter_string("name#{operator}>x")).to eq([name_field, operator, ">x"])
end
end

it "parses parses associations" do
field = MiqExpression::Field.new(Vm, ["host"], "name")
expect(split_filter_string("host.name=x")).to eq([field, "=", "x"])
end

it "handles whitespace" do
expect(split_filter_string(" name = x ")).to eq([name_field, "=", "x"])
end

it "handles blank" do
expect(split_filter_string(" name = ")).to eq([name_field, "=", ""])
end

it "supports quotes" do
expect(split_filter_string(" name= \"x y\" ")).to eq([name_field, "=", "\"x y\""])
expect(split_filter_string("name ='x y'")).to eq([name_field, "=", "'x y'"])
end

it "supports a non-operator that looks like an operator" do
expect(split_filter_string("name=~= x y ")).to eq([name_field, "=~", "= x y"])
end

[
'name^bb',
'=bb',
'name',
].each do |str|
it "complains about '#{str}'" do
expect do
split_filter_string(str)
end.to raise_error(Api::BadRequestError, /Unknown operator specified/)
end
end

def split_filter_string(str)
described_class.new("", Vm).send(:split_filter_string, str)
end
end
end

0 comments on commit 8ac251c

Please sign in to comment.