class Grape::Endpoint
An Endpoint
is the proxy scope in which all routing blocks are executed. In other words, any methods on the instance level of this class may be called from inside a `get`, `post`, etc.
Attributes
Public Class Methods
# File lib/grape/endpoint.rb, line 20 def before_each(new_setup = false, &block) @before_each ||= [] if new_setup == false return @before_each unless block @before_each << block else @before_each = [new_setup] end end
@api private
Create an UnboundMethod that is appropriate for executing an endpoint route.
The unbound method allows explicit calls to return
without raising a LocalJumpError
. The method will be removed, but a Proc
reference to it will be returned. The returned Proc
expects a single argument: the instance of Endpoint
to bind to the method during the call.
@param [String, Symbol] method_name
@return [Proc] @raise [NameError] an instance method with the same name already exists
# File lib/grape/endpoint.rb, line 49 def generate_api_method(method_name, &block) raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name) define_method(method_name, &block) method = instance_method(method_name) remove_method(method_name) proc do |endpoint_instance| ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do method.bind(endpoint_instance).call end end end
# File lib/grape/endpoint.rb, line 16 def new(*args, &block) self == Endpoint ? Class.new(Endpoint).new(*args, &block) : super end
Create a new endpoint. @param new_settings [InheritableSetting] settings to determine the params,
validations, and other properties from.
@param options [Hash] attributes of this endpoint @option options path [String or Array] the path to this endpoint, within
the current scope.
@option options method [String or Array] which HTTP method(s) can be used
to reach this endpoint.
@option options route_options [Hash] @note This happens at the time of API
definition, so in this context the endpoint does not know if it will be mounted under a different endpoint. @yield a block defining what your API
should do when this endpoint is hit
# File lib/grape/endpoint.rb, line 76 def initialize(new_settings, options = {}, &block) require_option(options, :path) require_option(options, :method) self.inheritable_setting = new_settings.point_in_time_copy # now +namespace_stackable(:declared_params)+ contains all params defined for # this endpoint and its parents, but later it will be cleaned up, # see +reset_validations!+ in lib/grape/dsl/validations.rb route_setting(:declared_params, namespace_stackable(:declared_params).flatten) route_setting(:saved_validations, namespace_stackable(:validations)) namespace_stackable(:representations, []) unless namespace_stackable(:representations) namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status) @options = options @options[:path] = Array(options[:path]) @options[:path] << '/' if options[:path].empty? @options[:method] = Array(options[:method]) @options[:route_options] ||= {} @lazy_initialize_lock = Mutex.new @lazy_initialized = nil @block = nil @status = nil @stream = nil @body = nil @proc = nil return unless block @source = block @block = self.class.generate_api_method(method_name, &block) end
# File lib/grape/endpoint.rb, line 31 def run_before_each(endpoint) superclass.run_before_each(endpoint) unless self == Endpoint before_each.each { |blk| blk.call(endpoint) if blk.respond_to?(:call) } end
Public Instance Methods
# File lib/grape/endpoint.rb, line 217 def call(env) lazy_initialize! dup.call!(env) end
# File lib/grape/endpoint.rb, line 222 def call!(env) env[Grape::Env::API_ENDPOINT] = self @env = env @app.call(env) end
Return the collection of endpoints within this endpoint. This is the case when an Grape::API
mounts another Grape::API
.
# File lib/grape/endpoint.rb, line 230 def endpoints options[:app].endpoints if options[:app].respond_to?(:endpoints) end
# File lib/grape/endpoint.rb, line 234 def equals?(e) (options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash) end
Update our settings from a given set of stackable parameters. Used when the endpoint's API
is mounted under another one.
# File lib/grape/endpoint.rb, line 116 def inherit_settings(namespace_stackable) inheritable_setting.route[:saved_validations] += namespace_stackable[:validations] parent_declared_params = namespace_stackable[:declared_params] inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params endpoints&.each { |e| e.inherit_settings(namespace_stackable) } end
# File lib/grape/endpoint.rb, line 204 def map_routes options[:method].map { |method| options[:path].map { |path| yield method, path } } end
# File lib/grape/endpoint.rb, line 200 def merge_route_options(**default) options[:route_options].clone.merge!(**default) end
# File lib/grape/endpoint.rb, line 129 def method_name [options[:method], Namespace.joined_space(namespace_stackable(:namespace)), (namespace_stackable(:mount_path) || []).join('/'), options[:path].join('/')] .join(' ') end
# File lib/grape/endpoint.rb, line 147 def mount_in(router) if endpoints endpoints.each { |e| e.mount_in(router) } else reset_routes! routes.each do |route| methods = [route.request_method] methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET methods.each do |method| route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method router.append(route.apply(self)) end end end end
# File lib/grape/endpoint.rb, line 213 def namespace @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace)) end
# File lib/grape/endpoint.rb, line 181 def prepare_default_route_attributes { namespace: namespace, version: prepare_version, requirements: prepare_routes_requirements, prefix: namespace_inheritable(:root_prefix), anchor: options[:route_options].fetch(:anchor, true), settings: inheritable_setting.route.except(:declared_params, :saved_validations), forward_match: options[:forward_match] } end
# File lib/grape/endpoint.rb, line 208 def prepare_path(path) path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable]) Path.prepare(path, namespace, path_settings) end
# File lib/grape/endpoint.rb, line 173 def prepare_routes_requirements endpoint_requirements = options[:route_options][:requirements] || {} all_requirements = (namespace_stackable(:namespace).map(&:requirements) << endpoint_requirements) all_requirements.reduce({}) do |base_requirements, single_requirements| base_requirements.merge!(single_requirements) end end
# File lib/grape/endpoint.rb, line 193 def prepare_version version = namespace_inheritable(:version) || [] return if version.empty? version.length == 1 ? version.first.to_s : version end
# File lib/grape/endpoint.rb, line 125 def require_option(options, key) raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key) end
# File lib/grape/endpoint.rb, line 141 def reset_routes! endpoints&.each(&:reset_routes!) @namespace = nil @routes = nil end
# File lib/grape/endpoint.rb, line 137 def routes @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes end
# File lib/grape/endpoint.rb, line 163 def to_routes route_options = prepare_default_route_attributes map_routes do |method, path| path = prepare_path(path) params = merge_route_options(**route_options.merge(suffix: path.suffix)) route = Router::Route.new(method, path.path, **params) route.apply(self) end.flatten end
Protected Instance Methods
# File lib/grape/endpoint.rb, line 384 def after_validations namespace_stackable(:after_validations) || [] end
# File lib/grape/endpoint.rb, line 388 def afters namespace_stackable(:afters) || [] end
# File lib/grape/endpoint.rb, line 380 def before_validations namespace_stackable(:before_validations) || [] end
# File lib/grape/endpoint.rb, line 376 def befores namespace_stackable(:befores) || [] end
# File lib/grape/endpoint.rb, line 327 def execute @block ? @block.call(self) : nil end
# File lib/grape/endpoint.rb, line 392 def finallies namespace_stackable(:finallies) || [] end
# File lib/grape/endpoint.rb, line 331 def helpers lazy_initialize! && @helpers end
# File lib/grape/endpoint.rb, line 335 def lazy_initialize! return true if @lazy_initialized @lazy_initialize_lock.synchronize do return true if @lazy_initialized @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) } @app = options[:app] || build_stack(@helpers) @lazy_initialized = true end end
# File lib/grape/endpoint.rb, line 400 def options? options[:options_route_enabled] && env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS end
# File lib/grape/endpoint.rb, line 240 def run ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do @header = {} @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with)) @params = @request.params @headers = @request.headers begin cookies.read(@request) self.class.run_before_each(self) run_filters befores, :before if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]) raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options? header 'Allow', allowed_methods response_object = '' status 204 else run_filters before_validations, :before_validation run_validators validations, request run_filters after_validations, :after_validation response_object = execute end run_filters afters, :after cookies.write(header) # status verifies body presence when DELETE @body ||= response_object # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object response_object = stream || [body] [status, header, response_object] ensure run_filters finallies, :finally end end end
# File lib/grape/endpoint.rb, line 368 def run_filters(filters, type = :other) ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do (filters || []).each { |filter| instance_eval(&filter) } end post_extension = DSL::InsideRoute.post_filter_methods(type) extend post_extension if post_extension end
# File lib/grape/endpoint.rb, line 348 def run_validators(validator_factories, request) validation_errors = [] validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(**options) } ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do validators.each do |validator| validator.validate(request) rescue Grape::Exceptions::Validation => e validation_errors << e break if validator.fail_fast? rescue Grape::Exceptions::ValidationArrayErrors => e validation_errors.concat e.errors break if validator.fail_fast? end end validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) end
# File lib/grape/endpoint.rb, line 396 def validations route_setting(:saved_validations) || [] end
Private Instance Methods
# File lib/grape/endpoint.rb, line 320 def build_helpers helpers = namespace_stackable(:helpers) || [] Module.new { helpers.each { |mod_to_include| include mod_to_include } } end
# File lib/grape/endpoint.rb, line 280 def build_stack(helpers) stack = Grape::Middleware::Stack.new stack.use Rack::Head stack.use Class.new(Grape::Middleware::Error), helpers: helpers, format: namespace_inheritable(:format), content_types: namespace_stackable_with_hash(:content_types), default_status: namespace_inheritable(:default_error_status), rescue_all: namespace_inheritable(:rescue_all), rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions), default_error_formatter: namespace_inheritable(:default_error_formatter), error_formatters: namespace_stackable_with_hash(:error_formatters), rescue_options: namespace_stackable_with_hash(:rescue_options) || {}, rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {}, base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {}, all_rescue_handler: namespace_inheritable(:all_rescue_handler) stack.concat namespace_stackable(:middleware) if namespace_inheritable(:version) stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]), versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil, version_options: namespace_inheritable(:version_options), prefix: namespace_inheritable(:root_prefix), mount_path: namespace_stackable(:mount_path).first end stack.use Grape::Middleware::Formatter, format: namespace_inheritable(:format), default_format: namespace_inheritable(:default_format) || :txt, content_types: namespace_stackable_with_hash(:content_types), formatters: namespace_stackable_with_hash(:formatters), parsers: namespace_stackable_with_hash(:parsers) builder = stack.build builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run } builder.to_app end