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 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
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 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
new(*args, &block) click to toggle source
Calls superclass method
# File lib/grape/endpoint.rb, line 14
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 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
run_before_each(endpoint) click to toggle source
# 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

call(env) click to toggle source
# File lib/grape/endpoint.rb, line 219
def call(env)
  lazy_initialize!
  dup.call!(env)
end
call!(env) click to toggle source
# File lib/grape/endpoint.rb, line 224
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 232
def endpoints
  options[:app].endpoints if options[:app] && options[:app].respond_to?(:endpoints)
end
equals?(e) click to toggle source
# File lib/grape/endpoint.rb, line 236
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 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
map_routes() { |method, path| ... } click to toggle source
# File lib/grape/endpoint.rb, line 206
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 202
def merge_route_options(**default)
  options[:route_options].clone.reverse_merge(**default)
end
method_name() click to toggle source
# 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
mount_in(router) click to toggle source
# 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
namespace() click to toggle source
# File lib/grape/endpoint.rb, line 215
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 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
prepare_path(path) click to toggle source
# 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
prepare_routes_requirements() click to toggle source
# 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
prepare_version() click to toggle source
# 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
require_option(options, key) click to toggle source
# File lib/grape/endpoint.rb, line 124
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 140
def reset_routes!
  endpoints.each(&:reset_routes!) if endpoints
  @namespace = nil
  @routes = nil
end
routes() click to toggle source
# File lib/grape/endpoint.rb, line 136
def routes
  @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
end
to_routes() click to toggle source
# 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

after_validations() click to toggle source
# File lib/grape/endpoint.rb, line 379
def after_validations
  namespace_stackable(:after_validations) || []
end
afters() click to toggle source
# File lib/grape/endpoint.rb, line 383
def afters
  namespace_stackable(:afters) || []
end
before_validations() click to toggle source
# File lib/grape/endpoint.rb, line 375
def before_validations
  namespace_stackable(:before_validations) || []
end
befores() click to toggle source
# File lib/grape/endpoint.rb, line 371
def befores
  namespace_stackable(:befores) || []
end
helpers() click to toggle source
# File lib/grape/endpoint.rb, line 324
def helpers
  lazy_initialize! && @helpers
end
lazy_initialize!() click to toggle source
# 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
options?() click to toggle source
# File lib/grape/endpoint.rb, line 391
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 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
run_filters(filters, type = :other) click to toggle source
# 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
run_validators(validator_factories, request) click to toggle source
# 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
validations() click to toggle source
# File lib/grape/endpoint.rb, line 387
def validations
  route_setting(:saved_validations) || []
end

Private Instance Methods

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