cuprum-collections 0.1.0

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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +59 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +25 -0
  5. data/LICENSE +22 -0
  6. data/README.md +950 -0
  7. data/lib/cuprum/collections/base.rb +11 -0
  8. data/lib/cuprum/collections/basic/collection.rb +135 -0
  9. data/lib/cuprum/collections/basic/command.rb +112 -0
  10. data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
  11. data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
  12. data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
  13. data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
  14. data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
  15. data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
  16. data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
  17. data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
  18. data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
  19. data/lib/cuprum/collections/basic/commands.rb +18 -0
  20. data/lib/cuprum/collections/basic/query.rb +160 -0
  21. data/lib/cuprum/collections/basic/query_builder.rb +69 -0
  22. data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
  23. data/lib/cuprum/collections/basic/rspec.rb +8 -0
  24. data/lib/cuprum/collections/basic.rb +22 -0
  25. data/lib/cuprum/collections/command.rb +26 -0
  26. data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
  27. data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
  28. data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
  29. data/lib/cuprum/collections/commands.rb +8 -0
  30. data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
  31. data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
  32. data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
  33. data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
  34. data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
  35. data/lib/cuprum/collections/constraints/order.rb +8 -0
  36. data/lib/cuprum/collections/constraints/ordering.rb +114 -0
  37. data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
  38. data/lib/cuprum/collections/constraints.rb +8 -0
  39. data/lib/cuprum/collections/errors/already_exists.rb +86 -0
  40. data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
  41. data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
  42. data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
  43. data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
  44. data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
  45. data/lib/cuprum/collections/errors/not_found.rb +81 -0
  46. data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
  47. data/lib/cuprum/collections/errors.rb +8 -0
  48. data/lib/cuprum/collections/queries/ordering.rb +74 -0
  49. data/lib/cuprum/collections/queries/parse.rb +22 -0
  50. data/lib/cuprum/collections/queries/parse_block.rb +206 -0
  51. data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
  52. data/lib/cuprum/collections/queries.rb +25 -0
  53. data/lib/cuprum/collections/query.rb +247 -0
  54. data/lib/cuprum/collections/query_builder.rb +61 -0
  55. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
  56. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
  57. data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
  58. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
  59. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
  60. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
  61. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
  62. data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
  63. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
  64. data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
  65. data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
  66. data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
  67. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
  68. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
  69. data/lib/cuprum/collections/rspec.rb +8 -0
  70. data/lib/cuprum/collections/version.rb +59 -0
  71. data/lib/cuprum/collections.rb +26 -0
  72. metadata +219 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+ require 'sleeping_king_studios/tools/toolbelt'
5
+
6
+ require 'cuprum/collections/errors'
7
+
8
+ module Cuprum::Collections::Errors
9
+ # Returned when a find command does not find the requested items.
10
+ class NotFound < Cuprum::Error
11
+ # Short string used to identify the type of error.
12
+ TYPE = 'cuprum.collections.errors.not_found'
13
+
14
+ # @param collection_name [String, Symbol] The name of the collection.
15
+ # @param primary_key_name [String, Symbol] The name of the primary key
16
+ # attribute.
17
+ # @param primary_key_values [Object, Array] The expected values of the
18
+ # primary key attribute.
19
+ def initialize(collection_name:, primary_key_name:, primary_key_values:)
20
+ @collection_name = collection_name
21
+ @primary_key_name = primary_key_name
22
+ @primary_key_values = Array(primary_key_values)
23
+
24
+ super(message: default_message)
25
+ end
26
+
27
+ # @return [String, Symbol] the name of the collection.
28
+ attr_reader :collection_name
29
+
30
+ # @return [String, Symbol] the name of the primary key attribute.
31
+ attr_reader :primary_key_name
32
+
33
+ # @return [Array] The expected values of the primary key attribute.
34
+ attr_reader :primary_key_values
35
+
36
+ # @return [Hash] a serializable hash representation of the error.
37
+ def as_json
38
+ {
39
+ 'data' => {
40
+ 'collection_name' => collection_name,
41
+ 'primary_key_name' => primary_key_name,
42
+ 'primary_key_values' => primary_key_values
43
+ },
44
+ 'message' => message,
45
+ 'type' => type
46
+ }
47
+ end
48
+
49
+ # @return [String] short string used to identify the type of error.
50
+ def type
51
+ TYPE
52
+ end
53
+
54
+ private
55
+
56
+ def default_message
57
+ primary_keys = primary_key_values.map(&:inspect).join(', ')
58
+
59
+ "#{entity_name} not found with #{primary_key_name} #{primary_keys}"
60
+ end
61
+
62
+ def entity_name
63
+ entity_name = collection_name
64
+ entity_name = tools.str.singularize(entity_name) if singular?
65
+
66
+ titleize(entity_name)
67
+ end
68
+
69
+ def singular?
70
+ primary_key_values.size == 1
71
+ end
72
+
73
+ def titleize(string)
74
+ tools.str.underscore(string).split('_').map(&:capitalize).join(' ')
75
+ end
76
+
77
+ def tools
78
+ SleepingKingStudios::Tools::Toolbelt.instance
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/errors'
4
+ require 'cuprum/collections/queries'
5
+
6
+ module Cuprum::Collections::Errors
7
+ # An error returned when a query attempts to filter by an unknown operator.
8
+ class UnknownOperator < Cuprum::Error
9
+ # Short string used to identify the type of error.
10
+ TYPE = 'cuprum.collections.errors.unknown_operator'
11
+
12
+ # @param operator [String, Symbol] The unknown operator.
13
+ def initialize(operator:)
14
+ @operator = operator
15
+
16
+ super(
17
+ message: generate_message,
18
+ operator: operator
19
+ )
20
+ end
21
+
22
+ # @return [String, Symbol] the unknown operator.
23
+ attr_reader :operator
24
+
25
+ # @return [Hash] a serializable hash representation of the error.
26
+ def as_json
27
+ {
28
+ 'data' => {
29
+ 'corrections' => corrections,
30
+ 'operator' => operator
31
+ },
32
+ 'message' => message,
33
+ 'type' => type
34
+ }
35
+ end
36
+
37
+ # @return [Array<String>] Suggested possible values for the operator.
38
+ def corrections
39
+ @corrections ||=
40
+ DidYouMean::SpellChecker
41
+ .new(dictionary: Cuprum::Collections::Queries::VALID_OPERATORS.to_a)
42
+ .correct(operator)
43
+ end
44
+
45
+ # @return [String] short string used to identify the type of error.
46
+ def type
47
+ TYPE
48
+ end
49
+
50
+ private
51
+
52
+ def generate_message
53
+ message = "unknown operator #{operator.inspect}"
54
+
55
+ return message if corrections.empty?
56
+
57
+ "#{message} - did you mean #{suggestion}?"
58
+ end
59
+
60
+ def suggestion
61
+ tools.ary.humanize_list(
62
+ corrections.map(&:inspect),
63
+ last_separator: ', or '
64
+ )
65
+ end
66
+
67
+ def tools
68
+ SleepingKingStudios::Tools::Toolbelt.instance
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections'
4
+
5
+ module Cuprum::Collections
6
+ # Namespace for errors, which represent failure states of commands.
7
+ module Errors; end
8
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/constraints/ordering'
4
+ require 'cuprum/collections/queries'
5
+
6
+ module Cuprum::Collections::Queries
7
+ # Functionality around validating and normalizing query sort orderings.
8
+ module Ordering
9
+ # Exception class for handling invalid order keywords.
10
+ class InvalidOrderError < ArgumentError; end
11
+
12
+ ORDER_HASH_VALUES = {
13
+ asc: :asc,
14
+ ascending: :asc,
15
+ desc: :desc,
16
+ descending: :desc
17
+ }.freeze
18
+ private_constant :ORDER_HASH_VALUES
19
+
20
+ class << self
21
+ # @overload normalize(*attributes, ordering_hash = nil)
22
+ # Converts the given sort order into a hash with standard values.
23
+ #
24
+ # @param attributes [Array<String, Symbol>] The attribute names to sort
25
+ # by, in ascending direction, and in order of importance.
26
+ # @param ordering_hash [Hash] An optional ordering hash, with keys that
27
+ # are valid attribute names and values that are valid sort directions.
28
+ #
29
+ # @return [Hash] the normalized sort ordering.
30
+ #
31
+ # @raise InvalidOrderError if any of the attributes are invalid
32
+ # attribute names.
33
+ # @raise InvalidOrderError if any of the hash keys are invalid attribute
34
+ # names, or any of the hash values are invalid sort directions.
35
+ def normalize(*attributes)
36
+ validate_ordering!(attributes)
37
+
38
+ qualified = attributes.last.is_a?(Hash) ? attributes.pop : {}
39
+ qualified = normalize_order_hash(qualified)
40
+
41
+ attributes
42
+ .each
43
+ .with_object({}) { |attribute, hsh| hsh[attribute.intern] = :asc }
44
+ .merge(qualified)
45
+ end
46
+
47
+ private
48
+
49
+ def normalize_order_hash(hsh)
50
+ hsh.each.with_object({}) do |(key, value), normalized|
51
+ normalized[key.intern] = normalize_order_hash_value(value)
52
+ end
53
+ end
54
+
55
+ def normalize_order_hash_value(value)
56
+ value = value.downcase if value.is_a?(String)
57
+
58
+ ORDER_HASH_VALUES.fetch(value.is_a?(String) ? value.intern : value)
59
+ end
60
+
61
+ def ordering_constraint
62
+ Cuprum::Collections::Constraints::Ordering.instance
63
+ end
64
+
65
+ def validate_ordering!(attributes)
66
+ return if ordering_constraint.matches?(attributes)
67
+
68
+ raise InvalidOrderError,
69
+ 'order must be a list of attribute names and/or a hash of attribute' \
70
+ ' names with values :asc or :desc'
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/queries'
4
+ require 'cuprum/collections/queries/parse_strategy'
5
+
6
+ module Cuprum::Collections::Queries
7
+ # Command to parse parameters passed to Query#where into criteria.
8
+ class Parse < Cuprum::Command
9
+ private
10
+
11
+ def process(where:, strategy: nil)
12
+ command = step do
13
+ Cuprum::Collections::Queries::ParseStrategy.new.call(
14
+ strategy: strategy,
15
+ where: where
16
+ )
17
+ end
18
+
19
+ command.call(where: where)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'cuprum/errors/uncaught_exception'
6
+ require 'stannum/contracts/parameters_contract'
7
+
8
+ require 'cuprum/collections/command'
9
+ require 'cuprum/collections/constraints/query_hash'
10
+ require 'cuprum/collections/errors/invalid_query'
11
+ require 'cuprum/collections/errors/unknown_operator'
12
+ require 'cuprum/collections/queries'
13
+
14
+ module Cuprum::Collections::Queries
15
+ # Command for parsing a Query#where block into criteria.
16
+ #
17
+ # @example An Empty Query
18
+ # command = Cuprum::Collections::Queries::ParseBlock.new
19
+ # result = command.call { {} }
20
+ # result.value #=> []
21
+ #
22
+ # @example A Value Query
23
+ # command = Cuprum::Collections::Queries::ParseBlock.new
24
+ # result = command.call do
25
+ # {
26
+ # author: 'Nnedi Okorafor',
27
+ # series: 'Binti',
28
+ # genre: 'Africanfuturism'
29
+ # }
30
+ # end
31
+ # result.value #=>
32
+ # # [
33
+ # # ['author', :eq, 'Nnedi Okorafor'],
34
+ # # ['series', :eq, 'Binti'],
35
+ # # ['genre', :eq, 'Africanfuturism']
36
+ # # ]
37
+ #
38
+ # @example A Query With Operators
39
+ # command = Cuprum::Collections::Queries::ParseBlock.new
40
+ # result = command.call do
41
+ # {
42
+ # author: equal('Nnedi Okorafor'),
43
+ # series: not_equal('Binti')
44
+ # }
45
+ # end
46
+ # result.value #=>
47
+ # # [
48
+ # # ['author', :eq, 'Nnedi Okorafor'],
49
+ # # ['series', :ne, 'Binti']
50
+ # # ]
51
+ class ParseBlock < Cuprum::Collections::Command
52
+ # Evaluation context for query blocks.
53
+ class Builder < BasicObject
54
+ # Generates an equality criterion.
55
+ #
56
+ # @return [Array] the equality criterion.
57
+ def equals(value)
58
+ [nil, Operators::EQUAL, value]
59
+ end
60
+ alias equal equals
61
+ alias eq equals
62
+
63
+ # Generates a greater than comparison criterion.
64
+ #
65
+ # @return [Array] the greater than criterion.
66
+ def greater_than(value)
67
+ [nil, Operators::GREATER_THAN, value]
68
+ end
69
+ alias gt greater_than
70
+
71
+ # Generates a greater than or equal to comparison criterion.
72
+ #
73
+ # @return [Array] the greater than or equal to criterion.
74
+ def greater_than_or_equal_to(value)
75
+ [nil, Operators::GREATER_THAN_OR_EQUAL_TO, value]
76
+ end
77
+ alias gte greater_than_or_equal_to
78
+
79
+ # Generates a less than comparison criterion.
80
+ #
81
+ # @return [Array] the less than criterion.
82
+ def less_than(value)
83
+ [nil, Operators::LESS_THAN, value]
84
+ end
85
+ alias lt less_than
86
+
87
+ # Generates a less than or equal to comparison criterion.
88
+ #
89
+ # @return [Array] the less than or equal to criterion.
90
+ def less_than_or_equal_to(value)
91
+ [nil, Operators::LESS_THAN_OR_EQUAL_TO, value]
92
+ end
93
+ alias lte less_than_or_equal_to
94
+
95
+ # Generates a negated equality criterion.
96
+ #
97
+ # @return [Array] the negated equality criterion.
98
+ def not_equal(value)
99
+ [nil, Operators::NOT_EQUAL, value]
100
+ end
101
+ alias ne not_equal
102
+
103
+ # Generates a negated inclusion criterion.
104
+ #
105
+ # @return [Array] the negated inclusion criterion.
106
+ def not_one_of(value)
107
+ [nil, Operators::NOT_ONE_OF, value]
108
+ end
109
+
110
+ # Generates an inclusion criterion.
111
+ #
112
+ # @return [Array] the inclusion criterion.
113
+ def one_of(value)
114
+ [nil, Operators::ONE_OF, value]
115
+ end
116
+ end
117
+
118
+ class << self
119
+ extend Forwardable
120
+
121
+ def_delegators :validation_contract,
122
+ :errors_for,
123
+ :match,
124
+ :matches?
125
+
126
+ private
127
+
128
+ def validation_contract
129
+ self::MethodValidations.contracts.fetch(:call)
130
+ end
131
+ end
132
+
133
+ validate_parameters :call do
134
+ keyword :where, Proc
135
+ end
136
+
137
+ private
138
+
139
+ def call_block(&block)
140
+ handle_unknown_operator { Builder.new.instance_exec(&block) }
141
+ rescue StandardError => exception
142
+ error = Cuprum::Errors::UncaughtException.new(
143
+ exception: exception,
144
+ message: 'uncaught exception when parsing query block'
145
+ )
146
+
147
+ failure(error)
148
+ end
149
+
150
+ def generate_criteria(hsh)
151
+ hsh.map do |key, value|
152
+ unless partial_criterion?(value)
153
+ next [key.to_s, Cuprum::Collections::Queries::Operators::EQUAL, value]
154
+ end
155
+
156
+ value.tap { |ary| ary[0] = key.to_s }
157
+ end
158
+ end
159
+
160
+ def handle_unknown_operator
161
+ yield
162
+ rescue NoMethodError => exception
163
+ error = Cuprum::Collections::Errors::UnknownOperator.new(
164
+ operator: exception.name
165
+ )
166
+
167
+ failure(error)
168
+ end
169
+
170
+ def invalid_query_error(errors:, message: nil)
171
+ Cuprum::Collections::Errors::InvalidQuery.new(
172
+ errors: errors,
173
+ message: message,
174
+ strategy: :block
175
+ )
176
+ end
177
+
178
+ def partial_criterion?(obj)
179
+ return false unless obj.is_a?(Array) && obj.size == 3
180
+
181
+ attribute, operator, _value = obj
182
+
183
+ return false unless attribute.nil?
184
+
185
+ Cuprum::Collections::Queries::VALID_OPERATORS.include?(operator)
186
+ end
187
+
188
+ def process(where:)
189
+ hsh = step { call_block(&where) }
190
+
191
+ step { validate_hash(hsh) }
192
+
193
+ generate_criteria(hsh)
194
+ end
195
+
196
+ def validate_hash(obj)
197
+ constraint = Cuprum::Collections::Constraints::QueryHash.new
198
+ match, errors = constraint.match(obj)
199
+
200
+ return if match
201
+
202
+ message = 'query block returned invalid value'
203
+ failure(invalid_query_error(errors: errors, message: message))
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/queries'
4
+ require 'cuprum/collections/queries/parse_block'
5
+ require 'cuprum/collections/errors/invalid_query'
6
+
7
+ module Cuprum::Collections::Queries
8
+ # Command to select the parsing strategy for parsing Query#where parameters.
9
+ class ParseStrategy < Cuprum::Command
10
+ STRATEGIES = {
11
+ block: Cuprum::Collections::Queries::ParseBlock
12
+ }.freeze
13
+ private_constant :STRATEGIES
14
+
15
+ # The :type of the error generated for an unknown parsing strategy.
16
+ UNKNOWN_STRATEGY_ERROR =
17
+ 'cuprum.collections.errors.queries.unknown_strategy'
18
+
19
+ private
20
+
21
+ def find_and_validate_strategy(strategy:, where:)
22
+ command_class = step { find_strategy_by_key(strategy: strategy) }
23
+ parameters = {
24
+ arguments: [],
25
+ block: nil,
26
+ keywords: { where: where }
27
+ }
28
+
29
+ return command_class if command_class.matches?(parameters)
30
+
31
+ errors = command_class.errors_for(parameters)
32
+
33
+ failure(invalid_parameters_error(errors: errors, strategy: strategy))
34
+ end
35
+
36
+ def find_strategy(strategy:, where:)
37
+ if strategy
38
+ return find_and_validate_strategy(strategy: strategy, where: where)
39
+ end
40
+
41
+ command_class = find_strategy_by_parameters(where: where)
42
+
43
+ return command_class if command_class
44
+
45
+ failure(unknown_strategy_error(strategy: strategy))
46
+ end
47
+
48
+ def find_strategy_by_key(strategy:)
49
+ STRATEGIES.fetch(strategy) do
50
+ failure(unknown_strategy_error(strategy: strategy))
51
+ end
52
+ end
53
+
54
+ def find_strategy_by_parameters(where:)
55
+ STRATEGIES
56
+ .values
57
+ .find do |command_class|
58
+ command_class.matches?(
59
+ arguments: [],
60
+ block: nil,
61
+ keywords: { where: where }
62
+ )
63
+ end
64
+ end
65
+
66
+ def invalid_parameters_error(errors:, strategy:)
67
+ Cuprum::Collections::Errors::InvalidQuery.new(
68
+ errors: errors,
69
+ strategy: strategy
70
+ )
71
+ end
72
+
73
+ def process(strategy: nil, where: nil)
74
+ command_class = step do
75
+ find_strategy(strategy: strategy, where: where)
76
+ end
77
+
78
+ command_class.new
79
+ end
80
+
81
+ def unknown_strategy_error(strategy:)
82
+ errors = Stannum::Errors.new
83
+ errors[:strategy].add(UNKNOWN_STRATEGY_ERROR, strategy: strategy)
84
+
85
+ Cuprum::Collections::Errors::InvalidQuery.new(
86
+ errors: errors,
87
+ strategy: strategy
88
+ )
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sleeping_king_studios/tools/toolbox/constant_map'
4
+
5
+ require 'cuprum/collections'
6
+
7
+ module Cuprum::Collections
8
+ # Namespace for internal functionality for implementing collection queries.
9
+ module Queries
10
+ # Defines the supported operators for a Query.
11
+ Operators = SleepingKingStudios::Tools::Toolbox::ConstantMap.new(
12
+ EQUAL: :equal,
13
+ GREATER_THAN: :greater_than,
14
+ GREATER_THAN_OR_EQUAL_TO: :greater_than_or_equal_to,
15
+ LESS_THAN: :less_than,
16
+ LESS_THAN_OR_EQUAL_TO: :less_than_or_equal_to,
17
+ NOT_EQUAL: :not_equal,
18
+ NOT_ONE_OF: :not_one_of,
19
+ ONE_OF: :one_of
20
+ ).freeze
21
+
22
+ # Enumerates the valid operators as a Set for performant lookup.
23
+ VALID_OPERATORS = Set.new(Operators.values).freeze
24
+ end
25
+ end