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

block[RW]
env[R]
headers[R]
options[RW]
params[R]
request[R]
source[RW]

Public Class Methods

before_each(new_setup = false, &block) click to toggle source
# 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
generate_api_method(method_name, &block) click to toggle source

@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
new(...) click to toggle source
Calls superclass method
# File lib/grape/endpoint.rb, line 16
def new(...)
  self == Endpoint ? Class.new(Endpoint).new(...) : super
end
new(new_settings, options = {}, &block) click to toggle source

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
run_before_each(endpoint) click to toggle source
# 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

call(env) click to toggle source
# File lib/grape/endpoint.rb, line 218
def call(env)
  lazy_initialize!
  dup.call!(env)
end
call!(env) click to toggle source
# File lib/grape/endpoint.rb, line 223
def call!(env)
  env[Grape::Env::API_ENDPOINT] = self
  @env = env
  @app.call(env)
end
endpoints() click to toggle source

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
equals?(endpoint) click to toggle source
# File lib/grape/endpoint.rb, line 235
def equals?(endpoint)
  (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
end
inherit_settings(namespace_stackable) click to toggle source

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
inspect() click to toggle source

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.

Calls superclass method
# File lib/grape/endpoint.rb, line 242
def inspect
  return super unless env

  "#{self.class} in '#{route.origin}' endpoint"
end
map_routes() { |method, path| ... } click to toggle source
# File lib/grape/endpoint.rb, line 203
def map_routes
  options[:method].map { |method| options[:path].map { |path| yield method, path } }
end
merge_route_options(**default) click to toggle source
# File lib/grape/endpoint.rb, line 199
def merge_route_options(**default)
  options[:route_options].clone.merge!(**default)
end
method_name() click to toggle source
# 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
mount_in(router) click to toggle source
# 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
namespace() click to toggle source
# File lib/grape/endpoint.rb, line 214
def namespace
  @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
end
prepare_default_route_attributes() click to toggle source
# 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
prepare_path(path) click to toggle source
# 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
prepare_routes_requirements() click to toggle source
# 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
prepare_version() click to toggle source
# 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
require_option(options, key) click to toggle source
# File lib/grape/endpoint.rb, line 125
def require_option(options, key)
  raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
end
reset_routes!() click to toggle source
# File lib/grape/endpoint.rb, line 141
def reset_routes!
  endpoints&.each(&:reset_routes!)
  @namespace = nil
  @routes = nil
end
routes() click to toggle source
# File lib/grape/endpoint.rb, line 137
def routes
  @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
end
to_routes() click to toggle source
# 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

after_validations() click to toggle source
# File lib/grape/endpoint.rb, line 398
def after_validations
  namespace_stackable(:after_validations)
end
afters() click to toggle source
# File lib/grape/endpoint.rb, line 402
def afters
  namespace_stackable(:afters)
end
before_validations() click to toggle source
# File lib/grape/endpoint.rb, line 394
def before_validations
  namespace_stackable(:before_validations)
end
befores() click to toggle source
# File lib/grape/endpoint.rb, line 390
def befores
  namespace_stackable(:befores)
end
execute() click to toggle source
# File lib/grape/endpoint.rb, line 343
def execute
  @block&.call(self)
end
finallies() click to toggle source
# File lib/grape/endpoint.rb, line 406
def finallies
  namespace_stackable(:finallies)
end
helpers() click to toggle source
# File lib/grape/endpoint.rb, line 347
def helpers
  lazy_initialize! && @helpers
end
lazy_initialize!() click to toggle source
# 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
options?() click to toggle source
# File lib/grape/endpoint.rb, line 418
def options?
  options[:options_route_enabled] &&
    env[Rack::REQUEST_METHOD] == Rack::OPTIONS
end
run() click to toggle source
# 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
run_filters(filters, type = :other) click to toggle source
# 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
run_validators(validators, request) click to toggle source
# 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
validations() { |create_validator(**saved_validation)| ... } click to toggle source
# 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

build_helpers() click to toggle source
# 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
build_stack(helpers) click to toggle source
# 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