Module: ApiHammer::Sinatra

Includes:
Halt
Defined in:
lib/api_hammer/sinatra.rb,
lib/api_hammer/sinatra/halt.rb

Defined Under Namespace

Modules: Halt

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Halt

#halt

Methods included from HaltMethods

#find_or_halt, #halt_accepted, #halt_already_reported, #halt_authentication_timeout, #halt_bad_gateway, #halt_bad_request, #halt_bandwidth_limit_exceeded, #halt_conflict, #halt_created, #halt_error, #halt_expectation_failed, #halt_failed_dependency, #halt_forbidden, #halt_found, #halt_gateway_timeout, #halt_gone, #halt_http_version_not_supported, #halt_im_a_teapot, #halt_im_used, #halt_insufficient_storage, #halt_internal_server_error, #halt_length_required, #halt_locked, #halt_loop_detected, #halt_method_not_allowed, #halt_moved_permanently, #halt_multi_status, #halt_multiple_choices, #halt_network_authentication_required, #halt_no_content, #halt_no_response, #halt_non_authoritative_information, #halt_not_acceptable, #halt_not_extended, #halt_not_found, #halt_not_implemented, #halt_not_modified, #halt_ok, #halt_partial_content, #halt_payment_required, #halt_permanent_redirect, #halt_precondition_failed, #halt_precondition_required, #halt_proxy_authentication_required, #halt_redirect, #halt_request_entity_too_large, #halt_request_header_fields_too_large, #halt_request_timeout, #halt_request_uri_too_long, #halt_requested_range_not_satisfiable, #halt_reset_content, #halt_see_other, #halt_service_unavailable, #halt_temporary_redirect, #halt_too_many_requests, #halt_unauthorized, #halt_unavailable_for_legal_reasons, #halt_unordered_collection, #halt_unprocessable_entity, #halt_unsupported_media_type, #halt_upgrade_required, #halt_use_proxy, #halt_variant_also_negotiates

Class Method Details

.included(klass) ⇒ Object



5
6
7
8
9
# File 'lib/api_hammer/sinatra.rb', line 5

def self.included(klass)
  (@on_included || []).each do |included_proc|
    included_proc.call(klass)
  end
end

Instance Method Details

#check_accept ⇒ Object



77
78
79
# File 'lib/api_hammer/sinatra.rb', line 77

def check_accept
  response_media_type(:halt_if_unacceptable => true)
end

#format_response(status, body_object, headers = {}) ⇒ Object

returns a rack response with the given object encoded in the appropriate format for the requests.

arguments are in the order of what tends to vary most frequently rather than rack's way, so headers come last



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/api_hammer/sinatra.rb', line 85

def format_response(status, body_object, headers={})
  body = case response_media_type
  when 'application/json'
    JSON.pretty_generate(body_object)
  else
    # :nocov:
    raise NotImplementedError, "unsupported response media type #{response_media_type}"
    # :nocov:
  end
  [status, headers.merge({'Content-Type' => response_media_type}), [body]]
end

#parsed_body ⇒ Object

returns the parsed contents of the request body.

checks the Content-Type of the request, and unless it's supported (or omitted - in which case assumed to be the first supported media type), halts with 415.

if the body is not parseable, then halts with 400.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/api_hammer/sinatra.rb', line 113

def parsed_body
  request_media_type = request.media_type
  unless request_media_type =~ /\S/
    fallback = true
    request_media_type = supported_media_types.first
  end
  case request_media_type
  when 'application/json'
    begin
      return JSON.parse(request_body)
    rescue JSON::ParserError
      if fallback
        t_key = 'app.errors.request.body_parse_fallback_json'
        default = "Error encountered attempting to parse the request body. No Content-Type was specified and parsing as JSON failed. Supported media types are %{supported_media_types}. JSON parser error: %{error_class}: %{error_message}"
      else
        t_key = 'app.errors.request.body_parse_indicated_json'
        default = "Error encountered attempting to parse the JSON request body: %{error_class}: %{error_message}"
      end
      message = I18n.t(t_key,
        :default => default,
        :error_class => $!.class,
        :error_message => $!.message,
        :supported_media_types => supported_media_types.join(', ')
      )
      errors = {'json' => [message]}
      halt_error(400, errors)
    end
  else
    if supported_media_types.include?(request_media_type)
      # :nocov:
      raise NotImplementedError, "handling request body with media type #{request_media_type} not implemented"
      # :nocov:
    end
    logger.error "received Content-Type of #{request.content_type.inspect}; halting with 415"
    message = I18n.t('app.errors.request.content_type',
      :default => "Unsupported Content-Type of %{content_type} given for the request body. Supported media types are %{supported_media_types}",
      :content_type => request.content_type,
      :supported_media_types => supported_media_types.join(', ')
    )
    errors = {'Content-Type' => [message]}
    halt_error(415, errors)
  end
end

#request_body ⇒ Object

reads the request body



98
99
100
101
102
103
104
105
# File 'lib/api_hammer/sinatra.rb', line 98

def request_body
  # rewind in case anything in the past has left this un-rewound 
  request.body.rewind
  request.body.read.tap do
    # rewind in case anything in the future expects this to have been left rewound 
    request.body.rewind
  end
end

#response_media_type(options = {}) ⇒ Object

finds the best match (highest q) for those supported_media_types indicated as acceptable by the Accept header.

If the Accept header is not present, assumes that any supported media type is acceptable, and returns the first one.

if the :halt_if_unacceptable option is true and no supported media type is acceptable, this halts with 406.

if the :halt_if_unacceptable option is false (or omitted) and no supported media type is acceptable, this returns the first supported media type.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/api_hammer/sinatra.rb', line 47

def response_media_type(options={})
  options = {:halt_if_unacceptable => false}.merge(options)
  accept = env['HTTP_ACCEPT']
  if accept =~ /\S/
    begin
      best_media_type = env['rack-accept.request'].best_media_type(supported_media_types)
    rescue RuntimeError => e
      # TODO: this is a crappy way to recognize this exception 
      raise unless e.message =~ /Invalid header value/
    end
    if best_media_type
      best_media_type
    else
      if options[:halt_if_unacceptable]
        logger.error "received Accept header of #{accept.inspect}; halting with 406"
        message = I18n.t('app.errors.request.accept',
          :default => "The request indicated that no supported media type is acceptable. Supported media types are: %{supported_media_types}. The request specified Accept: %{accept}",
          :accept => accept,
          :supported_media_types => supported_media_types.join(', ')
        )
        halt_error(406, {'Accept' => [message]})
      else
        supported_media_types.first
      end
    end
  else
    supported_media_types.first
  end
end

#route_missing ⇒ Object

override Sinatra::Base#route_missing



12
13
14
15
16
17
18
# File 'lib/api_hammer/sinatra.rb', line 12

def route_missing
  message = I18n.t('app.errors.request.route_404',
    :default => "Not a known route: %{method} %{path}",
    :method => env['REQUEST_METHOD'], :path => env['PATH_INFO']
  )
  halt_error(404, {'route' => [message]})
end

#supported_media_types ⇒ Object



34
35
36
# File 'lib/api_hammer/sinatra.rb', line 34

def supported_media_types
  self.class.supported_media_types
end