hoodoo 1.11.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- Y2FmMmNkOGIxNmI3Yjc3Njk2ZGU4MmJlMzVkZDkyYWQ2ZGI2NGNiNQ==
5
- data.tar.gz: !binary |-
6
- YWNkMjFhNmMwODQ2OGYxYjZmNDI1ZGIxZGJmNzg3YjQyY2I5ZDMxYw==
2
+ SHA1:
3
+ metadata.gz: 30fd454d84a35502e8c324645a1c8c981521aa7e
4
+ data.tar.gz: fae766337e9034cf46e9c030c64d5d6ea0609827
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZWJjN2JjMjA3Zjc1YTRmMzYwN2ZkODYyMzE4M2I2N2VkOTg4M2I1MDBjMDhj
10
- ZGE3N2RhNWZlOGMwMDQ2MWVmNzNhMzQ3MWEwZTRiZDhjMjBkMDNjNDdhZjdh
11
- ZjYxOGUwYTk1NDFlYTJmY2VhNGYxYWI5ODk5YzdmNDNkOThjYTE=
12
- data.tar.gz: !binary |-
13
- MmQzNzQxZWI0OTQ2YjI2Nzg1ODAxMzA0ODVlNmM0ODRmMTE2YTY0YmEwYzA2
14
- YTdjMzZkMTY4NWRlNjlmMzdhNTc1NDdiYWM4OTQ1YjNkNzQzNjYzZTlmNDFl
15
- Nzc3Mzk5ZmFhMDIxOTk0NmY2MzIyMTc2MGQ2YjFlYWQ5MDQxMGQ=
6
+ metadata.gz: be4607dfff0f5f798b48eb83abbd3985562df696570137f1c7c9fa7fbaad5df227659c274c758c323fb12901365d665c9a1642d743c17103708cb1509b23db3f
7
+ data.tar.gz: 0c35e38a6740d1e689c361fcf2f0ab07bac09540eb46a9bb1d7ac2f3ab40319c3711b26741fe85c0836e5b1d277208c9c06152f3aa5369745c936714de7a4bf7
@@ -20,8 +20,8 @@ module Hoodoo
20
20
  #
21
21
  # It is _STRONGLY_ _RECOMMENDED_ that you use the likes of:
22
22
  #
23
- # * Hoodoo::ActiveRecord::Finder::ClassMethods::acquire_in
24
- # * Hoodoo::ActiveRecord::Finder::ClassMethods::list_in
23
+ # * Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in
24
+ # * Hoodoo::ActiveRecord::Finder::ClassMethods#list_in
25
25
  #
26
26
  # ...to retrieve model data related to resource instances and participate
27
27
  # "for free" in whatever plug-in ActiveRecord modules are mixed into the
@@ -428,20 +428,14 @@ module Hoodoo
428
428
  finder = all.offset( list_parameters.offset ).limit( list_parameters.limit )
429
429
  finder = finder.order( list_parameters.sort_data )
430
430
 
431
- # DRY up the 'each' loops below. Use a Proc not a method because any
432
- # methods we define will end up being defined on the including Model,
433
- # increasing the chance of a name collision.
434
- #
435
- dry_proc = Proc.new do | data, attr, proc |
436
- value = data[ attr ]
437
- proc.call( attr, value ) unless value.nil?
438
- end
439
-
440
431
  search_map = self.nz_co_loyalty_hoodoo_search_with
441
432
 
442
433
  unless search_map.nil?
443
- search_map.each do | attr, proc |
444
- args = dry_proc.call( list_parameters.search_data, attr, proc )
434
+ search_map.each do | attr, finder_args_proc |
435
+ value = list_parameters.search_data[ attr ]
436
+ next if value.nil?
437
+
438
+ args = finder_args_proc.call( attr, value )
445
439
  finder = finder.where( *args ) unless args.nil?
446
440
  end
447
441
  end
@@ -449,8 +443,11 @@ module Hoodoo
449
443
  filter_map = self.nz_co_loyalty_hoodoo_filter_with
450
444
 
451
445
  unless filter_map.nil?
452
- filter_map.each do | attr, proc |
453
- args = dry_proc.call( list_parameters.filter_data, attr, proc )
446
+ filter_map.each do | attr, finder_args_proc |
447
+ value = list_parameters.filter_data[ attr ]
448
+ next if value.nil?
449
+
450
+ args = finder_args_proc.call( attr, value )
454
451
  finder = finder.where.not( *args ) unless args.nil?
455
452
  end
456
453
  end
@@ -714,7 +711,8 @@ module Hoodoo
714
711
  # which assist with filling in non-nil values for this Hash.
715
712
  #
716
713
  def search_with( hash )
717
- self.nz_co_loyalty_hoodoo_search_with = Hoodoo::ActiveRecord::Support.process_to_map( hash )
714
+ self.nz_co_loyalty_hoodoo_search_with ||= Hoodoo::ActiveRecord::Support.framework_search_and_filter_data()
715
+ self.nz_co_loyalty_hoodoo_search_with.merge!( Hoodoo::ActiveRecord::Support.process_to_map( hash ) )
718
716
  end
719
717
 
720
718
  # As #search_with, but used in +where.not+ queries.
@@ -735,7 +733,8 @@ module Hoodoo
735
733
  # +map+:: As #search_with.
736
734
  #
737
735
  def filter_with( hash )
738
- self.nz_co_loyalty_hoodoo_filter_with = Hoodoo::ActiveRecord::Support.process_to_map( hash )
736
+ self.nz_co_loyalty_hoodoo_filter_with ||= Hoodoo::ActiveRecord::Support.framework_search_and_filter_data()
737
+ self.nz_co_loyalty_hoodoo_filter_with.merge!( Hoodoo::ActiveRecord::Support.process_to_map( hash ) )
739
738
  end
740
739
 
741
740
  # Deprecated interface replaced by #acquire. Instead of:
@@ -14,8 +14,9 @@ module Hoodoo
14
14
  module Finder
15
15
 
16
16
  # Help build up Hash maps to pass into Hoodoo::ActiveRecord::Finder
17
- # methods Hoodoo::ActiveRecord::Finder#search_with and
18
- # Hoodoo::ActiveRecord::Finder#filter_with.
17
+ # methods Hoodoo::ActiveRecord::Finder::ClassMethods#search_with and
18
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with. Used also
19
+ # by the default framework search scopes.
19
20
  #
20
21
  # The usage pattern is as follows, using "sh" as a local variable
21
22
  # just for brevity - it isn't required:
@@ -31,7 +32,7 @@ module Hoodoo
31
32
  # end
32
33
  #
33
34
  # The helper methods just provide values to pass into the Hash used
34
- # with the search/fitler Hoodoo::ActiveRecord::Finder methods, so
35
+ # with the search/filter Hoodoo::ActiveRecord::Finder methods, so
35
36
  # they're optional and compatible with calls that write it out "by
36
37
  # hand".
37
38
  #
@@ -48,15 +49,16 @@ module Hoodoo
48
49
  # will be case sensitive only if your database is configured for
49
50
  # case sensitive matching by default.
50
51
  #
51
- # Results in a <tt>foo = bar</tt> query.
52
+ # Results in a <tt>foo = bar AND foo IS NOT NULL</tt> query.
52
53
  #
53
54
  # +model_field_name+:: If the model attribute name differs from the
54
55
  # search key you want to use in the URI, give
55
56
  # the model attribute name here, else omit.
56
57
  #
57
58
  # Returns a value that can be asssigned to a URI query string key in
58
- # the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
59
- # Hoodoo::ActiveRecord::Finder#filter_with.
59
+ # the Hash given to
60
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
61
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
60
62
  #
61
63
  def self.cs_match( model_field_name = nil )
62
64
  Proc.new { | attr, value |
@@ -70,15 +72,17 @@ module Hoodoo
70
72
  # which are split into an array then processed by AREL back to
71
73
  # something SQL-safe.
72
74
  #
73
- # Results in a <tt>foo IN bar,baz,boo</tt> query.
75
+ # Results in a <tt>foo IN (bar,baz,boo) AND foo IS NOT NULL</tt>
76
+ # query.
74
77
  #
75
78
  # +model_field_name+:: If the model attribute name differs from the
76
79
  # search key you want to use in the URI, give
77
80
  # the model attribute name here, else omit.
78
81
  #
79
82
  # Returns a value that can be asssigned to a URI query string key in
80
- # the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
81
- # Hoodoo::ActiveRecord::Finder#filter_with.
83
+ # the Hash given to
84
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
85
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
82
86
  #
83
87
  def self.cs_match_csv( model_field_name = nil )
84
88
  Proc.new { | attr, value |
@@ -92,17 +96,20 @@ module Hoodoo
92
96
  # Case-sensitive match of a series of values given as an Array.
93
97
  # Normally, query string information comes in as a String so the
94
98
  # use cases for this are quite unusual; you probably want to use
95
- # #cs_match_csv most of the time.
99
+ # Hoodoo::ActiveRecord::Finder::SearchHelper::cs_match_csv most of
100
+ # the time.
96
101
  #
97
- # Results in a <tt>foo IN bar,baz,boo</tt> query.
102
+ # Results in a <tt>foo IN (bar,baz,boo) AND foo IS NOT NULL</tt>
103
+ # query.
98
104
  #
99
105
  # +model_field_name+:: If the model attribute name differs from the
100
106
  # search key you want to use in the URI, give
101
107
  # the model attribute name here, else omit.
102
108
  #
103
109
  # Returns a value that can be asssigned to a URI query string key in
104
- # the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
105
- # Hoodoo::ActiveRecord::Finder#filter_with.
110
+ # the Hash given to
111
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
112
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
106
113
  #
107
114
  def self.cs_match_array( model_field_name = nil )
108
115
  Proc.new { | attr, value |
@@ -113,8 +120,11 @@ module Hoodoo
113
120
  }
114
121
  end
115
122
 
116
- # As #cs_match, but adds wildcards at the front and end of the string
117
- # for a case-sensitive-all-wildcard match.
123
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::cs_match, but
124
+ # adds wildcards at the front and end of the string for a
125
+ # case-sensitive-all-wildcard match.
126
+ #
127
+ # Results in a <tt>foo LIKE bar AND foo IS NOT NULL</tt> query.
118
128
  #
119
129
  def self.csaw_match( model_field_name = nil )
120
130
  Proc.new { | attr, value |
@@ -127,19 +137,22 @@ module Hoodoo
127
137
 
128
138
  # Case-insensitive match which should be fairly database independent
129
139
  # but will run relatively slowly as a result. If you are using
130
- # PostgreSQL, consider using the faster #ci_match_postgres method
140
+ # PostgreSQL, consider using the faster
141
+ # Hoodoo::ActiveRecord::Finder::SearchHelper::ci_match_postgres method
131
142
  # instead.
132
143
  #
133
- # Results in a <tt>lower(foo) = bar</tt> query with +bar+ coerced to
134
- # a String and converted to lower case by Ruby first.
144
+ # Results in a <tt>lower(foo) = bar AND foo IS NOT NULL</tt> query
145
+ # with +bar+ coerced to a String and converted to lower case by Ruby
146
+ # first.
135
147
  #
136
148
  # +model_field_name+:: If the model attribute name differs from the
137
149
  # search key you want to use in the URI, give
138
150
  # the model attribute name here, else omit.
139
151
  #
140
152
  # Returns a value that can be asssigned to a URI query string key in
141
- # the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
142
- # Hoodoo::ActiveRecord::Finder#filter_with.
153
+ # the Hash given to
154
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
155
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
143
156
  #
144
157
  def self.ci_match_generic( model_field_name = nil )
145
158
  Proc.new { | attr, value |
@@ -150,8 +163,11 @@ module Hoodoo
150
163
  }
151
164
  end
152
165
 
153
- # As #ci_match_generic, but adds wildcards at the front and end of
154
- # the string for a case-insensitive-all-wildcard match.
166
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::ci_match_generic,
167
+ # but adds wildcards at the front and end of the string for a
168
+ # case-insensitive-all-wildcard match.
169
+ #
170
+ # Results in a <tt>foo LIKE %bar% AND foo IS NOT NULL</tt> query.
155
171
  #
156
172
  def self.ciaw_match_generic( model_field_name = nil )
157
173
  Proc.new { | attr, value |
@@ -164,17 +180,20 @@ module Hoodoo
164
180
 
165
181
  # Case-insensitive match which requires PostgreSQL but should run
166
182
  # quickly. If you need a database agnostic solution, consider using
167
- # the slower #ci_match_generic method instead.
183
+ # the slower
184
+ # Hoodoo::ActiveRecord::Finder::SearchHelper::ci_match_generic method
185
+ # instead.
168
186
  #
169
- # Results in a <tt>foo ILIKE bar</tt> query.
187
+ # Results in a <tt>foo ILIKE bar AND foo IS NOT NULL</tt> query.
170
188
  #
171
189
  # +model_field_name+:: If the model attribute name differs from the
172
190
  # search key you want to use in the URI, give
173
191
  # the model attribute name here, else omit.
174
192
  #
175
193
  # Returns a value that can be asssigned to a URI query string key in
176
- # the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
177
- # Hoodoo::ActiveRecord::Finder#filter_with.
194
+ # the Hash given to
195
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
196
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
178
197
  #
179
198
  def self.ci_match_postgres( model_field_name = nil )
180
199
  Proc.new { | attr, value |
@@ -184,8 +203,11 @@ module Hoodoo
184
203
  }
185
204
  end
186
205
 
187
- # As #ci_match_postgres, but adds wildcards at the front and end of
188
- # the string for a case-insensitive-all-wildcard match.
206
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::ci_match_postgres,
207
+ # but adds wildcards at the front and end of the string for a
208
+ # case-insensitive-all-wildcard match.
209
+ #
210
+ # Results in a <tt>foo ILIKE %bar% AND foo IS NOT NULL</tt> query.
189
211
  #
190
212
  def self.ciaw_match_postgres( model_field_name = nil )
191
213
  Proc.new { | attr, value |
@@ -194,8 +216,75 @@ module Hoodoo
194
216
  [ "#{ column } ILIKE ? AND #{ column } IS NOT NULL", "%#{ value }%" ]
195
217
  }
196
218
  end
197
- end
198
219
 
220
+ # Case-sensitive less-than (default-style comparison). *WARNING:* This
221
+ # will be case sensitive only if your database is configured for
222
+ # case sensitive matching by default.
223
+ #
224
+ # If comparing non-string column types be sure to pass in a value of an
225
+ # appropriate matching type (e.g. compare dates with DateTimes), else
226
+ # returned results will be incorrect but errors may not arise depending
227
+ # on database engine in use.
228
+ #
229
+ # Results in a <tt>foo < bar AND foo IS NOT NULL</tt> query.
230
+ #
231
+ # +model_field_name+:: If the model attribute name differs from the
232
+ # search key you want to use in the URI, give
233
+ # the model attribute name here, else omit.
234
+ #
235
+ # Returns a value that can be asssigned to a URI query string key in
236
+ # the Hash given to
237
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
238
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with.
239
+ #
240
+ def self.cs_lt( model_field_name = nil )
241
+ Proc.new { | attr, value |
242
+ column = model_field_name || attr
243
+
244
+ [ "#{ column } < ? AND #{ column } IS NOT NULL", value ]
245
+ }
246
+ end
247
+
248
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::cs_lt, but
249
+ # compares with less-than-or-equal-to.
250
+ #
251
+ # Results in a <tt>foo <= bar AND foo IS NOT NULL</tt> query.
252
+ #
253
+ def self.cs_lte( model_field_name = nil )
254
+ Proc.new { | attr, value |
255
+ column = model_field_name || attr
256
+
257
+ [ "#{ column } <= ? AND #{ column } IS NOT NULL", value ]
258
+ }
259
+ end
260
+
261
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::cs_lt, but
262
+ # compares with greater-than.
263
+ #
264
+ # Results in a <tt>foo > bar AND foo IS NOT NULL</tt> query.
265
+ #
266
+ def self.cs_gt( model_field_name = nil )
267
+ Proc.new { | attr, value |
268
+ column = model_field_name || attr
269
+
270
+ [ "#{ column } > ? AND #{ column } IS NOT NULL", value ]
271
+ }
272
+ end
273
+
274
+ # As Hoodoo::ActiveRecord::Finder::SearchHelper::cs_lt, but
275
+ # compares with greater-than-or-equal-to.
276
+ #
277
+ # Results in a <tt>foo >= bar AND foo IS NOT NULL</tt> query.
278
+ #
279
+ def self.cs_gte( model_field_name = nil )
280
+ Proc.new { | attr, value |
281
+ column = model_field_name || attr
282
+
283
+ [ "#{ column } >= ? AND #{ column } IS NOT NULL", value ]
284
+ }
285
+ end
286
+
287
+ end
199
288
  end
200
289
  end
201
290
  end
@@ -35,6 +35,51 @@ module Hoodoo
35
35
  #
36
36
  class Support
37
37
 
38
+ # Returns a (newly generated) Hash of search keys mapping to helper Procs
39
+ # which are in the same format as would be passed to
40
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with or
41
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with, describing the
42
+ # default framework search parameters. The middleware defines keys, but
43
+ # each ORM adapter module must specify how those keys actually get used
44
+ # to search inside supported database engines.
45
+ #
46
+ def self.framework_search_and_filter_data
47
+
48
+ # The middleware includes framework-level mappings between URI query
49
+ # string search keys and data validators and processors which convert
50
+ # types where necessary. For example, 'created_at' must be given a
51
+ # valid ISO 8601 subset string and a parsed DateTime will end up in
52
+ # the parsed search hash.
53
+ #
54
+ # Services opt out of framework-level searching at an interface level
55
+ # which means the Finder code herein, under normal flow, will never
56
+ # be asked to process something the interface omits. There is thus no
57
+ # need to try and break encapsulation and come up with a way to read
58
+ # the service interface's omissions. Instead, map everything.
59
+ #
60
+ # This could actually be useful if someone manually drives the #list
61
+ # mechanism with hand-constructed search or filter data that quite
62
+ # intentionally includes framework level parameters even if their own
63
+ # service interface for some reason opts out of allowing them to be
64
+ # exposed to API callers.
65
+ #
66
+ # Note that the #search_with / #filter_with DSL declaration in an
67
+ # appropriately extended model can be used to override the default
68
+ # values wired in below, because the defaults are established by
69
+ # design _before_ the model declarations are processed.
70
+ #
71
+ mapping = {
72
+ 'created_after' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_gt( :created_at ),
73
+ 'created_before' => Hoodoo::ActiveRecord::Finder::SearchHelper.cs_lt( :created_at )
74
+ }
75
+
76
+ if mapping.keys.length != ( mapping.keys | Hoodoo::Services::Middleware::FRAMEWORK_QUERY_DATA.keys ).length
77
+ raise 'Hoodoo::ActiveRecord::Support#framework_search_and_filter_data: Mismatch between internal mapping and Hoodoo::Services::Middleware::FRAMEWORK_QUERY_DATA'
78
+ end
79
+
80
+ return mapping
81
+ end
82
+
38
83
  # Takes a Hash of possibly-non-String keys and with +nil+ values or
39
84
  # Proc instances appropriate for Hoodoo::ActiveRecord::Finder#search_with
40
85
  # / #filter_with. Returns a similar Hash with all-String keys and a Proc
@@ -52,7 +52,7 @@ module Hoodoo
52
52
  if results.size > 0
53
53
 
54
54
  if results.platform_errors.has_errors?
55
- raise "Hoodoo::Client:: PaginatedEnumeration#enumerate_all: Unexpected internal state combination of results set and results error indication"
55
+ raise 'Hoodoo::Client::PaginatedEnumeration#enumerate_all: Unexpected internal state combination of results set and results error indication'
56
56
  end
57
57
 
58
58
  # Yield a resource at a time to the caller
@@ -175,6 +175,48 @@ module Hoodoo; module Services
175
175
  }
176
176
  } )
177
177
 
178
+ # A validation Proc for FRAMEWORK_QUERY_DATA - see that for details. This
179
+ # one ensures that the value is a valid ISO 8601 subset date/time string
180
+ # and evaluates to the parsed version of that string if so.
181
+ #
182
+ FRAMEWORK_QUERY_VALUE_DATE_PROC = -> ( value ) {
183
+ Hoodoo::Utilities.valid_iso8601_subset_datetime?( value ) ?
184
+ Hoodoo::Utilities.rationalise_datetime( value ) :
185
+ nil
186
+ }
187
+
188
+ # Out-of-box search and filter query keys. Interfaces can override the
189
+ # support for these inside the Hoodoo::Services::Interface.to_list block
190
+ # using Hoodoo::Services::Interface::ToListDSL.do_not_search and
191
+ # Hoodoo::Services::Interface::ToListDSL.do_not_filter.
192
+ #
193
+ # Keys, in order, are:
194
+ #
195
+ # * Query key to detect records with a +created_at+ date that is after the
196
+ # given value, in supporting resource; if used as a filter instead of a
197
+ # search string, would find records on-or-before the date.
198
+ #
199
+ # * Query key to detect records with a +created_at+ date that is before
200
+ # the given value, in supporting resource; if used as a filter instead
201
+ # of a search string, would find records on-or-after the date.
202
+ #
203
+ # Values are either a validation Proc or +nil+ for no validation. The
204
+ # Proc takes the search query value as its sole input paraeter and must
205
+ # evaluate to the input value either unmodified or in some canonicalised
206
+ # form if it is valid, else to +nil+ if the input value is invalid. The
207
+ # canonicalisation is typically used to coerce a URI query string based
208
+ # String type into a more useful comparable entity such as an Integer or
209
+ # DateTime.
210
+ #
211
+ # *IMPORTANT* - if this list is changed, any database support modules -
212
+ # e.g. in Hoodoo::ActiveRecord::Support - will need any internal mapping
213
+ # of "framework query keys to module-appropriate query code" updating.
214
+ #
215
+ FRAMEWORK_QUERY_DATA = {
216
+ 'created_after' => FRAMEWORK_QUERY_VALUE_DATE_PROC,
217
+ 'created_before' => FRAMEWORK_QUERY_VALUE_DATE_PROC,
218
+ }
219
+
178
220
  # Utility - returns the execution environment as a Rails-like environment
179
221
  # object which answers queries like +production?+ or +staging?+ with +true+
180
222
  # or +false+ according to the +RACK_ENV+ environment variable setting.
@@ -2770,21 +2812,52 @@ module Hoodoo; module Services
2770
2812
  end
2771
2813
  end
2772
2814
 
2773
- search = query_hash[ 'search' ] || {}
2774
- unrecognised_search_keys = search.keys - interface.to_list.search
2775
- malformed << "search: #{ unrecognised_search_keys.join(', ') }" unless unrecognised_search_keys.empty?
2815
+ search = query_hash[ 'search' ] || {}
2816
+ framework_search = FRAMEWORK_QUERY_DATA.keys - interface.to_list.do_not_search
2817
+ bad_search_keys = search.keys - framework_search - interface.to_list.search
2818
+
2819
+ framework_search.each do | search_key |
2820
+ next unless search.has_key?( search_key )
2821
+
2822
+ search_value = search[ search_key ]
2823
+ validator = FRAMEWORK_QUERY_DATA[ search_key ]
2824
+ canonical = validator.call( search_value )
2825
+
2826
+ if canonical.nil?
2827
+ bad_search_keys << search_key
2828
+ else
2829
+ search[ search_key ] = canonical
2830
+ end
2831
+ end
2832
+
2833
+ filter = query_hash[ 'filter' ] || {}
2834
+ framework_filter = FRAMEWORK_QUERY_DATA.keys - interface.to_list.do_not_filter
2835
+ bad_filter_keys = filter.keys - framework_filter - interface.to_list.filter
2836
+
2837
+ framework_filter.each do | filter_key |
2838
+ next unless filter.has_key?( filter_key )
2839
+
2840
+ filter_value = filter[ filter_key ]
2841
+ validator = FRAMEWORK_QUERY_DATA[ filter_key ]
2842
+ canonical = validator.call( filter_value )
2843
+
2844
+ if canonical.nil?
2845
+ bad_filter_keys << filter_key
2846
+ else
2847
+ filter[ filter_key ] = canonical
2848
+ end
2849
+ end
2776
2850
 
2777
- filter = query_hash[ 'filter' ] || {}
2778
- unrecognised_filter_keys = filter.keys - interface.to_list.filter
2779
- malformed << "filter: #{ unrecognised_filter_keys.join(', ') }" unless unrecognised_filter_keys.empty?
2851
+ embeds = query_hash[ '_embed' ] || []
2852
+ bad_embeds = embeds - interface.embeds
2780
2853
 
2781
- embeds = query_hash[ '_embed' ] || []
2782
- unrecognised_embeds = embeds - interface.embeds
2783
- malformed << "_embed: #{ unrecognised_embeds.join(', ') }" unless unrecognised_embeds.empty?
2854
+ references = query_hash[ '_reference' ] || []
2855
+ bad_references = references - interface.embeds # (sic.)
2784
2856
 
2785
- references = query_hash[ '_reference' ] || []
2786
- unrecognised_references = references - interface.embeds # (sic.)
2787
- malformed << "_reference: #{ unrecognised_references.join(', ') }" unless unrecognised_references.empty?
2857
+ malformed << "search: #{ bad_search_keys.join( ', ' ) }" unless bad_search_keys.empty?
2858
+ malformed << "filter: #{ bad_filter_keys.join( ', ' ) }" unless bad_filter_keys.empty?
2859
+ malformed << "_embed: #{ bad_embeds.join( ', ' ) }" unless bad_embeds.empty?
2860
+ malformed << "_reference: #{ bad_references.join( ', ' ) }" unless bad_references.empty?
2788
2861
 
2789
2862
  return response.add_error(
2790
2863
  'platform.malformed',