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