-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathbase_parameter_parser.rb
180 lines (161 loc) · 5.42 KB
/
base_parameter_parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
require "ostruct"
require "unf"
class BaseParameterParser
# The fields listed here are the only ones which the search results can be
# ordered by. These are listed and validated explicitly because
# sorting by arbitrary fields can be expensive in terms of memory usage in
# elasticsearch, and because elasticsearch gives fairly obscure error
# messages if undefined sort fields are used.
ALLOWED_SORT_FIELDS = %w(
public_timestamp
closing_date
title
tribunal_decision_decision_date
start_date
).freeze
SORT_MAPPINGS = {
"title" => "title.sort"
}.freeze
# Incoming filter fields will have their names transformed according to the
# following mapping. Fields not listed here will be passed through unchanged.
FILTER_NAME_MAPPING = {
# TODO: clients should not use `document_type` to search for documents.
"document_type" => "_type",
"elasticsearch_type" => "_type",
}.freeze
# The fields listed here are the only ones which can be used to calculated
# facets for. This should be a subset of allowed_filter_fields
ALLOWED_FACET_FIELDS = %w(
detailed_format
document_collections
document_series
format
mainstream_browse_pages
manual
organisation_type
organisations
people
policies
policy_areas
search_format_types
specialist_sectors
taxons
world_locations
).freeze
# The fields for which facet examples are allowed to be requested.
# This is locked down because these can only be requested with the current
# version of elasticsearch by performing a separate query for each facet
# option. This is done using the msearch API to perform many queries
# together, but is still potentially expensive. They could be efficiently
# calculated with the top-documents aggregator in elasticsearch 1.3, so this
# restriction could be relaxed in future.
ALLOWED_FACET_EXAMPLE_FIELDS = %w(
format
mainstream_browse_pages
manual
organisations
specialist_sectors
).freeze
# The keys by which facet values can be sorted (using the "order" option).
# Multiple can be supplied, separated by colons - items which are equal
# according to the first option are sorted by the next key, etc. keys can be
# preceded with a "-" to sort in descending order.
# - filtered: sort fields which have filters applied to them first.
# - count: sort values by number of matching documents.
# - value: sort by value if string, sort by title if not a string
# - value.slug: sort values by the slug part of the value.
# - value.title: sort values by the title of the value.
# - value.link: sort values by the link of the value.
#
ALLOWED_FACET_SORT_OPTIONS = %w(
filtered
count
value
value.slug
value.title
value.link
).freeze
# Scopes that are allowed when requesting examples for facets
# - query: Return only examples that match the query and filters
# - global: Return examples for the facet regardless of whether they match
# the query and filters
ALLOWED_EXAMPLE_SCOPES = [:global, :query].freeze
# The fields which are returned by default for search results.
DEFAULT_RETURN_FIELDS = %w(
description
display_type
document_series
format
link
organisations
public_timestamp
slug
specialist_sectors
title
policy_areas
world_locations
topic_content_ids
expanded_topics
organisation_content_ids
expanded_organisations
).freeze
# Default order in which facet results are sorted
DEFAULT_FACET_SORT = [
[:filtered, 1],
[:count, -1],
[:slug, 1],
].freeze
# The fields which are returned by default for facet examples.
DEFAULT_FACET_EXAMPLE_FIELDS = %w(
link
title
).freeze
# A special value used to filter for missing fields.
MISSING_FIELD_SPECIAL_VALUE = "_MISSING".freeze
attr_reader :parsed_params, :errors
def valid?
@errors.empty?
end
protected
def parse_positive_integer(value, description)
begin
result = Integer(value, 10)
rescue ArgumentError
@errors << %{Invalid value "#{value}" for #{description} (expected positive integer)}
return nil
end
if result < 0
@errors << %{Invalid negative value "#{value}" for #{description} (expected positive integer)}
return nil
end
result
end
# Get a parameter that occurs at most once
# Returns the string value of the parameter, or nil
def single_param(param_name, description = "")
@used_params << param_name
values = @params.fetch(param_name, [])
if values.size > 1
@errors << %{Too many values (#{values.size}) for parameter "#{param_name}"#{description} (must occur at most once)}
end
values.first
end
# Get a parameter represented as a comma separated list
# Multiple occurrences of the parameter will be joined together
def character_separated_param(param_name, separator = ",")
@used_params << param_name
values = @params.fetch(param_name, [])
values.map { |value|
value.split(separator)
}.flatten
end
# Parse a parameter which should contain an integer and occur only once
# Returns the integer value, or the provided default
def single_integer_param(param_name, default, description = "")
value = single_param(param_name, description)
return default if value.nil?
value = parse_positive_integer(value, %{parameter "#{param_name}"#{description}})
return default if value.nil?
value
end
end