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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +128 -43
- data/LICENSE +1 -1
- data/README.md +394 -47
- data/UPGRADING.md +111 -0
- data/grape.gemspec +3 -1
- data/lib/grape.rb +98 -66
- data/lib/grape/api.rb +136 -175
- data/lib/grape/api/instance.rb +280 -0
- data/lib/grape/config.rb +32 -0
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/desc.rb +39 -7
- data/lib/grape/dsl/inside_route.rb +12 -6
- data/lib/grape/dsl/middleware.rb +7 -0
- data/lib/grape/dsl/parameters.rb +9 -4
- data/lib/grape/dsl/routing.rb +5 -1
- data/lib/grape/dsl/validations.rb +4 -3
- data/lib/grape/eager_load.rb +18 -0
- data/lib/grape/endpoint.rb +42 -26
- data/lib/grape/error_formatter.rb +1 -1
- data/lib/grape/exceptions/base.rb +9 -1
- data/lib/grape/exceptions/invalid_response.rb +9 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -2
- data/lib/grape/formatter.rb +1 -1
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/base.rb +2 -4
- data/lib/grape/middleware/base.rb +2 -0
- data/lib/grape/middleware/error.rb +9 -4
- data/lib/grape/middleware/helpers.rb +10 -0
- data/lib/grape/middleware/stack.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/parser.rb +1 -1
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router/attribute_translator.rb +2 -0
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/util/base_inheritable.rb +34 -0
- data/lib/grape/util/endpoint_configuration.rb +6 -0
- data/lib/grape/util/inheritable_values.rb +5 -25
- data/lib/grape/util/lazy_block.rb +25 -0
- data/lib/grape/util/lazy_value.rb +95 -0
- data/lib/grape/util/reverse_stackable_values.rb +7 -36
- data/lib/grape/util/stackable_values.rb +19 -22
- data/lib/grape/validations/attributes_iterator.rb +5 -3
- data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
- data/lib/grape/validations/params_scope.rb +20 -14
- data/lib/grape/validations/single_attribute_iterator.rb +13 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
- data/lib/grape/validations/types/file.rb +1 -1
- data/lib/grape/validations/validator_factory.rb +6 -11
- data/lib/grape/validations/validators/all_or_none.rb +6 -13
- data/lib/grape/validations/validators/as.rb +2 -3
- data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
- data/lib/grape/validations/validators/base.rb +11 -10
- data/lib/grape/validations/validators/coerce.rb +4 -0
- data/lib/grape/validations/validators/default.rb +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
- data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
- data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
- data/lib/grape/validations/validators/same_as.rb +23 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
- data/spec/grape/api_remount_spec.rb +466 -0
- data/spec/grape/api_spec.rb +379 -1
- data/spec/grape/config_spec.rb +17 -0
- data/spec/grape/dsl/desc_spec.rb +40 -16
- data/spec/grape/dsl/middleware_spec.rb +8 -0
- data/spec/grape/dsl/routing_spec.rb +10 -0
- data/spec/grape/endpoint_spec.rb +40 -4
- data/spec/grape/exceptions/base_spec.rb +65 -0
- data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
- data/spec/grape/integration/rack_spec.rb +22 -6
- data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
- data/spec/grape/middleware/base_spec.rb +8 -0
- data/spec/grape/middleware/exception_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +15 -5
- data/spec/grape/middleware/versioner/header_spec.rb +6 -0
- data/spec/grape/named_api_spec.rb +19 -0
- data/spec/grape/request_spec.rb +24 -0
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
- data/spec/grape/validations/params_scope_spec.rb +184 -8
- data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
- data/spec/grape/validations/validators/coerce_spec.rb +10 -2
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
- data/spec/grape/validations/validators/same_as_spec.rb +63 -0
- data/spec/grape/validations_spec.rb +33 -21
- data/spec/spec_helper.rb +4 -1
- metadata +35 -23
- data/Appraisals +0 -32
- data/Dangerfile +0 -2
- data/Gemfile +0 -33
- data/Gemfile.lock +0 -231
- data/Guardfile +0 -10
- data/RELEASING.md +0 -111
- data/Rakefile +0 -25
- data/benchmark/simple.rb +0 -27
- data/benchmark/simple_with_type_coercer.rb +0 -22
- data/gemfiles/multi_json.gemfile +0 -35
- data/gemfiles/multi_xml.gemfile +0 -35
- data/gemfiles/rack_1.5.2.gemfile +0 -35
- data/gemfiles/rack_edge.gemfile +0 -35
- data/gemfiles/rails_3.gemfile +0 -36
- data/gemfiles/rails_4.gemfile +0 -35
- data/gemfiles/rails_5.gemfile +0 -35
- data/gemfiles/rails_edge.gemfile +0 -35
- data/pkg/grape-0.17.0.gem +0 -0
- data/pkg/grape-0.19.0.gem +0 -0
data/UPGRADING.md
CHANGED
@@ -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
|
data/grape.gemspec
CHANGED
@@ -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 =
|
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
|
data/lib/grape.rb
CHANGED
@@ -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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
156
|
-
|
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
|
-
|
199
|
+
eager_autoload do
|
200
|
+
autoload :Helpers
|
201
|
+
end
|
181
202
|
end
|
182
203
|
|
183
204
|
module Presenters
|
184
205
|
extend ::ActiveSupport::Autoload
|
185
|
-
|
206
|
+
eager_autoload do
|
207
|
+
autoload :Presenter
|
208
|
+
end
|
186
209
|
end
|
187
210
|
|
188
211
|
module ServeFile
|
189
212
|
extend ::ActiveSupport::Autoload
|
190
|
-
|
191
|
-
|
192
|
-
|
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'
|
data/lib/grape/api.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
12
|
+
attr_accessor :base_instance, :instances
|
11
13
|
|
12
|
-
#
|
13
|
-
|
14
|
-
|
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
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
#
|
24
|
-
#
|
25
|
-
def
|
26
|
-
@
|
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
|
-
#
|
30
|
-
def
|
31
|
-
|
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
|
-
|
39
|
-
|
40
|
-
call
|
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
|
-
#
|
44
|
-
def
|
45
|
-
|
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
|
-
#
|
49
|
-
def
|
50
|
-
if
|
51
|
-
|
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
|
-
|
83
|
+
super
|
54
84
|
end
|
55
85
|
end
|
56
86
|
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
66
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
120
|
+
super
|
80
121
|
end
|
81
122
|
end
|
82
123
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
124
|
+
def compile!
|
125
|
+
require 'grape/eager_load'
|
126
|
+
instance_for_rack.compile! # See API::Instance.compile!
|
86
127
|
end
|
87
128
|
|
88
|
-
|
89
|
-
top_level_setting.inherit_from other_settings.point_in_time_copy
|
129
|
+
private
|
90
130
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
167
|
+
def any_lazy?(args)
|
168
|
+
args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
|
169
|
+
end
|
187
170
|
|
188
|
-
|
189
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
189
|
+
def mounted_instances
|
190
|
+
instances - [base_instance]
|
191
|
+
end
|
231
192
|
end
|
232
193
|
end
|
233
194
|
end
|