class Grape::Middleware::Versioner::Header

This middleware sets various version related rack environment variables based on the HTTP Accept header with the pattern: application/vnd.:vendor-:version+:format

Example: For request header

Accept: application/vnd.mycompany.a-cool-resource-v1+json

The following rack env variables are set:

env['api.type']    => 'application'
env['api.subtype'] => 'vnd.mycompany.a-cool-resource-v1+json'
env['api.vendor]   => 'mycompany.a-cool-resource'
env['api.version]  => 'v1'
env['api.format]   => 'json'

If version does not match this route, then a 406 is raised with X-Cascade header to alert Grape::Router to attempt the next matched route.

Constants

HAS_VENDOR_REGEX
HAS_VERSION_REGEX
VENDOR_VERSION_HEADER_REGEX

Public Instance Methods

before() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 32
def before
  strict_header_checks if strict?

  if media_type || env[Grape::Env::GRAPE_ALLOWED_METHODS]
    media_type_header_handler
  elsif headers_contain_wrong_vendor?
    fail_with_invalid_accept_header!('API vendor not found.')
  elsif headers_contain_wrong_version?
    fail_with_invalid_version_header!('API version not found.')
  end
end

Private Instance Methods

an_accept_header_with_version_and_vendor_is_present?() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 62
def an_accept_header_with_version_and_vendor_is_present?
  header.qvalues.keys.any? do |h|
    VENDOR_VERSION_HEADER_REGEX =~ h.sub('application/', '')
  end
end
available_media_types() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 99
def available_media_types
  available_media_types = []

  content_types.each do |extension, _media_type|
    versions.reverse_each do |version|
      available_media_types += [
        "application/vnd.#{vendor}-#{version}+#{extension}",
        "application/vnd.#{vendor}-#{version}"
      ]
    end
    available_media_types << "application/vnd.#{vendor}+#{extension}"
  end

  available_media_types << "application/vnd.#{vendor}"

  content_types.each do |_, media_type|
    available_media_types << media_type
  end

  available_media_types.flatten
end
cascade?() click to toggle source

By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking of routes (see Grape::Router for more information). To prevent # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.

# File lib/grape/middleware/versioner/header.rb, line 160
def cascade?
  if version_options && version_options.key?(:cascade)
    version_options[:cascade]
  else
    true
  end
end
error_headers() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 168
def error_headers
  cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
end
fail_with_invalid_accept_header!(message) click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 89
def fail_with_invalid_accept_header!(message)
  raise Grape::Exceptions::InvalidAcceptHeader
    .new(message, error_headers)
end
fail_with_invalid_version_header!(message) click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 94
def fail_with_invalid_version_header!(message)
  raise Grape::Exceptions::InvalidVersionHeader
    .new(message, error_headers)
end
header() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 68
def header
  @header ||= rack_accept_header
end
headers_contain_wrong_vendor?() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 121
def headers_contain_wrong_vendor?
  header.values.all? do |header_value|
    vendor?(header_value) && request_vendor(header_value) != vendor
  end
end
headers_contain_wrong_version?() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 127
def headers_contain_wrong_version?
  header.values.all? do |header_value|
    version?(header_value) && !versions.include?(request_version(header_value))
  end
end
media_type() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 72
def media_type
  @media_type ||= header.best_of(available_media_types)
end
media_type_header_handler() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 76
def media_type_header_handler
  type, subtype = Rack::Accept::Header.parse_media_type(media_type)
  env[Grape::Env::API_TYPE] = type
  env[Grape::Env::API_SUBTYPE] = subtype

  return unless VENDOR_VERSION_HEADER_REGEX =~ subtype

  env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
  env[Grape::Env::API_VERSION] = Regexp.last_match[2]
  # weird that Grape::Middleware::Formatter also does this
  env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
end
rack_accept_header() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 133
def rack_accept_header
  Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
rescue RuntimeError => e
  fail_with_invalid_accept_header!(e.message)
end
request_vendor(media_type) click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 179
def request_vendor(media_type)
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
  subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
end
request_version(media_type) click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 184
def request_version(media_type)
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
  subtype.match(VENDOR_VERSION_HEADER_REGEX)[2]
end
strict?() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 147
def strict?
  version_options && version_options[:strict]
end
strict_accept_header_presence_check() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 51
def strict_accept_header_presence_check
  return unless header.qvalues.empty?
  fail_with_invalid_accept_header!('Accept header must be set.')
end
strict_header_checks() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 46
def strict_header_checks
  strict_accept_header_presence_check
  strict_version_vendor_accept_header_presence_check
end
strict_version_vendor_accept_header_presence_check() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 56
def strict_version_vendor_accept_header_presence_check
  return unless versions.present?
  return if an_accept_header_with_version_and_vendor_is_present?
  fail_with_invalid_accept_header!('API vendor or version not found.')
end
vendor() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 143
def vendor
  version_options && version_options[:vendor]
end
vendor?(media_type) click to toggle source

@param [String] #media_type a content type @return [Boolean] whether the content type sets a vendor

# File lib/grape/middleware/versioner/header.rb, line 174
def vendor?(media_type)
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
  subtype[HAS_VENDOR_REGEX]
end
version?(media_type) click to toggle source

@param [String] #media_type a content type @return [Boolean] whether the content type sets an API version

# File lib/grape/middleware/versioner/header.rb, line 191
def version?(media_type)
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
  subtype[HAS_VERSION_REGEX]
end
version_options() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 151
def version_options
  options[:version_options]
end
versions() click to toggle source
# File lib/grape/middleware/versioner/header.rb, line 139
def versions
  options[:versions] || []
end