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
@@ -1,6 +1,117 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 1.2.4
5
+
6
+ #### Headers in `error!` call
7
+
8
+ Headers in `error!` will be merged with `headers` hash. If any header need to be cleared on `error!` call, make sure to move it to the `after` block.
9
+
10
+ ```ruby
11
+ class SampleApi < Grape::API
12
+ before do
13
+ header 'X-Before-Header', 'before_call'
14
+ end
15
+
16
+ get 'ping' do
17
+ header 'X-App-Header', 'on_call'
18
+ error! :pong, 400, 'X-Error-Details' => 'Invalid token'
19
+ end
20
+ end
21
+ ```
22
+ **Former behaviour**
23
+ ```ruby
24
+ response.headers['X-Before-Header'] # => nil
25
+ response.headers['X-App-Header'] # => nil
26
+ response.headers['X-Error-Details'] # => Invalid token
27
+ ```
28
+
29
+ **Current behaviour**
30
+ ```ruby
31
+ response.headers['X-Before-Header'] # => 'before_call'
32
+ response.headers['X-App-Header'] # => 'on_call'
33
+ response.headers['X-Error-Details'] # => Invalid token
34
+ ```
35
+
36
+ ### Upgrading to >= 1.2.1
37
+
38
+ #### Obtaining the name of a mounted class
39
+
40
+ In order to make obtaining the name of a mounted class simpler, we've delegated `.to_s` to `base.name`
41
+
42
+ **Deprecated in 1.2.0**
43
+ ```ruby
44
+ payload[:endpoint].options[:for].name
45
+ ```
46
+ **New**
47
+ ```ruby
48
+ payload[:endpoint].options[:for].to_s
49
+ ```
50
+
51
+ ### Upgrading to >= 1.2.0
52
+
53
+ #### Changes in the Grape::API class
54
+
55
+ ##### Patching the class
56
+
57
+ In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance,
58
+ rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced
59
+ with a class that can contain several instances of `Grape::API`.
60
+
61
+ This changes were done in such a way that no code-changes should be required.
62
+ However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`.
63
+
64
+ Note, this is particularly relevant if you are opening the class `Grape::API` for modification.
65
+
66
+ **Deprecated**
67
+ ```ruby
68
+ class Grape::API
69
+ # your patched logic
70
+ ...
71
+ end
72
+ ```
73
+ **New**
74
+ ```ruby
75
+ class Grape::API::Instance
76
+ # your patched logic
77
+ ...
78
+ end
79
+ ```
80
+
81
+ ##### `name` (and other caveats) of the mounted API
82
+
83
+ After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
84
+ which inherit from `Grape::API::Instance`.
85
+ What this means in practice, is:
86
+ - Generally: you can access the named class from the instance calling the getter `base`.
87
+ - In particular: If you need the `name`, you can use `base`.`name`
88
+
89
+ **Deprecated**
90
+ ```ruby
91
+ payload[:endpoint].options[:for].name
92
+ ```
93
+ **New**
94
+ ```ruby
95
+ payload[:endpoint].options[:for].base.name
96
+ ```
97
+
98
+ #### Changes in rescue_from returned object
99
+
100
+ Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation.
101
+
102
+ ```ruby
103
+ class Twitter::API < Grape::API
104
+ rescue_from :all do |e|
105
+ # version prior to 1.2.0
106
+ Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
107
+ # 1.2.0 version
108
+ Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })
109
+ end
110
+ end
111
+ ```
112
+
113
+ See [#1757](https://github.com/ruby-grape/grape/pull/1757) and [#1776](https://github.com/ruby-grape/grape/pull/1776) for more information.
114
+
4
115
  ### Upgrading to >= 1.1.0
5
116
 
6
117
  #### Changes in HTTP Response Code for Unsupported Content Type
@@ -19,7 +19,9 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency 'rack-accept'
20
20
  s.add_runtime_dependency 'virtus', '>= 1.0.0'
21
21
 
22
- s.files = Dir['**/*'].keep_if { |file| File.file?(file) }
22
+ s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE]
23
+ s.files += %w[grape.gemspec]
24
+ s.files += Dir['lib/**/*']
23
25
  s.test_files = Dir['spec/**/*']
24
26
  s.require_paths = ['lib']
25
27
  end
@@ -34,7 +34,6 @@ module Grape
34
34
  autoload :Namespace
35
35
 
36
36
  autoload :Path
37
-
38
37
  autoload :Cookies
39
38
  autoload :Validations
40
39
  autoload :ErrorFormatter
@@ -55,105 +54,125 @@ module Grape
55
54
 
56
55
  module Exceptions
57
56
  extend ::ActiveSupport::Autoload
58
- autoload :Base
59
- autoload :Validation
60
- autoload :ValidationArrayErrors
61
- autoload :ValidationErrors
62
- autoload :MissingVendorOption
63
- autoload :MissingMimeType
64
- autoload :MissingOption
65
- autoload :InvalidFormatter
66
- autoload :InvalidVersionerOption
67
- autoload :UnknownValidator
68
- autoload :UnknownOptions
69
- autoload :UnknownParameter
70
- autoload :InvalidWithOptionForRepresent
71
- autoload :IncompatibleOptionValues
72
- autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
73
- autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
74
- autoload :InvalidMessageBody
75
- autoload :InvalidAcceptHeader
76
- autoload :InvalidVersionHeader
77
- autoload :MethodNotAllowed
57
+ eager_autoload do
58
+ autoload :Base
59
+ autoload :Validation
60
+ autoload :ValidationArrayErrors
61
+ autoload :ValidationErrors
62
+ autoload :MissingVendorOption
63
+ autoload :MissingMimeType
64
+ autoload :MissingOption
65
+ autoload :InvalidFormatter
66
+ autoload :InvalidVersionerOption
67
+ autoload :UnknownValidator
68
+ autoload :UnknownOptions
69
+ autoload :UnknownParameter
70
+ autoload :InvalidWithOptionForRepresent
71
+ autoload :IncompatibleOptionValues
72
+ autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
73
+ autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
74
+ autoload :InvalidMessageBody
75
+ autoload :InvalidAcceptHeader
76
+ autoload :InvalidVersionHeader
77
+ autoload :MethodNotAllowed
78
+ autoload :InvalidResponse
79
+ end
78
80
  end
79
81
 
80
82
  module Extensions
81
83
  extend ::ActiveSupport::Autoload
82
-
83
- autoload :DeepMergeableHash
84
- autoload :DeepSymbolizeHash
85
- autoload :DeepHashWithIndifferentAccess
86
- autoload :Hash
87
-
84
+ eager_autoload do
85
+ autoload :DeepMergeableHash
86
+ autoload :DeepSymbolizeHash
87
+ autoload :DeepHashWithIndifferentAccess
88
+ autoload :Hash
89
+ end
88
90
  module ActiveSupport
89
91
  extend ::ActiveSupport::Autoload
90
-
91
- autoload :HashWithIndifferentAccess
92
+ eager_autoload do
93
+ autoload :HashWithIndifferentAccess
94
+ end
92
95
  end
93
96
 
94
97
  module Hashie
95
98
  extend ::ActiveSupport::Autoload
96
-
97
- autoload :Mash
99
+ eager_autoload do
100
+ autoload :Mash
101
+ end
98
102
  end
99
103
  end
100
104
 
101
105
  module Middleware
102
106
  extend ::ActiveSupport::Autoload
103
- autoload :Base
104
- autoload :Versioner
105
- autoload :Formatter
106
- autoload :Error
107
- autoload :Globals
108
- autoload :Stack
107
+ eager_autoload do
108
+ autoload :Base
109
+ autoload :Versioner
110
+ autoload :Formatter
111
+ autoload :Error
112
+ autoload :Globals
113
+ autoload :Stack
114
+ autoload :Helpers
115
+ end
109
116
 
110
117
  module Auth
111
118
  extend ::ActiveSupport::Autoload
112
- autoload :Base
113
- autoload :DSL
114
- autoload :StrategyInfo
115
- autoload :Strategies
119
+ eager_autoload do
120
+ autoload :Base
121
+ autoload :DSL
122
+ autoload :StrategyInfo
123
+ autoload :Strategies
124
+ end
116
125
  end
117
126
 
118
127
  module Versioner
119
128
  extend ::ActiveSupport::Autoload
120
- autoload :Path
121
- autoload :Header
122
- autoload :Param
123
- autoload :AcceptVersionHeader
129
+ eager_autoload do
130
+ autoload :Path
131
+ autoload :Header
132
+ autoload :Param
133
+ autoload :AcceptVersionHeader
134
+ end
124
135
  end
125
136
  end
126
137
 
127
138
  module Util
128
139
  extend ::ActiveSupport::Autoload
129
- autoload :InheritableValues
130
- autoload :StackableValues
131
- autoload :ReverseStackableValues
132
- autoload :InheritableSetting
133
- autoload :StrictHashConfiguration
134
- autoload :Registrable
140
+ eager_autoload do
141
+ autoload :InheritableValues
142
+ autoload :StackableValues
143
+ autoload :ReverseStackableValues
144
+ autoload :InheritableSetting
145
+ autoload :StrictHashConfiguration
146
+ autoload :Registrable
147
+ end
135
148
  end
136
149
 
137
150
  module ErrorFormatter
138
151
  extend ::ActiveSupport::Autoload
139
- autoload :Base
140
- autoload :Json
141
- autoload :Txt
142
- autoload :Xml
152
+ eager_autoload do
153
+ autoload :Base
154
+ autoload :Json
155
+ autoload :Txt
156
+ autoload :Xml
157
+ end
143
158
  end
144
159
 
145
160
  module Formatter
146
161
  extend ::ActiveSupport::Autoload
147
- autoload :Json
148
- autoload :SerializableHash
149
- autoload :Txt
150
- autoload :Xml
162
+ eager_autoload do
163
+ autoload :Json
164
+ autoload :SerializableHash
165
+ autoload :Txt
166
+ autoload :Xml
167
+ end
151
168
  end
152
169
 
153
170
  module Parser
154
171
  extend ::ActiveSupport::Autoload
155
- autoload :Json
156
- autoload :Xml
172
+ eager_autoload do
173
+ autoload :Json
174
+ autoload :Xml
175
+ end
157
176
  end
158
177
 
159
178
  module DSL
@@ -177,26 +196,38 @@ module Grape
177
196
 
178
197
  class API
179
198
  extend ::ActiveSupport::Autoload
180
- autoload :Helpers
199
+ eager_autoload do
200
+ autoload :Helpers
201
+ end
181
202
  end
182
203
 
183
204
  module Presenters
184
205
  extend ::ActiveSupport::Autoload
185
- autoload :Presenter
206
+ eager_autoload do
207
+ autoload :Presenter
208
+ end
186
209
  end
187
210
 
188
211
  module ServeFile
189
212
  extend ::ActiveSupport::Autoload
190
- autoload :FileResponse
191
- autoload :FileBody
192
- autoload :SendfileResponse
213
+ eager_autoload do
214
+ autoload :FileResponse
215
+ autoload :FileBody
216
+ autoload :SendfileResponse
217
+ end
193
218
  end
194
219
  end
195
220
 
221
+ require 'grape/config'
196
222
  require 'grape/util/content_types'
223
+ require 'grape/util/lazy_value'
224
+ require 'grape/util/lazy_block'
225
+ require 'grape/util/endpoint_configuration'
197
226
 
198
227
  require 'grape/validations/validators/base'
199
228
  require 'grape/validations/attributes_iterator'
229
+ require 'grape/validations/single_attribute_iterator'
230
+ require 'grape/validations/multiple_attributes_iterator'
200
231
  require 'grape/validations/validators/allow_blank'
201
232
  require 'grape/validations/validators/as'
202
233
  require 'grape/validations/validators/at_least_one_of'
@@ -206,6 +237,7 @@ require 'grape/validations/validators/exactly_one_of'
206
237
  require 'grape/validations/validators/mutual_exclusion'
207
238
  require 'grape/validations/validators/presence'
208
239
  require 'grape/validations/validators/regexp'
240
+ require 'grape/validations/validators/same_as'
209
241
  require 'grape/validations/validators/values'
210
242
  require 'grape/validations/validators/except_values'
211
243
  require 'grape/validations/params_scope'
@@ -1,233 +1,194 @@
1
1
  require 'grape/router'
2
+ require 'grape/api/instance'
2
3
 
3
4
  module Grape
4
5
  # The API class is the primary entry point for creating Grape APIs. Users
5
6
  # should subclass this class in order to build an API.
6
7
  class API
7
- include Grape::DSL::API
8
+ # Class methods that we want to call on the API rather than on the API object
9
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
8
10
 
9
11
  class << self
10
- attr_reader :instance
12
+ attr_accessor :base_instance, :instances
11
13
 
12
- # A class-level lock to ensure the API is not compiled by multiple
13
- # threads simultaneously within the same process.
14
- LOCK = Mutex.new
14
+ # Rather than initializing an object of type Grape::API, create an object of type Instance
15
+ def new(*args, &block)
16
+ base_instance.new(*args, &block)
17
+ end
15
18
 
16
- # Clears all defined routes, endpoints, etc., on this API.
17
- def reset!
18
- reset_endpoints!
19
- reset_routes!
20
- reset_validations!
19
+ # When inherited, will create a list of all instances (times the API was mounted)
20
+ # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
21
+ def inherited(api, base_instance_parent = Grape::API::Instance)
22
+ api.initial_setup(base_instance_parent)
23
+ api.override_all_methods!
24
+ make_inheritable(api)
21
25
  end
22
26
 
23
- # Parses the API's definition and compiles it into an instance of
24
- # Grape::API.
25
- def compile
26
- @instance ||= new
27
+ # Initialize the instance variables on the remountable class, and the base_instance
28
+ # an instance that will be used to create the set up but will not be mounted
29
+ def initial_setup(base_instance_parent)
30
+ @instances = []
31
+ @setup = []
32
+ @base_parent = base_instance_parent
33
+ @base_instance = mount_instance
27
34
  end
28
35
 
29
- # Wipe the compiled API so we can recompile after changes were made.
30
- def change!
31
- @instance = nil
36
+ # Redefines all methods so that are forwarded to add_setup and be recorded
37
+ def override_all_methods!
38
+ (base_instance.methods - NON_OVERRIDABLE).each do |method_override|
39
+ define_singleton_method(method_override) do |*args, &block|
40
+ add_setup(method_override, *args, &block)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Configure an API from the outside. If a block is given, it'll pass a
46
+ # configuration hash to the block which you can use to configure your
47
+ # API. If no block is given, returns the configuration hash.
48
+ # The configuration set here is accessible from inside an API with
49
+ # `configuration` as normal.
50
+ def configure
51
+ config = @base_instance.configuration
52
+ if block_given?
53
+ yield config
54
+ self
55
+ else
56
+ config
57
+ end
32
58
  end
33
59
 
34
60
  # This is the interface point between Rack and Grape; it accepts a request
35
61
  # from Rack and ultimately returns an array of three values: the status,
36
62
  # the headers, and the body. See [the rack specification]
37
63
  # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
38
- def call(env)
39
- LOCK.synchronize { compile } unless instance
40
- call!(env)
64
+ # NOTE: This will only be called on an API directly mounted on RACK
65
+ def call(*args, &block)
66
+ instance_for_rack.call(*args, &block)
41
67
  end
42
68
 
43
- # A non-synchronized version of ::call.
44
- def call!(env)
45
- instance.call(env)
69
+ # Allows an API to itself be inheritable:
70
+ def make_inheritable(api)
71
+ # When a child API inherits from a parent API.
72
+ def api.inherited(child_api)
73
+ # The instances of the child API inherit from the instances of the parent API
74
+ Grape::API.inherited(child_api, base_instance)
75
+ end
46
76
  end
47
77
 
48
- # (see #cascade?)
49
- def cascade(value = nil)
50
- if value.nil?
51
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
78
+ # Alleviates problems with autoloading by tring to search for the constant
79
+ def const_missing(*args)
80
+ if base_instance.const_defined?(*args)
81
+ base_instance.const_get(*args)
52
82
  else
53
- namespace_inheritable(:cascade, value)
83
+ super
54
84
  end
55
85
  end
56
86
 
57
- # see Grape::Router#recognize_path
58
- def recognize_path(path)
59
- LOCK.synchronize { compile } unless instance
60
- instance.router.recognize_path(path)
87
+ # The remountable class can have a configuration hash to provide some dynamic class-level variables.
88
+ # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
89
+ # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
90
+ # too much, you may actually want to provide a new API rather than remount it.
91
+ def mount_instance(opts = {})
92
+ instance = Class.new(@base_parent)
93
+ instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
94
+ instance.base = self
95
+ replay_setup_on(instance)
96
+ instance
61
97
  end
62
98
 
63
- protected
99
+ # Replays the set up to produce an API as defined in this class, can be called
100
+ # on classes that inherit from Grape::API
101
+ def replay_setup_on(instance)
102
+ @setup.each do |setup_step|
103
+ replay_step_on(instance, setup_step)
104
+ end
105
+ end
64
106
 
65
- def prepare_routes
66
- endpoints.map(&:routes).flatten
107
+ def respond_to?(method, include_private = false)
108
+ super(method, include_private) || base_instance.respond_to?(method, include_private)
67
109
  end
68
110
 
69
- # Execute first the provided block, then each of the
70
- # block passed in. Allows for simple 'before' setups
71
- # of settings stack pushes.
72
- def nest(*blocks, &block)
73
- blocks.reject!(&:nil?)
74
- if blocks.any?
75
- instance_eval(&block) if block_given?
76
- blocks.each { |b| instance_eval(&b) }
77
- reset_validations!
111
+ def respond_to_missing?(method, include_private = false)
112
+ base_instance.respond_to?(method, include_private)
113
+ end
114
+
115
+ def method_missing(method, *args, &block)
116
+ # If there's a missing method, it may be defined on the base_instance instead.
117
+ if respond_to_missing?(method)
118
+ base_instance.send(method, *args, &block)
78
119
  else
79
- instance_eval(&block)
120
+ super
80
121
  end
81
122
  end
82
123
 
83
- def inherited(subclass)
84
- subclass.reset!
85
- subclass.logger = logger.clone
124
+ def compile!
125
+ require 'grape/eager_load'
126
+ instance_for_rack.compile! # See API::Instance.compile!
86
127
  end
87
128
 
88
- def inherit_settings(other_settings)
89
- top_level_setting.inherit_from other_settings.point_in_time_copy
129
+ private
90
130
 
91
- # Propagate any inherited params down to our endpoints, and reset any
92
- # compiled routes.
93
- endpoints.each do |e|
94
- e.inherit_settings(top_level_setting.namespace_stackable)
95
- e.reset_routes!
131
+ def instance_for_rack
132
+ if never_mounted?
133
+ base_instance
134
+ else
135
+ mounted_instances.first
96
136
  end
97
-
98
- reset_routes!
99
137
  end
100
- end
101
-
102
- attr_reader :router
103
138
 
104
- # Builds the routes from the defined endpoints, effectively compiling
105
- # this API into a usable form.
106
- def initialize
107
- @router = Router.new
108
- add_head_not_allowed_methods_and_options_methods
109
- self.class.endpoints.each do |endpoint|
110
- endpoint.mount_in(@router)
139
+ # Adds a new stage to the set up require to get a Grape::API up and running
140
+ def add_setup(method, *args, &block)
141
+ setup_step = { method: method, args: args, block: block }
142
+ @setup << setup_step
143
+ last_response = nil
144
+ @instances.each do |instance|
145
+ last_response = replay_step_on(instance, setup_step)
146
+ end
147
+ last_response
111
148
  end
112
149
 
113
- @router.compile!
114
- @router.freeze
115
- end
116
-
117
- # Handle a request. See Rack documentation for what `env` is.
118
- def call(env)
119
- result = @router.call(env)
120
- result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
121
- result
122
- end
123
-
124
- # Some requests may return a HTTP 404 error if grape cannot find a matching
125
- # route. In this case, Grape::Router adds a X-Cascade header to the response
126
- # and sets it to 'pass', indicating to grape's parents they should keep
127
- # looking for a matching route on other resources.
128
- #
129
- # In some applications (e.g. mounting grape on rails), one might need to trap
130
- # errors from reaching upstream. This is effectivelly done by unsetting
131
- # X-Cascade. Default :cascade is true.
132
- def cascade?
133
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
134
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
135
- true
136
- end
137
-
138
- reset!
139
-
140
- private
141
-
142
- # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
143
- # with a list of HTTP methods that can be called. Also add a route that
144
- # will return an HTTP 405 response for any HTTP method that the resource
145
- # cannot handle.
146
- def add_head_not_allowed_methods_and_options_methods
147
- routes_map = {}
148
-
149
- self.class.endpoints.each do |endpoint|
150
- routes = endpoint.routes
151
- routes.each do |route|
152
- # using the :any shorthand produces [nil] for route methods, substitute all manually
153
- route_key = route.pattern.to_regexp
154
- routes_map[route_key] ||= {}
155
- route_settings = routes_map[route_key]
156
- route_settings[:pattern] = route.pattern
157
- route_settings[:requirements] = route.requirements
158
- route_settings[:path] = route.origin
159
- route_settings[:methods] ||= []
160
- route_settings[:methods] << route.request_method
161
- route_settings[:endpoint] = route.app
162
-
163
- # using the :any shorthand produces [nil] for route methods, substitute all manually
164
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
150
+ def replay_step_on(instance, setup_step)
151
+ return if skip_immediate_run?(instance, setup_step[:args])
152
+ args = evaluate_arguments(instance.configuration, *setup_step[:args])
153
+ response = instance.send(setup_step[:method], *args, &setup_step[:block])
154
+ if skip_immediate_run?(instance, [response])
155
+ response
156
+ else
157
+ evaluate_arguments(instance.configuration, response).first
165
158
  end
166
159
  end
167
160
 
168
- # The paths we collected are prepared (cf. Path#prepare), so they
169
- # contain already versioning information when using path versioning.
170
- # Disable versioning so adding a route won't prepend versioning
171
- # informations again.
172
- without_root_prefix do
173
- without_versioning do
174
- routes_map.each do |_, config|
175
- methods = config[:methods]
176
- allowed_methods = methods.dup
177
-
178
- unless self.class.namespace_inheritable(:do_not_route_head)
179
- allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
180
- end
181
-
182
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
161
+ # Skips steps that contain arguments to be lazily executed (on re-mount time)
162
+ def skip_immediate_run?(instance, args)
163
+ instance.base_instance? &&
164
+ (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
165
+ end
183
166
 
184
- unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
185
- config[:endpoint].options[:options_route_enabled] = true
186
- end
167
+ def any_lazy?(args)
168
+ args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
169
+ end
187
170
 
188
- attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
189
- generate_not_allowed_method(config[:pattern], attributes)
171
+ def evaluate_arguments(configuration, *args)
172
+ args.map do |argument|
173
+ if argument.respond_to?(:lazy?) && argument.lazy?
174
+ argument.evaluate_from(configuration)
175
+ elsif argument.is_a?(Hash)
176
+ argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
177
+ elsif argument.is_a?(Array)
178
+ evaluate_arguments(configuration, *argument)
179
+ else
180
+ argument
190
181
  end
191
182
  end
192
183
  end
193
- end
194
-
195
- # Generate a route that returns an HTTP 405 response for a user defined
196
- # path on methods not specified
197
- def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
198
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
199
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
200
184
 
201
- return if not_allowed_methods.empty?
202
-
203
- @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
204
- end
205
-
206
- # Allows definition of endpoints that ignore the versioning configuration
207
- # used by the rest of your API.
208
- def without_versioning(&_block)
209
- old_version = self.class.namespace_inheritable(:version)
210
- old_version_options = self.class.namespace_inheritable(:version_options)
211
-
212
- self.class.namespace_inheritable_to_nil(:version)
213
- self.class.namespace_inheritable_to_nil(:version_options)
214
-
215
- yield
216
-
217
- self.class.namespace_inheritable(:version, old_version)
218
- self.class.namespace_inheritable(:version_options, old_version_options)
219
- end
220
-
221
- # Allows definition of endpoints that ignore the root prefix used by the
222
- # rest of your API.
223
- def without_root_prefix(&_block)
224
- old_prefix = self.class.namespace_inheritable(:root_prefix)
225
-
226
- self.class.namespace_inheritable_to_nil(:root_prefix)
227
-
228
- yield
185
+ def never_mounted?
186
+ mounted_instances.empty?
187
+ end
229
188
 
230
- self.class.namespace_inheritable(:root_prefix, old_prefix)
189
+ def mounted_instances
190
+ instances - [base_instance]
191
+ end
231
192
  end
232
193
  end
233
194
  end