Class: ForestAdminAgent::Utils::QueryStringParser

Inherits:
Object
  • Object
show all
Includes:
Http::Exceptions, ForestAdminDatasourceToolkit::Components, ForestAdminDatasourceToolkit::Components::Query, ForestAdminDatasourceToolkit::Exceptions, ForestAdminDatasourceToolkit::Validations
Defined in:
lib/forest_admin_agent/utils/query_string_parser.rb

Constant Summary collapse

DEFAULT_ITEMS_PER_PAGE =
'15'.freeze
DEFAULT_PAGE_TO_SKIP =
'1'.freeze
POLYMORPHIC_TARGET_WILDCARD =
'*'.freeze
FALSY_SEARCH_EXTENDED =
[nil, false, 0, '0', 'false', ''].freeze

Class Method Summary collapse

Class Method Details

.parse_caller(args) ⇒ Object



32
33
34
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 32

def self.parse_caller(args)
  CallerParser.new(args).parse
end

.parse_chart_parameters(args) ⇒ Object



309
310
311
312
313
314
315
316
317
318
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 309

def self.parse_chart_parameters(args)
  params = args[:params] || {}

  params.each_with_object({}) do |(key, value), result|
    key_s = key.to_s
    next if value.nil? || value.is_a?(Hash) || value.is_a?(Array)

    result[key_s] = value.to_s
  end
end

.parse_condition_tree(collection, args) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 15

def self.parse_condition_tree(collection, args)
  filters = begin
    args.dig(:params, :data, :attributes, :all_records_subset_query, :filters) ||
      args.dig(:params, :filters) || args.dig(:params, :filter)
  rescue StandardError
    nil
  end

  return if filters.nil?

  filters = JSON.parse(filters, symbolize_names: true) if filters.is_a? String
  condition_tree = ConditionTreeParser.from_plain_object(collection, filters)
  ConditionTreeValidator.validate(condition_tree, collection)

  condition_tree
end

.parse_export_pagination(limit) ⇒ Object



245
246
247
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 245

def self.parse_export_pagination(limit)
  Page.new(offset: 0, limit: limit&.to_i)
end

.parse_pagination(args) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 225

def self.parse_pagination(args)
  items_per_pages = args.dig(:params, :data, :attributes, :all_records_subset_query, :size) ||
                    args.dig(:params, :page, :size) || DEFAULT_ITEMS_PER_PAGE

  page = args.dig(:params, :data, :attributes, :all_records_subset_query, :number) ||
         args.dig(:params, :page, :number) || DEFAULT_PAGE_TO_SKIP

  # Validate both parameters
  page_valid = !page.to_s.match(/\A[+]?\d+\z/).nil? && page.to_i.positive?
  limit_valid = !items_per_pages.to_s.match(/\A[+]?\d+\z/).nil? && items_per_pages.to_i.positive?

  unless page_valid && limit_valid
    raise BadRequestError, "Invalid pagination [limit: #{items_per_pages}, skip: #{page}]"
  end

  offset = (page.to_i - 1) * items_per_pages.to_i

  Page.new(offset: offset, limit: items_per_pages.to_i)
end

.parse_projection(collection, args) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 36

def self.parse_projection(collection, args)
  fields = args.dig(:params, :fields, collection.name) || ''

  return ProjectionFactory.all(collection) unless fields != '' && !fields.nil?

  requested_field_names = fields.split(',').map(&:strip)
  add_polymorphic_type_fields(collection, requested_field_names)
  projection_fields = build_projection_fields(collection, requested_field_names, args)

  ForestAdminDatasourceToolkit::Validations::ProjectionValidator.validate?(collection, projection_fields)

  Projection.new(projection_fields)
rescue ForestAdminDatasourceToolkit::Exceptions::ForestException => e
  raise BadRequestError, "Invalid projection: #{e.message}"
end

.parse_projection_from_header(collection, args) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 52

def self.parse_projection_from_header(collection, args)
  header = decode_header_value(args.dig(:headers, 'HTTP_FOREST_PROJECTION'))&.strip

  return if header.nil? || header.empty?

  projection_fields = build_header_projection_fields(collection, header.split(',', -1).map(&:strip))

  ForestAdminDatasourceToolkit::Validations::ProjectionValidator.validate?(collection, projection_fields)

  Projection.new(projection_fields)
rescue ForestAdminDatasourceToolkit::Exceptions::ForestException => e
  raise BadRequestError, "Invalid Forest-Projection header: #{e.message}"
end

.parse_projection_from_request(collection, args) ⇒ Object



66
67
68
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 66

def self.parse_projection_from_request(collection, args)
  parse_projection_from_header(collection, args) || parse_projection(collection, args)
end

.parse_projection_with_pks(collection, args) ⇒ Object



221
222
223
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 221

def self.parse_projection_with_pks(collection, args)
  parse_projection_from_request(collection, args).with_pks(collection)
end

.parse_requested_projection(collection, args) ⇒ Object

Both halves read the same fields param the same way on purpose: an empty fields[<collection>]= means "every column", so it is not named and must take the redaction path rather than a 403.



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 73

def self.parse_requested_projection(collection, args)
  from_header = parse_projection_from_header(collection, args)

  return { projection: from_header, named_by_caller: true } if from_header

  fields = args.dig(:params, :fields, collection.name)

  {
    projection: parse_projection(collection, args),
    named_by_caller: !(fields.nil? || fields == '')
  }
end

.parse_search(collection, args) ⇒ Object

Raises:



268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 268

def self.parse_search(collection, args)
  search = subset_or_query(args, :search)

  return nil if search.nil?

  raise BadRequestError, 'Collection is not searchable' unless collection.is_searchable?

  unless search.is_a?(String) || search.is_a?(Numeric)
    raise BadRequestError, 'Search must be a string or a number'
  end

  search.to_s
end

.parse_search_extended(args) ⇒ Object



282
283
284
285
286
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 282

def self.parse_search_extended(args)
  extended = subset_or_query(args, :searchExtended)

  !FALSY_SEARCH_EXTENDED.include?(extended.is_a?(String) ? extended.downcase : extended)
end

.parse_segment(collection, args) ⇒ Object

Raises:



320
321
322
323
324
325
326
327
328
329
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 320

def self.parse_segment(collection, args)
  segment = args.dig(:params, :data, :attributes, :all_records_subset_query,
                     :segment) || args.dig(:params, :segment)

  return unless segment

  raise BadRequestError, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment)

  segment
end

.parse_sort(collection, args) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 288

def self.parse_sort(collection, args)
  raw_sort_string = args.dig(:params, :sort)

  return SortUtils::SortFactory.by_primary_keys(collection) unless raw_sort_string

  sort_list = []
  raw_sort_string.split(',').map do |sort_string|
    field = sort_string.tr('.', ':')
    ascending = !sort_string.start_with?('-')
    field = field[1..] unless ascending

    sort_list.push({ field: field, ascending: ascending })
  end

  sort = Sort.new(sort_list)

  ForestAdminDatasourceToolkit::Validations::SortValidator.validate(collection, sort)

  sort
end

.subset_or_query(args, key) ⇒ Object

Presence, not truth: with || a false in the select-all body would silently lose to the query string. An explicit null or '' there is read as absent rather than as "no search", so neither can widen a result set by discarding the term the URL carried.



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/forest_admin_agent/utils/query_string_parser.rb', line 252

def self.subset_or_query(args, key)
  subset = begin
    args.dig(:params, :data, :attributes, :all_records_subset_query)
  rescue StandardError
    nil
  end

  return subset[key] if subset.is_a?(Hash) && !subset[key].nil? && subset[key] != ''

  begin
    args.dig(:params, key)
  rescue StandardError
    nil
  end
end