module Hanami::Action::Mime

Mime type API

@since 0.1.0

@see Hanami::Action::Mime::ClassMethods#accept

Constants

CONTENT_TYPE

The header key to set the mime type of the response

@since 0.1.0 @api private

DEFAULT_ACCEPT

The default mime type for an incoming HTTP request

@since 0.1.0 @api private

DEFAULT_CHARSET

The default charset that is returned in the response

@since 0.3.0 @api private

DEFAULT_CONTENT_TYPE

The default mime type that is returned in the response

@since 0.1.0 @api private

HTTP_ACCEPT

The key that returns accepted mime types from the Rack env

@since 0.1.0 @api private

HTTP_CONTENT_TYPE

The key that returns content mime type from the Rack env

@since 1.2.0 @api private

MIME_TYPES

Most commom MIME Types used for responses

@since 1.0.0 @api private

Public Class Methods

included(base) click to toggle source

Override Ruby's hook for modules. It includes Mime types logic

@param base [Class] the target action

@since 0.1.0 @api private

@see www.ruby-doc.org/core-2.1.2/Module.html#method-i-included

# File lib/hanami/action/mime.rb, line 116
def self.included(base)
  base.class_eval do
    extend ClassMethods
    prepend InstanceMethods
  end
end

Public Instance Methods

charset() click to toggle source

The charset that will be automatically set in the response.

It prefers, in order:

* Explicit set value (see #charset=)
* Default configuration charset
* Default content type

To override the value, use #charset=

@return [String] the charset of the request.

@since 0.3.0

@see Hanami::Action::Mime#charset= @see Hanami::Configuration#default_charset @see Hanami::Action::Mime#default_charset @see Hanami::Action::Mime#DEFAULT_CHARSET

@example

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    charset # => 'text/html'
  end
end
# File lib/hanami/action/mime.rb, line 360
def charset
  @charset || default_charset || DEFAULT_CHARSET
end
charset=(value) click to toggle source

Action charset setter, receives new charset value

@return [String] the charset of the request.

@since 0.3.0

@example

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    self.charset = 'koi8-r'
  end
end
# File lib/hanami/action/mime.rb, line 327
def charset=(value)
  @charset = value
end
content_type() click to toggle source

The content type that will be automatically set in the response.

It prefers, in order:

* Explicit set value (see Hanami::Action::Mime#format=)
* Weighted value from Accept header based on all known MIME Types:
  - Custom registered MIME Types (see Hanami::Controller::Configuration#format)
* Configured default content type (see Hanami::Controller::Configuration#default_response_format)
* Hard-coded default content type (see Hanami::Action::Mime::DEFAULT_CONTENT_TYPE)

To override the value, use #format=

@return [String] the content type from the request.

@since 0.1.0

@see Hanami::Action::Mime#format= @see Hanami::Configuration#default_request_format @see Hanami::Action::Mime#default_content_type @see Hanami::Action::Mime#DEFAULT_CONTENT_TYPE @see Hanami::Controller::Configuration#format @see Hanami::Controller::Configuration#default_response_format

@example

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    content_type # => 'text/html'
  end
end
# File lib/hanami/action/mime.rb, line 304
def content_type
  return @content_type unless @content_type.nil?
  @content_type = content_type_from_accept_header if accept_header?
  @content_type || default_response_type || default_content_type || DEFAULT_CONTENT_TYPE
end
format() click to toggle source

Returns a symbol representation of the content type.

The framework automatically detects the request mime type, and returns the corresponding format.

However, if this value was explicitly set by `#format=`, it will return that value

@return [Symbol] a symbol that corresponds to the content type

@since 0.2.0

@see Hanami::Action::Mime#format= @see Hanami::Action::Mime#content_type

@example Default scenario

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
  end
end

action = Show.new

_, headers, _ = action.call({ 'HTTP_ACCEPT' => 'text/html' })
headers['Content-Type'] # => 'text/html'
action.format           # => :html

@example Set value

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    self.format = :xml
  end
end

action = Show.new

_, headers, _ = action.call({ 'HTTP_ACCEPT' => 'text/html' })
headers['Content-Type'] # => 'application/xml'
action.format           # => :xml
# File lib/hanami/action/mime.rb, line 267
def format
  @format ||= detect_format
end

Private Instance Methods

accept() click to toggle source

@since 0.1.0 @api private

# File lib/hanami/action/mime.rb, line 529
def accept
  @accept ||= @_env[HTTP_ACCEPT] || DEFAULT_ACCEPT
end
accept?(mime_type) click to toggle source

Match the given mime type with the Accept header

@return [Boolean] true if the given mime type matches Accept

@since 0.1.0

@example

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    # @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'

    accept?('text/html')        # => true
    accept?('application/xml')  # => true
    accept?('application/json') # => false

    # @_env['HTTP_ACCEPT'] # => '*/*'

    accept?('text/html')        # => true
    accept?('application/xml')  # => true
    accept?('application/json') # => true
  end
end
# File lib/hanami/action/mime.rb, line 521
def accept?(mime_type)
  !!::Rack::Utils.q_values(accept).find do |mime, _|
    ::Rack::Mime.match?(mime_type, mime)
  end
end
accept_header?() click to toggle source

Checks if there is an Accept header for the current request.

@return [TrueClass,FalseClass] the result of the check

@since 0.8.0 @api private

# File lib/hanami/action/mime.rb, line 539
def accept_header?
  accept != DEFAULT_ACCEPT
end
best_q_match(q_value_header, available_mimes) click to toggle source

Patched version of Rack::Utils.best_q_match.

@since 0.4.1 @api private

@see www.rubydoc.info/gems/rack/Rack/Utils#best_q_match-class_method @see github.com/rack/rack/pull/659 @see github.com/hanami/controller/issues/59 @see github.com/hanami/controller/issues/104 @see github.com/hanami/controller/issues/275

# File lib/hanami/action/mime.rb, line 602
def best_q_match(q_value_header, available_mimes)
  ::Rack::Utils.q_values(q_value_header).each_with_index.map do |(req_mime, quality), index|
    match = available_mimes.find { |am| ::Rack::Mime.match?(am, req_mime) }
    next unless match
    RequestMimeWeight.new(req_mime, quality, index, match)
  end.compact.max&.format
end
content_type_from_accept_header() click to toggle source

Look at the Accept header for the current request and see if it matches any of the common MIME types (see Hanami::Action::Mime#MIME_TYPES) or the custom registered ones (see Hanami::Controller::Configuration#format).

@return [String,Nil] The matched MIME type for the given Accept header.

@since 0.8.0 @api private

@see Hanami::Action::Mime#MIME_TYPES @see Hanami::Controller::Configuration#format

@api private

# File lib/hanami/action/mime.rb, line 556
def content_type_from_accept_header
  best_q_match(accept, configuration.mime_types)
end
content_type_with_charset() click to toggle source

@since 0.3.0 @api private

# File lib/hanami/action/mime.rb, line 588
def content_type_with_charset
  "#{content_type}; charset=#{charset}"
end
default_charset() click to toggle source

@since 0.3.0 @api private

# File lib/hanami/action/mime.rb, line 582
def default_charset
  configuration.default_charset
end
default_content_type() click to toggle source

@since 0.2.0 @api private

# File lib/hanami/action/mime.rb, line 568
def default_content_type
  self.class.format_to_mime_type(
    configuration.default_request_format
  ) if configuration.default_request_format
end
default_response_type() click to toggle source

@since 0.5.0 @api private

# File lib/hanami/action/mime.rb, line 562
def default_response_type
  self.class.format_to_mime_type(configuration.default_response_format) if configuration.default_response_format
end
detect_format() click to toggle source

@since 0.2.0 @api private

# File lib/hanami/action/mime.rb, line 576
def detect_format
  configuration.format_for(content_type) || MIME_TYPES.key(content_type)
end
finish() click to toggle source

Finalize the response by setting the current content type

@since 0.1.0 @api private

@see Hanami::Action#finish

Calls superclass method
# File lib/hanami/action/mime.rb, line 372
def finish
  super
  headers[CONTENT_TYPE] ||= content_type_with_charset
end
format=(format) click to toggle source

Sets the given format and corresponding content type.

The framework detects the `HTTP_ACCEPT` header of the request and sets the proper `Content-Type` header in the response. Within this default scenario, `#format` returns a symbol that corresponds to `#content_type`. For instance, if a client sends an `HTTP_ACCEPT` with `text/html`, `#content_type` will return `text/html` and `#format` `:html`.

However, it's possible to override what the framework have detected. If a client asks for an `HTTP_ACCEPT` `/`, but we want to force the response to be a `text/html` we can use this method.

When the format is set, the framework searches for a corresponding mime type to be set as the `Content-Type` header of the response. This lookup is performed first in the configuration, and then in `Hanami::Action::Mime::MIME_TYPES`. If the lookup fails, it raises an error.

PERFORMANCE: Because `Hanami::Controller::Configuration#formats` is smaller and looked up first than `Hanami::Action::Mime::MIME_TYPES`, we suggest to configure the most common mime types used by your application, **even if they are already present in that Rack constant**.

@param format [#to_sym] the format

@return [void]

@raise [TypeError] if the format cannot be coerced into a Symbol @raise [Hanami::Controller::UnknownFormatError] if the format doesn't

have a corresponding mime type

@since 0.2.0

@see Hanami::Action::Mime#format @see Hanami::Action::Mime#content_type @see Hanami::Controller::Configuration#format

@example Default scenario

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
  end
end

action = Show.new

_, headers, _ = action.call({ 'HTTP_ACCEPT' => '*/*' })
headers['Content-Type'] # => 'application/octet-stream'
action.format           # => :all

_, headers, _ = action.call({ 'HTTP_ACCEPT' => 'text/html' })
headers['Content-Type'] # => 'text/html'
action.format           # => :html

@example Simple usage

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    self.format = :json
  end
end

action = Show.new

_, headers, _ = action.call({ 'HTTP_ACCEPT' => '*/*' })
headers['Content-Type'] # => 'application/json'
action.format           # => :json

@example Unknown format

require 'hanami/controller'

class Show
  include Hanami::Action

  def call(params)
    # ...
    self.format = :unknown
  end
end

action = Show.new
action.call({ 'HTTP_ACCEPT' => '*/*' })
  # => raise Hanami::Controller::UnknownFormatError

@example Custom mime type/format

require 'hanami/controller'

Hanami::Controller.configure do
  format :custom, 'application/custom'
end

class Show
  include Hanami::Action

  def call(params)
    # ...
    self.format = :custom
  end
end

_, headers, _ = action.call({ 'HTTP_ACCEPT' => '*/*' })
headers['Content-Type'] # => 'application/custom'
action.format           # => :custom
# File lib/hanami/action/mime.rb, line 487
def format=(format)
  @format       = Utils::Kernel.Symbol(format)
  @content_type = self.class.format_to_mime_type(@format)
end