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 18 def before_each(new_setup = false, &block) @before_each ||= [] if new_setup == false return @before_each unless block_given? @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 46 def generate_api_method(method_name, &block) if method_defined?(method_name) raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") end 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 14 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 75 def initialize(new_settings, options = {}, &block) require_option(options, :path) require_option(options, :method) self.inheritable_setting = new_settings.point_in_time_copy route_setting(:saved_declared_params, namespace_stackable(:declared_params)) 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 @file = nil @body = nil @proc = nil return unless block_given? @source = block @block = self.class.generate_api_method(method_name, &block) end
# File lib/grape/endpoint.rb, line 28 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 219 def call(env) lazy_initialize! dup.call!(env) end
# File lib/grape/endpoint.rb, line 224 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 232 def endpoints options[:app].endpoints if options[:app] && options[:app].respond_to?(:endpoints) end
# File lib/grape/endpoint.rb, line 236 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 112 def inherit_settings(namespace_stackable) inheritable_setting.route[:saved_validations] += namespace_stackable[:validations] parent_declared_params = namespace_stackable[:declared_params] if parent_declared_params inheritable_setting.route[:declared_params] ||= [] inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) end endpoints && endpoints.each { |e| e.inherit_settings(namespace_stackable) } end
# File lib/grape/endpoint.rb, line 206 def map_routes options[:method].map { |method| options[:path].map { |path| yield method, path } } end
# File lib/grape/endpoint.rb, line 202 def merge_route_options(**default) options[:route_options].clone.reverse_merge(**default) end
# File lib/grape/endpoint.rb, line 128 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 146 def mount_in(router) if endpoints endpoints.each { |e| e.mount_in(router) } else reset_routes! routes.each do |route| methods = [route.request_method] if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET methods << Grape::Http::Headers::HEAD end methods.each do |method| unless route.request_method.to_s.upcase == method route = Grape::Router::Route.new(method, route.origin, route.attributes.to_h) end router.append(route.apply(self)) end end end end
# File lib/grape/endpoint.rb, line 215 def namespace @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace)) end
# File lib/grape/endpoint.rb, line 184 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(:saved_declared_params, :saved_validations), forward_match: options[:forward_match] } end
# File lib/grape/endpoint.rb, line 210 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 176 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 196 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 124 def require_option(options, key) raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key) end
# File lib/grape/endpoint.rb, line 140 def reset_routes! endpoints.each(&:reset_routes!) if endpoints @namespace = nil @routes = nil end
# File lib/grape/endpoint.rb, line 136 def routes @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes end
# File lib/grape/endpoint.rb, line 166 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 379 def after_validations namespace_stackable(:after_validations) || [] end
# File lib/grape/endpoint.rb, line 383 def afters namespace_stackable(:afters) || [] end
# File lib/grape/endpoint.rb, line 375 def before_validations namespace_stackable(:before_validations) || [] end
# File lib/grape/endpoint.rb, line 371 def befores namespace_stackable(:befores) || [] end
# File lib/grape/endpoint.rb, line 324 def helpers lazy_initialize! && @helpers end
# File lib/grape/endpoint.rb, line 328 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 391 def options? options[:options_route_enabled] && env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS end
# File lib/grape/endpoint.rb, line 242 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 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, 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 = @block ? @block.call(self) : nil 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 File-like object response_object = file || [body] [status, header, response_object] end end
# File lib/grape/endpoint.rb, line 363 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 341 def run_validators(validator_factories, request) validation_errors = [] validators = validator_factories.map(&:create_validator) ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do validators.each do |validator| begin validator.validate(request) rescue Grape::Exceptions::Validation => e validation_errors << e break if validator.fail_fast? rescue Grape::Exceptions::ValidationArrayErrors => e validation_errors += e.errors break if validator.fail_fast? end end end validation_errors.any? && raise(Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header) end
# File lib/grape/endpoint.rb, line 387 def validations route_setting(:saved_validations) || [] end
Private Instance Methods
# File lib/grape/endpoint.rb, line 317 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 277 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