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(endpoint_instance).call
    end
  end
end
new(*args, &block) click to toggle source
Calls superclass method
# File lib/grape/endpoint.rb, line 16
def new(*args, &block)
  self == Endpoint ? Class.new(Endpoint).new(*args, &block) : 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 217
def call(env)
  lazy_initialize!
  dup.call!(env)
end
call!(env) click to toggle source
# File lib/grape/endpoint.rb, line 222
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 230
def endpoints
  options[:app].endpoints if options[:app].respond_to?(:endpoints)
end
equals?(e) click to toggle source
# File lib/grape/endpoint.rb, line 234
def equals?(e)
  (options == e.options) && (inheritable_setting.to_hash == e.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)
  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
map_routes() { |method, path| ... } click to toggle source
# File lib/grape/endpoint.rb, line 204
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 200
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 << 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
namespace() click to toggle source
# File lib/grape/endpoint.rb, line 213
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 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
prepare_path(path) click to toggle source
# 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
prepare_routes_requirements() click to toggle source
# 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
prepare_version() click to toggle source
# 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
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 384
def after_validations
  namespace_stackable(:after_validations) || []
end
afters() click to toggle source
# File lib/grape/endpoint.rb, line 388
def afters
  namespace_stackable(:afters) || []
end
before_validations() click to toggle source
# File lib/grape/endpoint.rb, line 380
def before_validations
  namespace_stackable(:before_validations) || []
end
befores() click to toggle source
# File lib/grape/endpoint.rb, line 376
def befores
  namespace_stackable(:befores) || []
end
execute() click to toggle source
# File lib/grape/endpoint.rb, line 327
def execute
  @block ? @block.call(self) : nil
end
finallies() click to toggle source
# File lib/grape/endpoint.rb, line 392
def finallies
  namespace_stackable(:finallies) || []
end
helpers() click to toggle source
# File lib/grape/endpoint.rb, line 331
def helpers
  lazy_initialize! && @helpers
end
lazy_initialize!() click to toggle source
# 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
options?() click to toggle source
# File lib/grape/endpoint.rb, line 400
def options?
  options[:options_route_enabled] &&
    env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS
end
run() click to toggle source
# 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
run_filters(filters, type = :other) click to toggle source
# 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
run_validators(validator_factories, request) click to toggle source
# 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
validations() click to toggle source
# File lib/grape/endpoint.rb, line 396
def validations
  route_setting(:saved_validations) || []
end

Private Instance Methods

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