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_call(endpoint_instance) end end end
# File lib/grape/endpoint.rb, line 16 def new(...) self == Endpoint ? Class.new(Endpoint).new(...) : 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 218 def call(env) lazy_initialize! dup.call!(env) end
# File lib/grape/endpoint.rb, line 223 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 231 def endpoints options[:app].endpoints if options[:app].respond_to?(:endpoints) end
# File lib/grape/endpoint.rb, line 235 def equals?(endpoint) (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.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) parent_validations = namespace_stackable[:validations] inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any? parent_declared_params = namespace_stackable[:declared_params] inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any? endpoints&.each { |e| e.inherit_settings(namespace_stackable) } end
The purpose of this override is solely for stripping internals when an error occurs while calling an endpoint through an api. See github.com/ruby-grape/grape/issues/2398 Otherwise, it calls super.
# File lib/grape/endpoint.rb, line 242 def inspect return super unless env "#{self.class} in '#{route.origin}' endpoint" end
# File lib/grape/endpoint.rb, line 203 def map_routes options[:method].map { |method| options[:path].map { |path| yield method, path } } end
# File lib/grape/endpoint.rb, line 199 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 << Rack::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::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 214 def namespace @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace)) end
# File lib/grape/endpoint.rb, line 180 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 207 def prepare_path(path) namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash path_settings = namespace_stackable_hash.merge!(namespace_inheritable_hash) Path.new(path, namespace, path_settings) end
# File lib/grape/endpoint.rb, line 173 def prepare_routes_requirements {}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements| endpoint_requirements = options.dig(:route_options, :requirements) requirements.merge!(endpoint_requirements) if endpoint_requirements end end
# File lib/grape/endpoint.rb, line 192 def prepare_version version = namespace_inheritable(:version) return if version.blank? version.length == 1 ? version.first : 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 398 def after_validations namespace_stackable(:after_validations) end
# File lib/grape/endpoint.rb, line 402 def afters namespace_stackable(:afters) end
# File lib/grape/endpoint.rb, line 394 def before_validations namespace_stackable(:before_validations) end
# File lib/grape/endpoint.rb, line 390 def befores namespace_stackable(:befores) end
# File lib/grape/endpoint.rb, line 343 def execute @block&.call(self) end
# File lib/grape/endpoint.rb, line 406 def finallies namespace_stackable(:finallies) end
# File lib/grape/endpoint.rb, line 347 def helpers lazy_initialize! && @helpers end
# File lib/grape/endpoint.rb, line 351 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.include mod } @app = options[:app] || build_stack(@helpers) @lazy_initialized = true end end
# File lib/grape/endpoint.rb, line 418 def options? options[:options_route_enabled] && env[Rack::REQUEST_METHOD] == Rack::OPTIONS end
# File lib/grape/endpoint.rb, line 250 def run ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do @header = Grape::Util::Header.new @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 Grape::Http::Headers::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 382 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 364 def run_validators(validators, request) validation_errors = [] 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 410 def validations return enum_for(:validations) unless block_given? route_setting(:saved_validations)&.each do |saved_validation| yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation) end end
Private Instance Methods
# File lib/grape/endpoint.rb, line 334 def build_helpers helpers = namespace_stackable(:helpers) return if helpers.empty? Module.new { helpers.each { |mod_to_include| include mod_to_include } } end
# File lib/grape/endpoint.rb, line 290 def build_stack(helpers) stack = Grape::Middleware::Stack.new content_types = namespace_stackable_with_hash(:content_types) format = namespace_inheritable(:format) stack.use Rack::Head stack.use Class.new(Grape::Middleware::Error), helpers: helpers, format: format, content_types: 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), grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler) stack.concat namespace_stackable(:middleware) if namespace_inheritable(:version).present? stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]), versions: namespace_inheritable(:version).flatten, 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: format, default_format: namespace_inheritable(:default_format) || :txt, content_types: 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