filter_param 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE +21 -0
  4. data/README.md +138 -0
  5. data/lib/filter_param/ast/attribute.rb +15 -0
  6. data/lib/filter_param/ast/expressions.rb +37 -0
  7. data/lib/filter_param/ast/group.rb +13 -0
  8. data/lib/filter_param/ast/literal.rb +26 -0
  9. data/lib/filter_param/ast/literals/boolean.rb +38 -0
  10. data/lib/filter_param/ast/literals/date.rb +33 -0
  11. data/lib/filter_param/ast/literals/date_time.rb +36 -0
  12. data/lib/filter_param/ast/literals/decimal.rb +29 -0
  13. data/lib/filter_param/ast/literals/integer.rb +39 -0
  14. data/lib/filter_param/ast/literals/null.rb +19 -0
  15. data/lib/filter_param/ast/literals/string.rb +43 -0
  16. data/lib/filter_param/ast/node.rb +15 -0
  17. data/lib/filter_param/ast/scope.rb +12 -0
  18. data/lib/filter_param/definition.rb +157 -0
  19. data/lib/filter_param/field.rb +45 -0
  20. data/lib/filter_param/operator.rb +46 -0
  21. data/lib/filter_param/operators/and.rb +13 -0
  22. data/lib/filter_param/operators/case_insensitive_equal.rb +16 -0
  23. data/lib/filter_param/operators/contains.rb +18 -0
  24. data/lib/filter_param/operators/ends_with.rb +18 -0
  25. data/lib/filter_param/operators/equal.rb +21 -0
  26. data/lib/filter_param/operators/field_filter_operator.rb +40 -0
  27. data/lib/filter_param/operators/greater_than.rb +16 -0
  28. data/lib/filter_param/operators/greater_than_equal.rb +16 -0
  29. data/lib/filter_param/operators/group.rb +17 -0
  30. data/lib/filter_param/operators/less_than.rb +16 -0
  31. data/lib/filter_param/operators/less_than_equal.rb +16 -0
  32. data/lib/filter_param/operators/not.rb +16 -0
  33. data/lib/filter_param/operators/not_equal.rb +19 -0
  34. data/lib/filter_param/operators/or.rb +13 -0
  35. data/lib/filter_param/operators/present.rb +21 -0
  36. data/lib/filter_param/operators/starts_with.rb +18 -0
  37. data/lib/filter_param/parser.rb +144 -0
  38. data/lib/filter_param/scope.rb +24 -0
  39. data/lib/filter_param/transformer.rb +37 -0
  40. data/lib/filter_param/transpiler.rb +114 -0
  41. data/lib/filter_param/version.rb +5 -0
  42. data/lib/filter_param.rb +70 -0
  43. metadata +143 -0
@@ -0,0 +1,114 @@
1
+ module FilterParam
2
+ class Transpiler
3
+ def initialize(ar_relation, definition)
4
+ @ar_relation = ar_relation
5
+ @definition = definition
6
+ end
7
+
8
+ def transpile!(string_expression)
9
+ return nil if string_expression.blank?
10
+
11
+ ast_root = parse(string_expression)
12
+
13
+ visit(ast_root)
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :ar_relation, :definition
19
+
20
+ def ar_model
21
+ @ar_relation.model
22
+ end
23
+
24
+ def parse(string_expression)
25
+ parse_tree = Parser.new.parse(string_expression, reporter: Parslet::ErrorReporter::Deepest.new)
26
+
27
+ Transformer.new.apply(parse_tree)
28
+ end
29
+
30
+ def field_for_name(name)
31
+ definition.find_field!(name)
32
+ end
33
+
34
+ def visit(node)
35
+ return node unless node.respond_to?(:accept)
36
+
37
+ node.accept(self)
38
+ end
39
+
40
+ def visit_group(group)
41
+ expression = visit(group.expression)
42
+
43
+ Operator.for(:group).sql(expression)
44
+ end
45
+
46
+ def visit_scope(scope)
47
+ actual_scope_name = definition.find_scope!(scope.name)
48
+ .actual_name
49
+ scope_args = scope.args.map { |arg| visit(arg) }
50
+
51
+ scope_sql = ar_model.send(actual_scope_name, *scope_args)
52
+ .select(ar_model.primary_key)
53
+ .to_sql
54
+
55
+ "(#{ar_model.primary_key} IN (#{scope_sql}))"
56
+ end
57
+
58
+ def visit_attribute(attribute)
59
+ field_for_name(attribute.name)
60
+ end
61
+
62
+ def visit_literal(literal)
63
+ literal.value
64
+ end
65
+
66
+ def visit_unary_expression(expression)
67
+ operator_symbol = expression.operator
68
+ operand = expression.operand
69
+ return transpile_negated_expression(operand) if operator_symbol == :not
70
+
71
+ operand = visit(operand)
72
+ Operator.for(operator_symbol).sql(operand)
73
+ end
74
+
75
+ def visit_binary_expression(expression)
76
+ operator_symbol = expression.operator
77
+ operator = Operator.for(operator_symbol)
78
+
79
+ if operator < Operators::FieldFilterOperator
80
+ field = visit(expression.left_operand)
81
+ literal = expression.right_operand
82
+ literal.value = field.transform_value(literal.value)
83
+
84
+ return operator.sql(field, literal.type_cast(field.type))
85
+ end
86
+
87
+ operator.sql(visit(expression.left_operand),
88
+ visit(expression.right_operand))
89
+ end
90
+
91
+ def transpile_negated_expression(expression)
92
+ operator_tag = expression.operator
93
+ operator = Operator.for(operator_tag)
94
+ not_operator = Operator.for(:not)
95
+
96
+ if operator < Operators::FieldFilterOperator
97
+ # TODO: refactor
98
+ if operator.type == :binary
99
+ field = visit(expression.left_operand)
100
+ literal = expression.right_operand
101
+ literal.value = field.transform_value(literal.value)
102
+
103
+ return not_operator.sql(operator_tag, field, literal.type_cast(field.type))
104
+ else
105
+ return not_operator.sql(operator_tag, visit(expression.operand))
106
+ end
107
+ end
108
+
109
+ operands = expression.operands.map { |operand| visit(operand) }
110
+
111
+ not_operator.sql(operator_tag, *operands)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FilterParam
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+ require "active_record"
5
+ require "active_support"
6
+ require "active_support/core_ext/object/inclusion"
7
+ require_relative "filter_param/ast/node"
8
+ require_relative "filter_param/ast/attribute"
9
+ require_relative "filter_param/ast/expressions"
10
+ require_relative "filter_param/ast/group"
11
+ require_relative "filter_param/ast/scope"
12
+ require_relative "filter_param/ast/literal"
13
+ require_relative "filter_param/ast/literals/null"
14
+ require_relative "filter_param/ast/literals/string"
15
+ require_relative "filter_param/ast/literals/boolean"
16
+ require_relative "filter_param/ast/literals/integer"
17
+ require_relative "filter_param/ast/literals/decimal"
18
+ require_relative "filter_param/ast/literals/date"
19
+ require_relative "filter_param/ast/literals/date_time"
20
+ require_relative "filter_param/operator"
21
+ require_relative "filter_param/operators/group"
22
+ require_relative "filter_param/operators/field_filter_operator"
23
+ require_relative "filter_param/operators/and"
24
+ require_relative "filter_param/operators/or"
25
+ require_relative "filter_param/operators/not"
26
+ require_relative "filter_param/operators/equal"
27
+ require_relative "filter_param/operators/not_equal"
28
+ require_relative "filter_param/operators/case_insensitive_equal"
29
+ require_relative "filter_param/operators/less_than"
30
+ require_relative "filter_param/operators/less_than_equal"
31
+ require_relative "filter_param/operators/greater_than"
32
+ require_relative "filter_param/operators/greater_than_equal"
33
+ require_relative "filter_param/operators/starts_with"
34
+ require_relative "filter_param/operators/ends_with"
35
+ require_relative "filter_param/operators/contains"
36
+ require_relative "filter_param/operators/present"
37
+ require_relative "filter_param/transformer"
38
+ require_relative "filter_param/parser"
39
+ require_relative "filter_param/transpiler"
40
+ require_relative "filter_param/field"
41
+ require_relative "filter_param/scope"
42
+ require_relative "filter_param/definition"
43
+ require_relative "filter_param/version"
44
+
45
+ module FilterParam
46
+ class BaseError < StandardError; end
47
+ class UnknownType < BaseError; end
48
+ class ExpressionError < BaseError; end
49
+ class UnknownField < BaseError; end
50
+ class UnknownScope < BaseError; end
51
+ class InvalidLiteral < ExpressionError; end
52
+ class ParseError < ExpressionError; end
53
+
54
+ # Creates a new FilterParam definition that whitelists the columns that are allowed to
55
+ # be filtered (i.e. used in SQL WHERE).
56
+ #
57
+ # @param [Proc] block Field definition block
58
+ #
59
+ # @example
60
+ # FilterParam.define do
61
+ # fields :first_name, :last_name
62
+ # field :birth_date, rename: bdate
63
+ # end
64
+ #
65
+ # @return [Definition] Filter param definition
66
+ #
67
+ def self.define(&block)
68
+ Definition.new.define(&block)
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filter_param
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Uy Jayson B
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7.1'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '4.2'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: activesupport
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '4.2'
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '7.1'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '4.2'
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '7.1'
67
+ description: Filter records using a SCIM inspired filter expression
68
+ email:
69
+ - uy.json.dev@gmail.com
70
+ executables: []
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - CHANGELOG.md
75
+ - LICENSE
76
+ - README.md
77
+ - lib/filter_param.rb
78
+ - lib/filter_param/ast/attribute.rb
79
+ - lib/filter_param/ast/expressions.rb
80
+ - lib/filter_param/ast/group.rb
81
+ - lib/filter_param/ast/literal.rb
82
+ - lib/filter_param/ast/literals/boolean.rb
83
+ - lib/filter_param/ast/literals/date.rb
84
+ - lib/filter_param/ast/literals/date_time.rb
85
+ - lib/filter_param/ast/literals/decimal.rb
86
+ - lib/filter_param/ast/literals/integer.rb
87
+ - lib/filter_param/ast/literals/null.rb
88
+ - lib/filter_param/ast/literals/string.rb
89
+ - lib/filter_param/ast/node.rb
90
+ - lib/filter_param/ast/scope.rb
91
+ - lib/filter_param/definition.rb
92
+ - lib/filter_param/field.rb
93
+ - lib/filter_param/operator.rb
94
+ - lib/filter_param/operators/and.rb
95
+ - lib/filter_param/operators/case_insensitive_equal.rb
96
+ - lib/filter_param/operators/contains.rb
97
+ - lib/filter_param/operators/ends_with.rb
98
+ - lib/filter_param/operators/equal.rb
99
+ - lib/filter_param/operators/field_filter_operator.rb
100
+ - lib/filter_param/operators/greater_than.rb
101
+ - lib/filter_param/operators/greater_than_equal.rb
102
+ - lib/filter_param/operators/group.rb
103
+ - lib/filter_param/operators/less_than.rb
104
+ - lib/filter_param/operators/less_than_equal.rb
105
+ - lib/filter_param/operators/not.rb
106
+ - lib/filter_param/operators/not_equal.rb
107
+ - lib/filter_param/operators/or.rb
108
+ - lib/filter_param/operators/present.rb
109
+ - lib/filter_param/operators/starts_with.rb
110
+ - lib/filter_param/parser.rb
111
+ - lib/filter_param/scope.rb
112
+ - lib/filter_param/transformer.rb
113
+ - lib/filter_param/transpiler.rb
114
+ - lib/filter_param/version.rb
115
+ homepage: https://github.com/jsonb-uy/filter_param
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ homepage_uri: https://github.com/jsonb-uy/filter_param
120
+ source_code_uri: https://github.com/jsonb-uy/filter_param
121
+ changelog_uri: https://github.com/jsonb-uy/filter_param/blob/main/CHANGELOG.md
122
+ documentation_uri: https://rubydoc.info/github/jsonb-uy/filter_param/main
123
+ bug_tracker_uri: https://github.com/jsonb-uy/filter_param/issues
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 2.3.0
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.4.6
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: SCIM-style API filter for ActiveRecord-based apps
143
+ test_files: []