apiculture 0.0.16 → 0.0.17

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
  SHA1:
3
- metadata.gz: fddefec311625b3874fbcf1f2359f17d9393ae03
4
- data.tar.gz: 2006215622d1839c0316d1bd1c790b65b08e6fb5
3
+ metadata.gz: 917fb8bfc86e8bc1d1a9d02729434c6af8398c1b
4
+ data.tar.gz: 4c73cab2e1f0a8dfb2756175f1037f32a8336d0b
5
5
  SHA512:
6
- metadata.gz: 3933f460a2b5b2cd1ea3187d8b4023851efc82ad5b555b55decb0922751f198336998f59d16262ca522f2ef097c26e6978a063be122b842bf1ecd6977b7bacff
7
- data.tar.gz: b8f2c0ed089a32b45b5edeb98aea9fe1110b07a92f3df5aa0497c24ccb61e602fcfc547d1b4a0d011a631ca5b871c3ab198569dee3ec04b6b78538392e1ce1fd
6
+ metadata.gz: d0d6b8796a963cd7814bba03afc1904efeb23e901084eab438ce2af8a6379f5e7b5d1474f89cee53401e6d58d94c68be5290c8ed9677b169b9fd85df98cd4117
7
+ data.tar.gz: 33a2024a4918f561da343f6df0aa0876b1c458f5ffc9f02d1393284f64af5b5c06811cd65022cb26fa28d9407c332d52f048adcdafdbaa7f7bfc990c52a1eb58
data/apiculture.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: apiculture 0.0.16 ruby lib
5
+ # stub: apiculture 0.0.17 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "apiculture"
9
- s.version = "0.0.16"
9
+ s.version = "0.0.17"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Julik Tarkhanov", "WeTransfer"]
14
- s.date = "2016-09-27"
14
+ s.date = "2016-11-25"
15
15
  s.description = "A toolkit for building REST APIs on top of Sinatra"
16
16
  s.email = "me@julik.nl"
17
17
  s.extra_rdoc_files = [
@@ -43,7 +43,7 @@ Gem::Specification.new do |s|
43
43
  ]
44
44
  s.homepage = "https://github.com/WeTransfer/apiculture"
45
45
  s.licenses = ["MIT"]
46
- s.rubygems_version = "2.5.1"
46
+ s.rubygems_version = "2.4.5.1"
47
47
  s.summary = "Sweet API sauce on top of Sintra"
48
48
 
49
49
  if s.respond_to? :specification_version then
@@ -1,3 +1,3 @@
1
1
  module Apiculture
2
- VERSION = '0.0.16'
3
- end
2
+ VERSION = '0.0.17'
3
+ end
data/lib/apiculture.rb CHANGED
@@ -26,10 +26,6 @@ module Apiculture
26
26
  param.matchable === value or raise ParameterTypeMismatch.new(param, value.class)
27
27
  }
28
28
 
29
- AC_PERMIT_PROC = ->(maybe_strong_params, param_name) {
30
- maybe_strong_params.permit(param_name) if maybe_strong_params.respond_to?(:permit)
31
- }
32
-
33
29
  class Parameter < Struct.new(:name, :description, :required, :matchable, :cast_proc_or_method)
34
30
  # Return Strings since Sinatra prefers string keys for params{}
35
31
  def name_as_string; name.to_s; end
@@ -154,7 +150,7 @@ module Apiculture
154
150
  end
155
151
 
156
152
  # Returns a Proc that calls the strong parameters to check the presence/types
157
- def parametric_validator_proc_from(parametric_validators)
153
+ def parametric_validator_proc_from(parametric_validators, implicitly_defined_route_parameter_names)
158
154
  required_params = parametric_validators.select{|e| e.required }
159
155
  # Return a lambda that will be called with the Sinatra params
160
156
  parametric_validation_blk = ->{
@@ -171,14 +167,16 @@ module Apiculture
171
167
 
172
168
  # Ensure the typecast value adheres to the enforced Ruby type
173
169
  AC_CHECK_TYPE_PROC.call(param, params[param_name])
174
- # ..permit it in the strong parameters if we support them
175
- AC_PERMIT_PROC.call(params, param_name)
176
170
  end
177
171
 
178
172
  # The following only applies if the app does not use strong_parameters -
179
173
  # this makes use of parameter mutability again to kill the parameters that are not permitted
180
- # or mentioned in the API specification
181
- unexpected_parameters = params.keys.map(&:to_s) - parametric_validators.map(&:name).map(&:to_s)
174
+ # or mentioned in the API specification. We need to keep the params which are specified in the
175
+ # route but not documented via Apiculture though
176
+ unexpected_parameters = Set.new(params.keys.map(&:to_s)) -
177
+ Set.new(parametric_validators.map(&:name).map(&:to_s)) -
178
+ Set.new(implicitly_defined_route_parameter_names.map(&:to_s))
179
+
182
180
  unexpected_parameters.each do | parameter_to_discard |
183
181
  # TODO: raise or record a warning
184
182
  if env['rack.logger'].respond_to?(:warn)
@@ -243,23 +241,30 @@ module Apiculture
243
241
 
244
242
  # Pick out all the defined parameters and set up a block that can validate them
245
243
  # when the action is called. With that, set up the actual Sinatra method that will
246
- # respond to the request.
247
- parametric_checker_proc = parametric_validator_proc_from(action_def.parameters + action_def.route_parameters)
244
+ # respond to the request. We take care to preserve all the params that have NOT been documented
245
+ # using Apiculture but _were_ in fact specified in the actual path.
246
+ route_parameter_names = path.scan(/:([^:\/]+)/).flatten.map(&:to_sym)
247
+ parametric_checker_proc = parametric_validator_proc_from(action_def.parameters + action_def.route_parameters, route_parameter_names)
248
248
  public_send(http_verb, path, options) do |*matched_sinatra_route_params|
249
- route_params = []
250
- action_def.route_parameters.each_with_index do |route_param, index|
249
+ # Extract all the parameter names from the route path as given to the method
250
+ route_parameters = Hash[route_parameter_names.zip(matched_sinatra_route_params)]
251
+
252
+ # Apply route parameter checks, but only to params that were defined in the Apiculture action descriptor.
253
+ # All the other params have to go via bypass.
254
+ checked_route_parameters = action_def.route_parameters.select {|par| route_parameter_names.include?(par.name) }
255
+ checked_route_parameters.each do |route_param|
251
256
  # Apply the type cast and save it (since using our override we can mutate the params)
252
- value_after_type_cast = AC_APPLY_TYPECAST_PROC.call(route_param.cast_proc_or_method, params[route_param.name])
253
- route_params[index] = value_after_type_cast
254
-
257
+ value_from_route_params = route_parameters.fetch(route_param.name)
258
+ value_after_type_cast = AC_APPLY_TYPECAST_PROC.call(route_param.cast_proc_or_method, value_from_route_params)
255
259
  # Ensure the typecast value adheres to the enforced Ruby type
256
- AC_CHECK_TYPE_PROC.call(route_param, route_params[index])
257
- # ..permit it in the strong parameters if we support them
258
- AC_PERMIT_PROC.call(route_params, route_param.name)
260
+ AC_CHECK_TYPE_PROC.call(route_param, value_after_type_cast)
261
+ # ..and overwrite it in the route parameters hash
262
+ route_parameters[route_param.name] = value_after_type_cast
259
263
  end
264
+ # Execute parametric checks on all the OTHER params (forms etc.)
260
265
  instance_exec(&parametric_checker_proc)
261
266
  # Execute the original action via instance_exec, passing along the route args
262
- instance_exec(*route_params, &blk)
267
+ instance_exec(*route_parameters.values, &blk)
263
268
  end
264
269
 
265
270
  # Reset for the subsequent action definition
@@ -191,7 +191,33 @@ describe "Apiculture" do
191
191
  post '/thing', {evil_ssh_injection: 'I am Homakov!'}
192
192
  expect(last_response).to be_ok
193
193
  end
194
-
194
+
195
+ it 'allows route parameters that are not mentioned in the action definition, but are given in Sinatra path' do
196
+ @app_class = Class.new(Sinatra::Base) do
197
+ settings.show_exceptions = false
198
+ settings.raise_errors = true
199
+ extend Apiculture
200
+
201
+ api_method :post, '/api-thing/:id_of_thing' do |id|
202
+ raise 'id_of_thing must be passed' unless id == '123456'
203
+ raise "id_of_thing must be present in params, but they were #{params.inspect}" unless params.keys.include?('id_of_thing')
204
+ 'All is well'
205
+ end
206
+
207
+ post '/vanilla-thing/:id_of_thing' do |id|
208
+ raise 'id_of_thing must be passed' unless id == '123456'
209
+ raise "id_of_thing must be present in params, but they were #{params.inspect}" unless params.keys.include?('id_of_thing')
210
+ 'All is well'
211
+ end
212
+ end
213
+
214
+ post '/vanilla-thing/123456'
215
+ expect(last_response).to be_ok
216
+
217
+ post '/api-thing/123456'
218
+ expect(last_response).to be_ok
219
+ end
220
+
195
221
  it 'raises when describing a route parameter that is not included in the path' do
196
222
  expect {
197
223
  Class.new(Sinatra::Base) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apiculture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-09-27 00:00:00.000000000 Z
12
+ date: 2016-11-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -220,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  version: '0'
221
221
  requirements: []
222
222
  rubyforge_project:
223
- rubygems_version: 2.5.1
223
+ rubygems_version: 2.4.5.1
224
224
  signing_key:
225
225
  specification_version: 4
226
226
  summary: Sweet API sauce on top of Sintra