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



91
92
93
# File 'lib/api_hammer/sinatra.rb', line 91

def check_accept
  response_media_type(:halt_if_unacceptable => true)
end

#check_params_and_object_consistent(path_params, object) ⇒ Object

for methods where parameters which are required in the path may also be specified in the body, this takes the path_params and the body object and ensures that they are consistent any place they are specified in the body.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/api_hammer/sinatra.rb', line 178

def check_params_and_object_consistent(path_params, object)
  errors = {}
  path_params.each do |(k, v)|
    if object.key?(k) && object[k] != v
      errors[k] = [I18n.t('app.errors.inconsistent_uri_and_entity',
        :key => k,
        :uri_value => v,
        :entity_value => object[k],
        :default => "Inconsistent data given in the request URI and request entity: %{key} was specified as %{uri_value} in the URI but %{entity_value} in the entity",
      )]
    end
  end
  if errors.any?
    halt_error(422, errors)
  end
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



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/api_hammer/sinatra.rb', line 99

def format_response(status, body_object, headers={})
  if status == 204
    body = ''
  else
    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
  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.



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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/api_hammer/sinatra.rb', line 131

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



116
117
118
119
120
121
122
123
# File 'lib/api_hammer/sinatra.rb', line 116

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.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/api_hammer/sinatra.rb', line 61

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



38
39
40
41
42
43
44
# File 'lib/api_hammer/sinatra.rb', line 38

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



48
49
50
# File 'lib/api_hammer/sinatra.rb', line 48

def supported_media_types
  self.class.supported_media_types
end