yes-read-api 2.7.0 → 2.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3619d601a3e567d6dc308faa9d760af33e029269ff7dd30438c64bfa1affd6c5
4
- data.tar.gz: 7464d17fdad8e36279a4af428805e87e9a26373de50f9b17dc4b5681f89db160
3
+ metadata.gz: d82a315c34db2028b08ec928dfa762687832077a9f2b5c49bf06c937a7759b61
4
+ data.tar.gz: b604a4bb25010bcf7e3180197fc1caf0dcbba43191849bb0a6e9249c3837e87c
5
5
  SHA512:
6
- metadata.gz: 96a9f8b748084398d5f3afd5b47418be939c77ae98bb934d547a9c1d03c2af07912007565c1783c40f3d165000d88badfb8a7ad6135f2f71230e0a86a3d90c32
7
- data.tar.gz: 1f7601458da94700a014a21e69e839de0fdad2253ec5f254255d7874533325744b881479b75dd14b6da51aff525bd201376bbf5ca4dd6c01fcced5a3953dcc89
6
+ metadata.gz: 5c772d5d935448c0a1137f16ff5b0afe6aea61181cd28d46e01fc96cf42d6e7304d232456f04f94a44cd5b1f9d006ad59974e0a658ff30b580f38a04663c6e18
7
+ data.tar.gz: 70d305e7f80e9a542654f7472718e300a9da5e27370011b2c0f8d1a7e4c3c93da8aa514e72e420e088d299a94a690f6162687199a81de13dcd678e14ad0da740
@@ -7,11 +7,15 @@ module Yes
7
7
  module Api
8
8
  class QueriesController < ApplicationController
9
9
  before_action :authenticate_with_token
10
+ before_action :reject_ignored_filter_param
10
11
  before_action :validate_advanced_payload, only: :advanced
11
12
  before_action :process_own_filter, only: :call
12
13
 
13
14
  RETRY_AFTER_SECONDS = 1
14
- IGNORED_FILTER_PARAM = { basic: :filter_definition, advanced: :filters }.freeze
15
+ FILTER_DEFINITION_REQUIRED = 'filter_definition is required for an advanced query'
16
+ FILTERS_IGNORED_BY_ADVANCED = 'filters is not applied by an advanced query, use filter_definition'
17
+ STORED_FILTER_WITHOUT_DEFINITION = 'the stored filter has no filter_definition'
18
+ FILTER_DEFINITION_IGNORED_BY_BASIC = 'filter_definition is not applied by a basic query, use filters'
15
19
 
16
20
  # Covers AuthenticationError raised outside #authenticate_with_token, e.g. by consumer
17
21
  # request authorizers, filters or serializers.
@@ -32,6 +36,8 @@ module Yes
32
36
  def call
33
37
  persisted_filter = filter(read_model_name).persisted_filter_scope.find_by(id: params[:filter_id]) if params[:filter_id].present?
34
38
 
39
+ return bad_request_response(STORED_FILTER_WITHOUT_DEFINITION) if without_definition?(persisted_filter)
40
+
35
41
  render json: response_json(persisted_filter:, filter_type: persisted_filter ? :advanced : :basic).to_json
36
42
  end
37
43
 
@@ -54,7 +60,7 @@ module Yes
54
60
  end
55
61
 
56
62
  def response_json(filter_type: :basic, persisted_filter: nil)
57
- filter_options = applied_filter_options(persisted_filter:, filter_type:)
63
+ filter_options = applied_filter_options(persisted_filter:)
58
64
 
59
65
  request_authorizer.call(filter_options, auth_data)
60
66
 
@@ -67,17 +73,34 @@ module Yes
67
73
  serialize(paginated_records, filter_options)
68
74
  end
69
75
 
70
- # The options the query is built from. They are what the request authorizer is handed too, so
71
- # it never authorizes a parameter the query does not apply: a basic query applies `filters` and
72
- # an advanced one `filter_definition`.
76
+ # The options the query is built from. They are what the request authorizer is handed too.
77
+ # A stored filter body replaces the request params and runs as an advanced query, which
78
+ # applies `filter_definition` only, so a `filters` key in the body is dropped.
73
79
  #
74
80
  # @param persisted_filter [#body, nil] stored filter whose body replaces the request params
75
- # @param filter_type [Symbol] `:basic` or `:advanced`
76
- # @return [Hash] the params without the key the filter type ignores
77
- def applied_filter_options(persisted_filter:, filter_type:)
78
- options = persisted_filter&.body&.deep_symbolize_keys&.merge(model: params[:model]) || params
81
+ # @return [Hash] the stored body (without `filters`) or the request params
82
+ def applied_filter_options(persisted_filter:)
83
+ return params unless persisted_filter
84
+
85
+ stored_filter_options(persisted_filter).except(:filters)
86
+ end
87
+
88
+ # An advanced query returns the unbounded base scope without a `filter_definition`.
89
+ #
90
+ # @param persisted_filter [#body, nil] stored filter
91
+ # @return [Boolean] whether a stored filter was found whose body has no `filter_definition`;
92
+ # a body that is not a Hash (the jsonb column also holds nil, arrays, strings) has none
93
+ def without_definition?(persisted_filter)
94
+ return false if persisted_filter.blank?
95
+ return true unless persisted_filter.body.is_a?(Hash)
79
96
 
80
- options.except(IGNORED_FILTER_PARAM.fetch(filter_type))
97
+ stored_filter_options(persisted_filter)[:filter_definition].blank?
98
+ end
99
+
100
+ # @param persisted_filter [#body] stored filter with a Hash body
101
+ # @return [Hash] its body with symbol keys and the requested model
102
+ def stored_filter_options(persisted_filter)
103
+ persisted_filter.body.deep_symbolize_keys.merge(model: params[:model])
81
104
  end
82
105
 
83
106
  def request_authorizer
@@ -162,9 +185,31 @@ module Yes
162
185
  )
163
186
  end
164
187
 
165
- def validate_advanced_payload
166
- return if params[:filter_definition].blank?
188
+ # Rejects a request that carries the filter parameter its query does not apply, or lacks the one
189
+ # it needs. Request authorizers pick what to check by which parameter is present, so a parameter
190
+ # the query silently ignores could be authorized without bounding the result. It runs as a
191
+ # before_action, so consumer actions of subclasses are covered too.
192
+ #
193
+ # @return [void]
194
+ def reject_ignored_filter_param
195
+ if request.post?
196
+ return bad_request_response(FILTER_DEFINITION_REQUIRED) if params[:filter_definition].blank?
197
+
198
+ bad_request_response(FILTERS_IGNORED_BY_ADVANCED) if params.key?(:filters)
199
+ elsif params.key?(:filter_definition)
200
+ bad_request_response(FILTER_DEFINITION_IGNORED_BY_BASIC)
201
+ end
202
+ end
203
+
204
+ # Renders a 400 with the reason the request is malformed.
205
+ #
206
+ # @param detail [String] why the request is rejected and what to send instead
207
+ # @return [void]
208
+ def bad_request_response(detail)
209
+ render(json: { title: 'Bad Request', detail: }.to_json, status: :bad_request)
210
+ end
167
211
 
212
+ def validate_advanced_payload
168
213
  validation_result = Yes::Read::Api::AdvancedFilterValidator.call(params)
169
214
  return if validation_result.success?
170
215
 
@@ -3,7 +3,7 @@
3
3
  module Yes
4
4
  module Read
5
5
  module Api
6
- VERSION = '2.7.0'
6
+ VERSION = '2.8.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yes-read-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nico Ritsche