grape 1.1.0 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -43
  3. data/LICENSE +1 -1
  4. data/README.md +394 -47
  5. data/UPGRADING.md +111 -0
  6. data/grape.gemspec +3 -1
  7. data/lib/grape.rb +98 -66
  8. data/lib/grape/api.rb +136 -175
  9. data/lib/grape/api/instance.rb +280 -0
  10. data/lib/grape/config.rb +32 -0
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/desc.rb +39 -7
  13. data/lib/grape/dsl/inside_route.rb +12 -6
  14. data/lib/grape/dsl/middleware.rb +7 -0
  15. data/lib/grape/dsl/parameters.rb +9 -4
  16. data/lib/grape/dsl/routing.rb +5 -1
  17. data/lib/grape/dsl/validations.rb +4 -3
  18. data/lib/grape/eager_load.rb +18 -0
  19. data/lib/grape/endpoint.rb +42 -26
  20. data/lib/grape/error_formatter.rb +1 -1
  21. data/lib/grape/exceptions/base.rb +9 -1
  22. data/lib/grape/exceptions/invalid_response.rb +9 -0
  23. data/lib/grape/exceptions/validation_errors.rb +4 -2
  24. data/lib/grape/formatter.rb +1 -1
  25. data/lib/grape/locale/en.yml +2 -0
  26. data/lib/grape/middleware/auth/base.rb +2 -4
  27. data/lib/grape/middleware/base.rb +2 -0
  28. data/lib/grape/middleware/error.rb +9 -4
  29. data/lib/grape/middleware/helpers.rb +10 -0
  30. data/lib/grape/middleware/stack.rb +1 -1
  31. data/lib/grape/middleware/versioner/header.rb +4 -4
  32. data/lib/grape/parser.rb +1 -1
  33. data/lib/grape/request.rb +1 -1
  34. data/lib/grape/router/attribute_translator.rb +2 -0
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/util/base_inheritable.rb +34 -0
  37. data/lib/grape/util/endpoint_configuration.rb +6 -0
  38. data/lib/grape/util/inheritable_values.rb +5 -25
  39. data/lib/grape/util/lazy_block.rb +25 -0
  40. data/lib/grape/util/lazy_value.rb +95 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  42. data/lib/grape/util/stackable_values.rb +19 -22
  43. data/lib/grape/validations/attributes_iterator.rb +5 -3
  44. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  45. data/lib/grape/validations/params_scope.rb +20 -14
  46. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/file.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +6 -11
  50. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  51. data/lib/grape/validations/validators/as.rb +2 -3
  52. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  53. data/lib/grape/validations/validators/base.rb +11 -10
  54. data/lib/grape/validations/validators/coerce.rb +4 -0
  55. data/lib/grape/validations/validators/default.rb +1 -1
  56. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  57. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  58. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  59. data/lib/grape/validations/validators/same_as.rb +23 -0
  60. data/lib/grape/version.rb +1 -1
  61. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  62. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  63. data/spec/grape/api_remount_spec.rb +466 -0
  64. data/spec/grape/api_spec.rb +379 -1
  65. data/spec/grape/config_spec.rb +17 -0
  66. data/spec/grape/dsl/desc_spec.rb +40 -16
  67. data/spec/grape/dsl/middleware_spec.rb +8 -0
  68. data/spec/grape/dsl/routing_spec.rb +10 -0
  69. data/spec/grape/endpoint_spec.rb +40 -4
  70. data/spec/grape/exceptions/base_spec.rb +65 -0
  71. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  72. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  73. data/spec/grape/integration/rack_spec.rb +22 -6
  74. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  75. data/spec/grape/middleware/base_spec.rb +8 -0
  76. data/spec/grape/middleware/exception_spec.rb +1 -1
  77. data/spec/grape/middleware/formatter_spec.rb +15 -5
  78. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  79. data/spec/grape/named_api_spec.rb +19 -0
  80. data/spec/grape/request_spec.rb +24 -0
  81. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  82. data/spec/grape/validations/params_scope_spec.rb +184 -8
  83. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  84. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  85. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  86. data/spec/grape/validations/validators/coerce_spec.rb +10 -2
  87. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  88. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  89. data/spec/grape/validations/validators/same_as_spec.rb +63 -0
  90. data/spec/grape/validations_spec.rb +33 -21
  91. data/spec/spec_helper.rb +4 -1
  92. metadata +35 -23
  93. data/Appraisals +0 -32
  94. data/Dangerfile +0 -2
  95. data/Gemfile +0 -33
  96. data/Gemfile.lock +0 -231
  97. data/Guardfile +0 -10
  98. data/RELEASING.md +0 -111
  99. data/Rakefile +0 -25
  100. data/benchmark/simple.rb +0 -27
  101. data/benchmark/simple_with_type_coercer.rb +0 -22
  102. data/gemfiles/multi_json.gemfile +0 -35
  103. data/gemfiles/multi_xml.gemfile +0 -35
  104. data/gemfiles/rack_1.5.2.gemfile +0 -35
  105. data/gemfiles/rack_edge.gemfile +0 -35
  106. data/gemfiles/rails_3.gemfile +0 -36
  107. data/gemfiles/rails_4.gemfile +0 -35
  108. data/gemfiles/rails_5.gemfile +0 -35
  109. data/gemfiles/rails_edge.gemfile +0 -35
  110. data/pkg/grape-0.17.0.gem +0 -0
  111. data/pkg/grape-0.19.0.gem +0 -0
@@ -158,7 +158,7 @@ module Grape
158
158
  # necessary.
159
159
  def enforce_symbolized_keys(type, method)
160
160
  # Collections have all values processed individually
161
- if type == Array || type == Set
161
+ if [Array, Set].include?(type)
162
162
  lambda do |val|
163
163
  method.call(val).tap do |new_value|
164
164
  new_value.map do |item|
@@ -19,7 +19,7 @@ module Grape
19
19
  # Rack::Request creates a Hash with filename,
20
20
  # content type and an IO object. Do a bit of basic
21
21
  # duck-typing.
22
- value.is_a?(::Hash) && value.key?(:tempfile)
22
+ value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile)
23
23
  end
24
24
  end
25
25
  end
@@ -1,17 +1,12 @@
1
1
  module Grape
2
2
  module Validations
3
3
  class ValidatorFactory
4
- def initialize(**options)
5
- @validator_class = options.delete(:validator_class)
6
- @options = options
7
- end
8
-
9
- def create_validator
10
- @validator_class.new(@options[:attributes],
11
- @options[:options],
12
- @options[:required],
13
- @options[:params_scope],
14
- @options[:opts])
4
+ def self.create_validator(**options)
5
+ options[:validator_class].new(options[:attributes],
6
+ options[:options],
7
+ options[:required],
8
+ options[:params_scope],
9
+ options[:opts])
15
10
  end
16
11
  end
17
12
  end
@@ -1,19 +1,12 @@
1
+ require 'grape/validations/validators/multiple_params_base'
2
+
1
3
  module Grape
2
4
  module Validations
3
- require 'grape/validations/validators/multiple_params_base'
4
5
  class AllOrNoneOfValidator < MultipleParamsBase
5
- def validate!(params)
6
- super
7
- if scope_requires_params && only_subset_present
8
- raise Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none)
9
- end
10
- params
11
- end
12
-
13
- private
14
-
15
- def only_subset_present
16
- scoped_params.any? { |resource_params| !keys_in_common(resource_params).empty? && keys_in_common(resource_params).length < attrs.length }
6
+ def validate_params!(params)
7
+ keys = keys_in_common(params)
8
+ return if keys.empty? || keys.length == all_keys.length
9
+ raise Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none)
17
10
  end
18
11
  end
19
12
  end
@@ -2,13 +2,12 @@ module Grape
2
2
  module Validations
3
3
  class AsValidator < Base
4
4
  def initialize(attrs, options, required, scope, opts = {})
5
- @alias = options
5
+ @renamed_options = options
6
6
  super
7
7
  end
8
8
 
9
9
  def validate_param!(attr_name, params)
10
- params[@alias] = params[attr_name]
11
- params.delete(attr_name)
10
+ params[@renamed_options] = params[attr_name]
12
11
  end
13
12
  end
14
13
  end
@@ -1,19 +1,11 @@
1
+ require 'grape/validations/validators/multiple_params_base'
2
+
1
3
  module Grape
2
4
  module Validations
3
- require 'grape/validations/validators/multiple_params_base'
4
5
  class AtLeastOneOfValidator < MultipleParamsBase
5
- def validate!(params)
6
- super
7
- if scope_requires_params && no_exclusive_params_are_present
8
- raise Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one)
9
- end
10
- params
11
- end
12
-
13
- private
14
-
15
- def no_exclusive_params_are_present
16
- scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
6
+ def validate_params!(params)
7
+ return unless keys_in_common(params).empty?
8
+ raise Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one)
17
9
  end
18
10
  end
19
11
  end
@@ -35,18 +35,19 @@ module Grape
35
35
  # @raise [Grape::Exceptions::Validation] if validation failed
36
36
  # @return [void]
37
37
  def validate!(params)
38
- attributes = AttributesIterator.new(self, @scope, params)
38
+ attributes = SingleAttributeIterator.new(self, @scope, params)
39
+ # we collect errors inside array because
40
+ # there may be more than one error per field
39
41
  array_errors = []
42
+
40
43
  attributes.each do |resource_params, attr_name|
41
44
  next if !@scope.required? && resource_params.empty?
42
- next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
43
45
  next unless @scope.meets_dependency?(resource_params, params)
44
-
45
46
  begin
46
- validate_param!(attr_name, resource_params)
47
+ if @required || resource_params.respond_to?(:key?) && resource_params.key?(attr_name)
48
+ validate_param!(attr_name, resource_params)
49
+ end
47
50
  rescue Grape::Exceptions::Validation => e
48
- # we collect errors inside array because
49
- # there may be more than one error per field
50
51
  array_errors << e
51
52
  end
52
53
  end
@@ -56,10 +57,10 @@ module Grape
56
57
 
57
58
  def self.convert_to_short_name(klass)
58
59
  ret = klass.name.gsub(/::/, '/')
59
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
60
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
61
- .tr('-', '_')
62
- .downcase
60
+ ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
61
+ ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
62
+ ret.tr!('-', '_')
63
+ ret.downcase!
63
64
  File.basename(ret, '_validator')
64
65
  end
65
66
 
@@ -1,6 +1,10 @@
1
1
  module Grape
2
2
  class API
3
3
  Boolean = Virtus::Attribute::Boolean
4
+
5
+ class Instance
6
+ Boolean = Virtus::Attribute::Boolean
7
+ end
4
8
  end
5
9
 
6
10
  module Validations
@@ -18,7 +18,7 @@ module Grape
18
18
  end
19
19
 
20
20
  def validate!(params)
21
- attrs = AttributesIterator.new(self, @scope, params)
21
+ attrs = SingleAttributeIterator.new(self, @scope, params)
22
22
  attrs.each do |resource_params, attr_name|
23
23
  if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
24
24
  validate_param!(attr_name, resource_params)
@@ -1,28 +1,11 @@
1
+ require 'grape/validations/validators/multiple_params_base'
2
+
1
3
  module Grape
2
4
  module Validations
3
- require 'grape/validations/validators/mutual_exclusion'
4
- class ExactlyOneOfValidator < MutualExclusionValidator
5
- def validate!(params)
6
- super
7
- if scope_requires_params && none_of_restricted_params_is_present
8
- raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one)
9
- end
10
- params
11
- end
12
-
13
- def message(default_key = nil)
14
- options = instance_variable_get(:@option)
15
- if options_key?(:message)
16
- (options_key?(default_key, options[:message]) ? options[:message][default_key] : options[:message])
17
- else
18
- default_key
19
- end
20
- end
21
-
22
- private
23
-
24
- def none_of_restricted_params_is_present
25
- scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
5
+ class ExactlyOneOfValidator < MultipleParamsBase
6
+ def validate_params!(params)
7
+ return if keys_in_common(params).length == 1
8
+ raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one)
26
9
  end
27
10
  end
28
11
  end
@@ -1,26 +1,30 @@
1
1
  module Grape
2
2
  module Validations
3
3
  class MultipleParamsBase < Base
4
- attr_reader :scoped_params
5
-
6
4
  def validate!(params)
7
- @scoped_params = [@scope.params(params)].flatten
8
- params
9
- end
5
+ attributes = MultipleAttributesIterator.new(self, @scope, params)
6
+ array_errors = []
10
7
 
11
- private
8
+ attributes.each do |resource_params|
9
+ begin
10
+ validate_params!(resource_params)
11
+ rescue Grape::Exceptions::Validation => e
12
+ array_errors << e
13
+ end
14
+ end
12
15
 
13
- def scope_requires_params
14
- @scope.required? || scoped_params.any?(&:any?)
16
+ raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any?
15
17
  end
16
18
 
19
+ private
20
+
17
21
  def keys_in_common(resource_params)
18
22
  return [] unless resource_params.is_a?(Hash)
19
- (all_keys & resource_params.stringify_keys.keys).map(&:to_s)
23
+ all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) }
20
24
  end
21
25
 
22
26
  def all_keys
23
- attrs.map(&:to_s)
27
+ attrs.map { |attr| @scope.full_name(attr) }
24
28
  end
25
29
  end
26
30
  end
@@ -1,24 +1,12 @@
1
+ require 'grape/validations/validators/multiple_params_base'
2
+
1
3
  module Grape
2
4
  module Validations
3
- require 'grape/validations/validators/multiple_params_base'
4
5
  class MutualExclusionValidator < MultipleParamsBase
5
- attr_reader :processing_keys_in_common
6
-
7
- def validate!(params)
8
- super
9
- if two_or_more_exclusive_params_are_present
10
- raise Grape::Exceptions::Validation, params: processing_keys_in_common, message: message(:mutual_exclusion)
11
- end
12
- params
13
- end
14
-
15
- private
16
-
17
- def two_or_more_exclusive_params_are_present
18
- scoped_params.any? do |resource_params|
19
- @processing_keys_in_common = keys_in_common(resource_params)
20
- @processing_keys_in_common.length > 1
21
- end
6
+ def validate_params!(params)
7
+ keys = keys_in_common(params)
8
+ return if keys.length <= 1
9
+ raise Grape::Exceptions::Validation, params: keys, message: message(:mutual_exclusion)
22
10
  end
23
11
  end
24
12
  end
@@ -0,0 +1,23 @@
1
+ module Grape
2
+ module Validations
3
+ class SameAsValidator < Base
4
+ def validate_param!(attr_name, params)
5
+ confirmation = options_key?(:value) ? @option[:value] : @option
6
+ return if params[attr_name] == params[confirmation]
7
+ raise Grape::Exceptions::Validation,
8
+ params: [@scope.full_name(attr_name)],
9
+ message: build_message
10
+ end
11
+
12
+ private
13
+
14
+ def build_message
15
+ if options_key?(:message)
16
+ @option[:message]
17
+ else
18
+ format I18n.t(:same_as, scope: 'grape.errors.messages'), parameter: @option
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '1.1.0'.freeze
3
+ VERSION = '1.2.5'.freeze
4
4
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::API::Instance do
4
+ describe 'boolean constant' do
5
+ module DefinesBooleanInstanceSpec
6
+ class API < Grape::API
7
+ params do
8
+ requires :message, type: Boolean
9
+ end
10
+ post :echo do
11
+ { class: params[:message].class.name, value: params[:message] }
12
+ end
13
+ end
14
+ end
15
+
16
+ def app
17
+ DefinesBooleanInstanceSpec::API
18
+ end
19
+
20
+ let(:expected_body) do
21
+ { class: 'TrueClass', value: true }.to_s
22
+ end
23
+
24
+ it 'sets Boolean as a Virtus::Attribute::Boolean' do
25
+ post '/echo?message=true'
26
+ expect(last_response.status).to eq(201)
27
+ expect(last_response.body).to eq expected_body
28
+ end
29
+
30
+ context 'Params endpoint type' do
31
+ subject { DefinesBooleanInstanceSpec::API.new.router.map['POST'].first.options[:params]['message'][:type] }
32
+ it 'params type is a Virtus::Attribute::Boolean' do
33
+ is_expected.to eq 'Virtus::Attribute::Boolean'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Endpoint do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ context 'get' do
11
+ it 'routes to a namespace param with dots' do
12
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
13
+ get '/' do
14
+ params[:ns_with_dots]
15
+ end
16
+ end
17
+
18
+ get '/test.id.with.dots'
19
+ expect(last_response.status).to eq 200
20
+ expect(last_response.body).to eq 'test.id.with.dots'
21
+ end
22
+
23
+ it 'routes to a path with multiple params with dots' do
24
+ subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+},
25
+ another_id_with_dots: %r{[^\/]+} } do
26
+ "#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
27
+ end
28
+
29
+ get '/test.id/test2.id'
30
+ expect(last_response.status).to eq 200
31
+ expect(last_response.body).to eq 'test.id/test2.id'
32
+ end
33
+
34
+ it 'routes to namespace and path params with dots, with overridden requirements' do
35
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
36
+ get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^\/]+},
37
+ another_id_with_dots: %r{[^\/]+} } do
38
+ "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
39
+ end
40
+ end
41
+
42
+ get '/test.id/test2.id'
43
+ expect(last_response.status).to eq 200
44
+ expect(last_response.body).to eq 'test.id/test2.id'
45
+ end
46
+
47
+ it 'routes to namespace and path params with dots, with merged requirements' do
48
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
49
+ get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^\/]+} } do
50
+ "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
51
+ end
52
+ end
53
+
54
+ get '/test.id/test2.id'
55
+ expect(last_response.status).to eq 200
56
+ expect(last_response.body).to eq 'test.id/test2.id'
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,466 @@
1
+ require 'spec_helper'
2
+ require 'shared/versioning_examples'
3
+
4
+ describe Grape::API do
5
+ subject(:a_remounted_api) { Class.new(Grape::API) }
6
+ let(:root_api) { Class.new(Grape::API) }
7
+
8
+ def app
9
+ root_api
10
+ end
11
+
12
+ describe 'remounting an API' do
13
+ context 'with a defined route' do
14
+ before do
15
+ a_remounted_api.get '/votes' do
16
+ '10 votes'
17
+ end
18
+ end
19
+
20
+ context 'when mounting one instance' do
21
+ before do
22
+ root_api.mount a_remounted_api
23
+ end
24
+
25
+ it 'can access the endpoint' do
26
+ get '/votes'
27
+ expect(last_response.body).to eql '10 votes'
28
+ end
29
+ end
30
+
31
+ context 'when mounting twice' do
32
+ before do
33
+ root_api.mount a_remounted_api => '/posts'
34
+ root_api.mount a_remounted_api => '/comments'
35
+ end
36
+
37
+ it 'can access the votes in both places' do
38
+ get '/posts/votes'
39
+ expect(last_response.body).to eql '10 votes'
40
+ get '/comments/votes'
41
+ expect(last_response.body).to eql '10 votes'
42
+ end
43
+ end
44
+
45
+ context 'when mounting on namespace' do
46
+ before do
47
+ stub_const('StaticRefToAPI', a_remounted_api)
48
+ root_api.namespace 'posts' do
49
+ mount StaticRefToAPI
50
+ end
51
+
52
+ root_api.namespace 'comments' do
53
+ mount StaticRefToAPI
54
+ end
55
+ end
56
+
57
+ it 'can access the votes in both places' do
58
+ get '/posts/votes'
59
+ expect(last_response.body).to eql '10 votes'
60
+ get '/comments/votes'
61
+ expect(last_response.body).to eql '10 votes'
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'with dynamic configuration' do
67
+ context 'when mounting an endpoint conditional on a configuration' do
68
+ subject(:a_remounted_api) do
69
+ Class.new(Grape::API) do
70
+ get 'always' do
71
+ 'success'
72
+ end
73
+
74
+ given configuration[:mount_sometimes] do
75
+ get 'sometimes' do
76
+ 'sometimes'
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ it 'mounts the endpoints only when configured to do so' do
83
+ root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true })
84
+ root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false })
85
+
86
+ get '/with_conditional/always'
87
+ expect(last_response.body).to eq 'success'
88
+
89
+ get '/with_conditional/sometimes'
90
+ expect(last_response.body).to eq 'sometimes'
91
+
92
+ get '/without_conditional/always'
93
+ expect(last_response.body).to eq 'success'
94
+
95
+ get '/without_conditional/sometimes'
96
+ expect(last_response.status).to eq 404
97
+ end
98
+ end
99
+
100
+ context 'when using an expression derived from a configuration' do
101
+ subject(:a_remounted_api) do
102
+ Class.new(Grape::API) do
103
+ get(mounted { "api_name_#{configuration[:api_name]}" }) do
104
+ 'success'
105
+ end
106
+ end
107
+ end
108
+
109
+ before do
110
+ root_api.mount a_remounted_api, with: {
111
+ api_name: 'a_name'
112
+ }
113
+ end
114
+
115
+ it 'mounts the endpoint with the name' do
116
+ get 'api_name_a_name'
117
+ expect(last_response.body).to eq 'success'
118
+ end
119
+
120
+ it 'does not mount the endpoint with a null name' do
121
+ get 'api_name_'
122
+ expect(last_response.body).not_to eq 'success'
123
+ end
124
+
125
+ context 'when the expression lives in a namespace' do
126
+ subject(:a_remounted_api) do
127
+ Class.new(Grape::API) do
128
+ namespace :base do
129
+ get(mounted { "api_name_#{configuration[:api_name]}" }) do
130
+ 'success'
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ it 'mounts the endpoint with the name' do
137
+ get 'base/api_name_a_name'
138
+ expect(last_response.body).to eq 'success'
139
+ end
140
+
141
+ it 'does not mount the endpoint with a null name' do
142
+ get 'base/api_name_'
143
+ expect(last_response.body).not_to eq 'success'
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'when executing a standard block within a `mounted` block with all dynamic params' do
149
+ subject(:a_remounted_api) do
150
+ Class.new(Grape::API) do
151
+ mounted do
152
+ desc configuration[:description] do
153
+ headers configuration[:headers]
154
+ end
155
+ get configuration[:endpoint] do
156
+ configuration[:response]
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ let(:api_endpoint) { 'custom_endpoint' }
163
+ let(:api_response) { 'custom response' }
164
+ let(:endpoint_description) { 'this is a custom API' }
165
+ let(:headers) do
166
+ {
167
+ 'XAuthToken' => {
168
+ 'description' => 'Validates your identity',
169
+ 'required' => true
170
+ }
171
+ }
172
+ end
173
+
174
+ it 'mounts the API and obtains the description and headers definition' do
175
+ root_api.mount a_remounted_api, with: {
176
+ description: endpoint_description,
177
+ headers: headers,
178
+ endpoint: api_endpoint,
179
+ response: api_response
180
+ }
181
+ get api_endpoint
182
+ expect(last_response.body).to eq api_response
183
+ expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:description])
184
+ .to eq endpoint_description
185
+ expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:headers])
186
+ .to eq headers
187
+ end
188
+ end
189
+
190
+ context 'when executing a custom block on mount' do
191
+ subject(:a_remounted_api) do
192
+ Class.new(Grape::API) do
193
+ get 'always' do
194
+ 'success'
195
+ end
196
+
197
+ mounted do
198
+ configuration[:endpoints].each do |endpoint_name, endpoint_response|
199
+ get endpoint_name do
200
+ endpoint_response
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ it 'mounts the endpoints only when configured to do so' do
208
+ root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } }
209
+ get 'api_name'
210
+ expect(last_response.body).to eq 'api_response'
211
+ end
212
+ end
213
+
214
+ context 'when the configuration is part of the arguments of a method' do
215
+ subject(:a_remounted_api) do
216
+ Class.new(Grape::API) do
217
+ get configuration[:endpoint_name] do
218
+ 'success'
219
+ end
220
+ end
221
+ end
222
+
223
+ it 'mounts the endpoint in the location it is configured' do
224
+ root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' }
225
+ get '/some_location'
226
+ expect(last_response.body).to eq 'success'
227
+
228
+ get '/different_location'
229
+ expect(last_response.status).to eq 404
230
+
231
+ root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' }
232
+ get '/new_location'
233
+ expect(last_response.body).to eq 'success'
234
+ end
235
+
236
+ context 'when the configuration is the value in a key-arg pair' do
237
+ subject(:a_remounted_api) do
238
+ Class.new(Grape::API) do
239
+ version 'v1', using: :param, parameter: configuration[:version_param]
240
+ get 'endpoint' do
241
+ 'version 1'
242
+ end
243
+
244
+ version 'v2', using: :param, parameter: configuration[:version_param]
245
+ get 'endpoint' do
246
+ 'version 2'
247
+ end
248
+ end
249
+ end
250
+
251
+ it 'takes the param from the configuration' do
252
+ root_api.mount a_remounted_api, with: { version_param: 'param_name' }
253
+
254
+ get '/endpoint?param_name=v1'
255
+ expect(last_response.body).to eq 'version 1'
256
+
257
+ get '/endpoint?param_name=v2'
258
+ expect(last_response.body).to eq 'version 2'
259
+
260
+ get '/endpoint?wrong_param_name=v2'
261
+ expect(last_response.body).to eq 'version 1'
262
+ end
263
+ end
264
+ end
265
+
266
+ context 'on the DescSCope' do
267
+ subject(:a_remounted_api) do
268
+ Class.new(Grape::API) do
269
+ desc 'The description of this' do
270
+ tags ['not_configurable_tag', configuration[:a_configurable_tag]]
271
+ end
272
+ get 'location' do
273
+ 'success'
274
+ end
275
+ end
276
+ end
277
+
278
+ it 'mounts the endpoint with the appropiate tags' do
279
+ root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' })
280
+ end
281
+ end
282
+
283
+ context 'on the ParamScope' do
284
+ subject(:a_remounted_api) do
285
+ Class.new(Grape::API) do
286
+ params do
287
+ requires configuration[:required_param], type: configuration[:required_type]
288
+ end
289
+
290
+ get 'location' do
291
+ 'success'
292
+ end
293
+ end
294
+ end
295
+
296
+ it 'mounts the endpoint in the location it is configured' do
297
+ root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String })
298
+ root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer })
299
+
300
+ get '/string/location', param_key: 'a'
301
+ expect(last_response.body).to eq 'success'
302
+
303
+ get '/string/location', param_integer: 1
304
+ expect(last_response.status).to eq 400
305
+
306
+ get '/integer/location', param_integer: 1
307
+ expect(last_response.body).to eq 'success'
308
+
309
+ get '/integer/location', param_integer: 'a'
310
+ expect(last_response.status).to eq 400
311
+ end
312
+
313
+ context 'on dynamic checks' do
314
+ subject(:a_remounted_api) do
315
+ Class.new(Grape::API) do
316
+ params do
317
+ optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] }
318
+ end
319
+
320
+ get 'location' do
321
+ 'success'
322
+ end
323
+ end
324
+ end
325
+
326
+ it 'can read the configuration on lambdas' do
327
+ root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' }
328
+ get '/location', restricted_values: 'always'
329
+ expect(last_response.body).to eq 'success'
330
+ get '/location', restricted_values: 'sometimes'
331
+ expect(last_response.body).to eq 'success'
332
+ get '/location', restricted_values: 'never'
333
+ expect(last_response.status).to eq 400
334
+ end
335
+ end
336
+ end
337
+
338
+ context 'when the configuration is read within a namespace' do
339
+ before do
340
+ a_remounted_api.namespace 'api' do
341
+ get "/#{configuration[:path]}" do
342
+ '10 votes'
343
+ end
344
+ end
345
+ root_api.mount a_remounted_api, with: { path: 'votes' }
346
+ root_api.mount a_remounted_api, with: { path: 'scores' }
347
+ end
348
+
349
+ it 'will use the dynamic configuration on all routes' do
350
+ get 'api/votes'
351
+ expect(last_response.body).to eql '10 votes'
352
+ get 'api/scores'
353
+ expect(last_response.body).to eql '10 votes'
354
+ end
355
+ end
356
+
357
+ context 'a very complex configuration example' do
358
+ before do
359
+ top_level_api = Class.new(Grape::API) do
360
+ remounted_api = Class.new(Grape::API) do
361
+ get configuration[:endpoint_name] do
362
+ configuration[:response]
363
+ end
364
+ end
365
+
366
+ expression_namespace = mounted { configuration[:namespace].to_s * 2 }
367
+ given(mounted { configuration[:should_mount_expressed] != false }) do
368
+ namespace expression_namespace do
369
+ mount remounted_api, with: { endpoint_name: configuration[:endpoint_name], response: configuration[:endpoint_response] }
370
+ end
371
+ end
372
+ end
373
+ root_api.mount top_level_api, with: configuration_options
374
+ end
375
+
376
+ context 'when the namespace should be mounted' do
377
+ let(:configuration_options) do
378
+ {
379
+ should_mount_expressed: true,
380
+ namespace: 'bang',
381
+ endpoint_name: 'james',
382
+ endpoint_response: 'bond'
383
+ }
384
+ end
385
+
386
+ it 'gets a response' do
387
+ get 'bangbang/james'
388
+ expect(last_response.body).to eq 'bond'
389
+ end
390
+ end
391
+
392
+ context 'when should be mounted is nil' do
393
+ let(:configuration_options) do
394
+ {
395
+ should_mount_expressed: nil,
396
+ namespace: 'bang',
397
+ endpoint_name: 'james',
398
+ endpoint_response: 'bond'
399
+ }
400
+ end
401
+
402
+ it 'gets a response' do
403
+ get 'bangbang/james'
404
+ expect(last_response.body).to eq 'bond'
405
+ end
406
+ end
407
+
408
+ context 'when it should not be mounted' do
409
+ let(:configuration_options) do
410
+ {
411
+ should_mount_expressed: false,
412
+ namespace: 'bang',
413
+ endpoint_name: 'james',
414
+ endpoint_response: 'bond'
415
+ }
416
+ end
417
+
418
+ it 'gets a response' do
419
+ get 'bangbang/james'
420
+ expect(last_response.body).not_to eq 'bond'
421
+ end
422
+ end
423
+ end
424
+
425
+ context 'when the configuration is read in a helper' do
426
+ subject(:a_remounted_api) do
427
+ Class.new(Grape::API) do
428
+ helpers do
429
+ def printed_response
430
+ configuration[:some_value]
431
+ end
432
+ end
433
+
434
+ get 'location' do
435
+ printed_response
436
+ end
437
+ end
438
+ end
439
+
440
+ it 'will use the dynamic configuration on all routes' do
441
+ root_api.mount(a_remounted_api, with: { some_value: 'response value' })
442
+
443
+ get '/location'
444
+ expect(last_response.body).to eq 'response value'
445
+ end
446
+ end
447
+
448
+ context 'when the configuration is read within the response block' do
449
+ subject(:a_remounted_api) do
450
+ Class.new(Grape::API) do
451
+ get 'location' do
452
+ configuration[:some_value]
453
+ end
454
+ end
455
+ end
456
+
457
+ it 'will use the dynamic configuration on all routes' do
458
+ root_api.mount(a_remounted_api, with: { some_value: 'response value' })
459
+
460
+ get '/location'
461
+ expect(last_response.body).to eq 'response value'
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end