Skip to content

Commit 691eca8

Browse files
committed
Changes APIInstance to API::Instance
1 parent 5fcc07b commit 691eca8

File tree

5 files changed

+239
-237
lines changed

5 files changed

+239
-237
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Naming/HeredocDelimiterNaming:
8787
# Configuration parameters: AutoCorrect.
8888
Performance/HashEachMethods:
8989
Exclude:
90-
- 'lib/grape/api_instance.rb'
90+
- 'lib/grape/api/instance.rb'
9191
- 'lib/grape/middleware/versioner/header.rb'
9292

9393
# Offense count: 1

lib/grape.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ module Grape
2828
extend ::ActiveSupport::Autoload
2929

3030
eager_autoload do
31-
autoload :APIInstance
3231
autoload :API
3332
autoload :Endpoint
3433

lib/grape/api.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
require 'grape/router'
2+
require 'grape/api/instance'
23

34
module Grape
45
# The API class is the primary entry point for creating Grape APIs. Users
56
# should subclass this class in order to build an API.
67
class API
78
# Class methods that we want to call on the API rather than on the API object
8-
NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? byebug].freeze
9+
NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze
910

1011
class << self
1112
attr_accessor :base_instance
1213
# When inherited, will create a list of all instances (times the API was mounted)
1314
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
14-
def inherited(remountable_class, base_instance_parent = Grape::APIInstance)
15+
def inherited(remountable_class, base_instance_parent = Grape::API::Instance)
1516
remountable_class.initial_setup(base_instance_parent)
1617
remountable_class.override_all_methods
1718
remountable_class.make_inheritable

lib/grape/api/instance.rb

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
require 'grape/router'
2+
3+
module Grape
4+
class API
5+
# The API Instance class, is the engine behind Grape::API. Each class that inherits
6+
# from this will represent a different API instance
7+
class Instance
8+
include Grape::DSL::API
9+
10+
class << self
11+
attr_reader :instance
12+
13+
# A class-level lock to ensure the API is not compiled by multiple
14+
# threads simultaneously within the same process.
15+
LOCK = Mutex.new
16+
17+
# Clears all defined routes, endpoints, etc., on this API.
18+
def reset!
19+
reset_endpoints!
20+
reset_routes!
21+
reset_validations!
22+
end
23+
24+
# Parses the API's definition and compiles it into an instance of
25+
# Grape::API.
26+
def compile
27+
@instance ||= new
28+
end
29+
30+
# Wipe the compiled API so we can recompile after changes were made.
31+
def change!
32+
@instance = nil
33+
end
34+
35+
# This is the interface point between Rack and Grape; it accepts a request
36+
# from Rack and ultimately returns an array of three values: the status,
37+
# the headers, and the body. See [the rack specification]
38+
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
39+
def call(env)
40+
LOCK.synchronize { compile } unless instance
41+
call!(env)
42+
end
43+
44+
# A non-synchronized version of ::call.
45+
def call!(env)
46+
instance.call(env)
47+
end
48+
49+
# (see #cascade?)
50+
def cascade(value = nil)
51+
if value.nil?
52+
inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
53+
else
54+
namespace_inheritable(:cascade, value)
55+
end
56+
end
57+
58+
# see Grape::Router#recognize_path
59+
def recognize_path(path)
60+
LOCK.synchronize { compile } unless instance
61+
instance.router.recognize_path(path)
62+
end
63+
64+
protected
65+
66+
def prepare_routes
67+
endpoints.map(&:routes).flatten
68+
end
69+
70+
# Execute first the provided block, then each of the
71+
# block passed in. Allows for simple 'before' setups
72+
# of settings stack pushes.
73+
def nest(*blocks, &block)
74+
blocks.reject!(&:nil?)
75+
if blocks.any?
76+
instance_eval(&block) if block_given?
77+
blocks.each { |b| instance_eval(&b) }
78+
reset_validations!
79+
else
80+
instance_eval(&block)
81+
end
82+
end
83+
84+
def inherited(subclass)
85+
subclass.reset!
86+
subclass.logger = logger.clone
87+
end
88+
89+
def inherit_settings(other_settings)
90+
top_level_setting.inherit_from other_settings.point_in_time_copy
91+
92+
# Propagate any inherited params down to our endpoints, and reset any
93+
# compiled routes.
94+
endpoints.each do |e|
95+
e.inherit_settings(top_level_setting.namespace_stackable)
96+
e.reset_routes!
97+
end
98+
99+
reset_routes!
100+
end
101+
end
102+
103+
attr_reader :router
104+
105+
# Builds the routes from the defined endpoints, effectively compiling
106+
# this API into a usable form.
107+
def initialize
108+
@router = Router.new
109+
add_head_not_allowed_methods_and_options_methods
110+
self.class.endpoints.each do |endpoint|
111+
endpoint.mount_in(@router)
112+
end
113+
114+
@router.compile!
115+
@router.freeze
116+
end
117+
118+
# Handle a request. See Rack documentation for what `env` is.
119+
def call(env)
120+
result = @router.call(env)
121+
result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
122+
result
123+
end
124+
125+
# Some requests may return a HTTP 404 error if grape cannot find a matching
126+
# route. In this case, Grape::Router adds a X-Cascade header to the response
127+
# and sets it to 'pass', indicating to grape's parents they should keep
128+
# looking for a matching route on other resources.
129+
#
130+
# In some applications (e.g. mounting grape on rails), one might need to trap
131+
# errors from reaching upstream. This is effectivelly done by unsetting
132+
# X-Cascade. Default :cascade is true.
133+
def cascade?
134+
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
135+
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
136+
true
137+
end
138+
139+
reset!
140+
141+
private
142+
143+
# For every resource add a 'OPTIONS' route that returns an HTTP 204 response
144+
# with a list of HTTP methods that can be called. Also add a route that
145+
# will return an HTTP 405 response for any HTTP method that the resource
146+
# cannot handle.
147+
def add_head_not_allowed_methods_and_options_methods
148+
routes_map = {}
149+
150+
self.class.endpoints.each do |endpoint|
151+
routes = endpoint.routes
152+
routes.each do |route|
153+
# using the :any shorthand produces [nil] for route methods, substitute all manually
154+
route_key = route.pattern.to_regexp
155+
routes_map[route_key] ||= {}
156+
route_settings = routes_map[route_key]
157+
route_settings[:pattern] = route.pattern
158+
route_settings[:requirements] = route.requirements
159+
route_settings[:path] = route.origin
160+
route_settings[:methods] ||= []
161+
route_settings[:methods] << route.request_method
162+
route_settings[:endpoint] = route.app
163+
164+
# using the :any shorthand produces [nil] for route methods, substitute all manually
165+
route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
166+
end
167+
end
168+
169+
# The paths we collected are prepared (cf. Path#prepare), so they
170+
# contain already versioning information when using path versioning.
171+
# Disable versioning so adding a route won't prepend versioning
172+
# informations again.
173+
without_root_prefix do
174+
without_versioning do
175+
routes_map.each do |_, config|
176+
methods = config[:methods]
177+
allowed_methods = methods.dup
178+
179+
unless self.class.namespace_inheritable(:do_not_route_head)
180+
allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
181+
end
182+
183+
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
184+
185+
unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
186+
config[:endpoint].options[:options_route_enabled] = true
187+
end
188+
189+
attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
190+
generate_not_allowed_method(config[:pattern], attributes)
191+
end
192+
end
193+
end
194+
end
195+
196+
# Generate a route that returns an HTTP 405 response for a user defined
197+
# path on methods not specified
198+
def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
199+
not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
200+
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
201+
202+
return if not_allowed_methods.empty?
203+
204+
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
205+
end
206+
207+
# Allows definition of endpoints that ignore the versioning configuration
208+
# used by the rest of your API.
209+
def without_versioning(&_block)
210+
old_version = self.class.namespace_inheritable(:version)
211+
old_version_options = self.class.namespace_inheritable(:version_options)
212+
213+
self.class.namespace_inheritable_to_nil(:version)
214+
self.class.namespace_inheritable_to_nil(:version_options)
215+
216+
yield
217+
218+
self.class.namespace_inheritable(:version, old_version)
219+
self.class.namespace_inheritable(:version_options, old_version_options)
220+
end
221+
222+
# Allows definition of endpoints that ignore the root prefix used by the
223+
# rest of your API.
224+
def without_root_prefix(&_block)
225+
old_prefix = self.class.namespace_inheritable(:root_prefix)
226+
227+
self.class.namespace_inheritable_to_nil(:root_prefix)
228+
229+
yield
230+
231+
self.class.namespace_inheritable(:root_prefix, old_prefix)
232+
end
233+
end
234+
end
235+
end

0 commit comments

Comments
 (0)