grape 1.1.0 → 1.2.5

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.
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