Class: Hoodoo::Services::Response
- Inherits:
-
Object
- Object
- Hoodoo::Services::Response
- Defined in:
- lib/hoodoo/services/services/response.rb
Overview
The service middleware creates a Hoodoo::Services::Response instance for each request it handles, populating it with some data before and after the service implementation runs as part of standard pre- and post-processing. In the middle, the service implementation is given the instance and adds its own data to it.
The instance carries data about both error conditions and successful work. In the successful case, #http_status_code and #body data is set by the service and used in the response. In the error case (see #errors), the HTTP status code is taken from the first error in the errors collection and the response body will be the JSON representation of that collection - any HTTP status code or response body data previously set by the service will be ignored.
Instance Attribute Summary collapse
-
#body ⇒ Object
A service implementation can set (and read back, should it wish) the API call response body data using this #body / #body= accessor.
-
#dataset_size ⇒ Object
readonly
Read back a dataset size given by a prior call to #set_resources, or
nil
if none has been provided (either the response contains no list yet/at all, or an Array was given but the dataset size was not supplied). -
#errors ⇒ Object
Obtain a reference to the Hoodoo::Errors instance for this response; use Hoodoo::Errors#add_error to add to the collection directly.
-
#estimated_dataset_size ⇒ Object
readonly
Read back an estimated dataset size given by a prior call to #set_resources, or
nil
if none has been provided (either the response contains no list yet/at all, or an Array was given but a dataset size estimation was not supplied). -
#http_status_code ⇒ Object
HTTP status code that will be involved in the response.
Instance Method Summary collapse
-
#add_error(code, options = nil) ⇒ Object
Add an error to the internal collection.
-
#add_errors(errors_object) ⇒ Object
Add errors from a Hoodoo::Errors instance to this response’s error collection.
-
#add_header(name, value, overwrite = false) ⇒ Object
Add an HTTP header to the internal collection that will be used for the response.
-
#add_precompiled_error(code, message, reference, http_status = 500) ⇒ Object
Add a precompiled error to the error collection.
-
#contemporary_exists(ident) ⇒ Object
Add the standard error message ‘
generic.contemporary_exists
’ to this response. -
#for_rack ⇒ Object
Convert the internal response data into something that Rack expects.
-
#get_header(name) ⇒ Object
Check the stored value of a given HTTP header.
-
#halt_processing? ⇒ Boolean
Returns
true
if processing should halt, e.g. -
#headers ⇒ Object
Returns the list previously set headers in a name: value Hash.
-
#initialize(interaction_id) ⇒ Response
constructor
Create a new instance, ready to take on a response.
-
#not_found(ident) ⇒ Object
Add the standard error message ‘
generic.not_found
’ to this response. -
#set_estimated_resources(array, estimated_dataset_size = nil) ⇒ Object
A companion to #set_resources.
-
#set_resources(array, dataset_size = nil) ⇒ Object
Similar to #body and #set_resource, but used when you are returning an array of items.
Constructor Details
#initialize(interaction_id) ⇒ Response
Create a new instance, ready to take on a response. The service middleware is responsible for doing this.
interaction_id
-
The UUID of the interaction taking place for which a response is required.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/hoodoo/services/services/response.rb', line 95 def initialize( interaction_id ) unless Hoodoo::UUID.valid?( interaction_id ) raise "Hoodoo::Services::Response.new must be given a valid Interaction ID (got '#{ interaction_id.inspect }')" end @interaction_id = interaction_id @errors = Hoodoo::Errors.new() @headers = {} @http_status_code = 200 @body = {} @dataset_size = nil @estimated_dataset_size = nil end |
Instance Attribute Details
#body ⇒ Object
A service implementation can set (and read back, should it wish) the API call response body data using this #body / #body= accessor. This is converted to a client-facing representation automatically (e.g. to JSON).
The response body MUST be either a Ruby Array
or a Ruby Hash
. For internal use only a Ruby String
of pre-encoded response data is also accepted.
This method is aliased as #set_resource, for semantic use when you want to set the response body to a representation (as a Hash) of a resource. When you want to set an Array of items for a list, it is strongly recommended that you call #set_resources and pass a total dataset size in addition to just the Array containing a page of list data.
When reading response data, the body information is only valid if method #halt_processing? returns false
.
71 72 73 |
# File 'lib/hoodoo/services/services/response.rb', line 71 def body @body end |
#dataset_size ⇒ Object (readonly)
Read back a dataset size given by a prior call to #set_resources, or nil
if none has been provided (either the response contains no list yet/at all, or an Array was given but the dataset size was not supplied). If the dataset size is absent, an estimation may be present; see #estimated_dataset_size.
80 81 82 |
# File 'lib/hoodoo/services/services/response.rb', line 80 def dataset_size @dataset_size end |
#errors ⇒ Object
Obtain a reference to the Hoodoo::Errors instance for this response; use Hoodoo::Errors#add_error to add to the collection directly. For convenience, this class also provides the #add_error proxy instance method (syntactic sugar for most service implementations, but with a return value that helps keep the service middleware code clean).
It’s possible to change the errors object if you want to swap it for any reason, though this is generally discouraged - especially if the existing errors collection isn’t empty. The middleware does this as part of request handling, but generally speaking nobody else should need to.
45 46 47 |
# File 'lib/hoodoo/services/services/response.rb', line 45 def errors @errors end |
#estimated_dataset_size ⇒ Object (readonly)
Read back an estimated dataset size given by a prior call to #set_resources, or nil
if none has been provided (either the response contains no list yet/at all, or an Array was given but a dataset size estimation was not supplied).
87 88 89 |
# File 'lib/hoodoo/services/services/response.rb', line 87 def estimated_dataset_size @estimated_dataset_size end |
#http_status_code ⇒ Object
HTTP status code that will be involved in the response. Default is 200. Integer, or something that can be converted to one with to_i
. If errors are added to the response then the status code is derived from the first error in the collection, overriding any value set here. See #errors.
52 53 54 |
# File 'lib/hoodoo/services/services/response.rb', line 52 def http_status_code @http_status_code end |
Instance Method Details
#add_error(code, options = nil) ⇒ Object
Add an error to the internal collection. Passes input parameters through to Hoodoo::Errors#add_error, so see that for details. For convenience, returns the for-rack representation of the response so far, so that code which wishes to add one error and abort request processing immediately can just do:
return response_object.add_error( ... )
…as part of processing a Rack invocation of the call
method. This is really only useful for the service middleware.
code
-
Error code (e.g. “platform.generic”).
options
-
Options Hash - see Hoodoo::Errors#add_error.
Example:
response.add_error(
'generic.not_found',
'message' => 'Optional custom message',
'reference' => { :ident => 'mandatory reference data' }
)
In the above example, the mandatory reference data uuid
comes from the description for the ‘platform.not_found’ message - see the Hoodoo::ErrorDescriptions#initialize implementation and Platform API.
262 263 264 265 |
# File 'lib/hoodoo/services/services/response.rb', line 262 def add_error( code, = nil ) @errors.add_error( code, ) return for_rack() end |
#add_errors(errors_object) ⇒ Object
Add errors from a Hoodoo::Errors instance to this response’s error collection.
errors_object
-
Hoodoo::Errors instance to merge into the error collection of ‘this’ response object.
Returns true
if errors were merged, else false
(the source collection was empty).
298 299 300 |
# File 'lib/hoodoo/services/services/response.rb', line 298 def add_errors( errors_object ) return @errors.merge!( errors_object ) end |
#add_header(name, value, overwrite = false) ⇒ Object
Add an HTTP header to the internal collection that will be used for the response. Trying to set data for the same HTTP header name more than once will result in an exception being raised unless the overwrite
parameter is used (this is strongly discouraged in the general case).
name
-
Correct case and punctuation HTTP header name (e.g. “Content-Type”).
value
-
Value for the header, as a string or something that behaves sensibly when
to_s
is invoked upon it. overwrite
-
Optional. Pass
true
to allow the same HTTP header name to be set more than once - the new value overwrites the old. By default this is prohibited and an exception will be raised to avoid accidental value overwrites.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/hoodoo/services/services/response.rb', line 201 def add_header( name, value, overwrite = false ) name = name.to_s dname = name.downcase value = value.to_s if ( overwrite == false && @headers.has_key?( dname ) ) hash = @headers[ dname ] name = hash.keys[ 0 ] value = hash.values[ 0 ] raise "Hoodoo::Services::Response\#add_header: Value '#{ value }' already defined for header '#{ name }'" else @headers[ dname ] = { name => value } end end |
#add_precompiled_error(code, message, reference, http_status = 500) ⇒ Object
Add a precompiled error to the error collection. Pass error code, error message and reference data directly.
In most cases you should be calling #add_error instead, NOT here.
No validation is performed. You should only really call here if storing an error / errors from another, trusted source with assumed validity (e.g. another service called remotely with errors in the JSON response). It’s possible to store invalid error data using this call, which means counter-to-documentation results could be returned to API clients. That is Very Bad.
Pass optionally the HTTP status code to use if this happens to be the first stored error. If this is omitted, 500 is kept as the default.
As with #add_error, returns a Rack representation of the response.
284 285 286 287 |
# File 'lib/hoodoo/services/services/response.rb', line 284 def add_precompiled_error( code, , reference, http_status = 500 ) @errors.add_precompiled_error( code, , reference, http_status ) return for_rack() end |
#contemporary_exists(ident) ⇒ Object
Add the standard error message ‘generic.contemporary_exists
’ to this response. Optionally used during a ‘show’ call for a historical dating-aware resource, when an instance does not exist at a requested historic date/time, but a contemporary version is present.
ident
-
The identifier of the resource which was not found
Example for resource implementations with a context
available:
resource = SomeModel.acquire_in( context )
if resource.nil?
ident = context.request.ident
context.response.not_found( ident )
# You'd omit "#manually_dated_contemporary" if using automatic
# dating (but manual dating is recommended over automatic for
# performance reasons).
#
contemporary_resource = SomeModel.
scoped_undated_in( context ).
manually_dated_contemporary().
acquire( ident )
# Use of ActiveRecord means some ActiveSupport extensions
# such as "#present?" will be available.
#
context.response.contemporary_exists( ident ) if contemporary_resource.present?
end
An even higher level approach through context
, through which this method is called automatically:
resource = SomeModel.acquire_in!( context )
return if context.response.halt_processing?
This frees application authors of the burden of constructing an appropriately secure and “now-dated” scope for the contemporary resource lookup.
See also:
-
#not_found
-
Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in
-
Hoodoo::ActiveRecord::Finder::ClassMethods#scoped_undated_in
-
Hoodoo::ActiveRecord::ManuallyDated::ClassMethods#manually_dated_contemporary
373 374 375 |
# File 'lib/hoodoo/services/services/response.rb', line 373 def contemporary_exists( ident ) @errors.add_error( 'generic.contemporary_exists', :reference => { :ident => ident } ) end |
#for_rack ⇒ Object
Convert the internal response data into something that Rack expects. The return value of this method can be passed back to Rack from Rack middleware or applications. Usually, this is only called directly by Hoodoo::Services::Middleware.
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/hoodoo/services/services/response.rb', line 382 def for_rack rack_response = Rack::Response.new # Work out the status code and basic response body if @errors.has_errors? http_status_code = @errors.http_status_code body_data = @errors.render( @interaction_id ) else http_status_code = @http_status_code body_data = @body end rack_response.status = http_status_code.to_i # We're not using JSON5, so the Platform API says that outmost arrays # are wrapped with a top-level object key "_data". if body_data.is_a?( ::Array ) response_hash = { '_data' => body_data } response_hash[ '_dataset_size' ] = @dataset_size unless @dataset_size.nil? response_hash[ '_estimated_dataset_size' ] = @estimated_dataset_size unless @estimated_dataset_size.nil? response_string = ::JSON.generate( response_hash ) elsif body_data.is_a?( ::Hash ) response_string = ::JSON.generate( body_data ) elsif body_data.is_a?( ::String ) response_string = body_data else raise "Hoodoo::Services::Response\#for_rack given unrecognised body data class '#{ body_data.class.name }'" end rack_response.write( response_string ) # Finally, sort out the headers headers().each do | header_name, header_value | rack_response[ header_name ] = header_value end # Return the complete response return rack_response.finish end |
#get_header(name) ⇒ Object
Check the stored value of a given HTTP header. Checks are case insensitive. Returns the value stored by a prior #add_header call, or nil
for no value (or an explicitly stored value of nil
)
name
-
HTTP header name (e.g. “Content-Type”, “CONTENT-TYPE”).
222 223 224 225 226 |
# File 'lib/hoodoo/services/services/response.rb', line 222 def get_header( name ) value_hash = @headers[ name.downcase ] return nil if value_hash.nil? return value_hash.values[ 0 ] end |
#halt_processing? ⇒ Boolean
Returns true
if processing should halt, e.g. because errors have been added to the errors collection. Check here whenever you would consider an early exit due to errors arising in processing (otherwise they will just continue to accumulate).
116 117 118 |
# File 'lib/hoodoo/services/services/response.rb', line 116 def halt_processing? @errors.has_errors? end |
#headers ⇒ Object
Returns the list previously set headers in a name: value Hash.
230 231 232 233 234 |
# File 'lib/hoodoo/services/services/response.rb', line 230 def headers @headers.inject( {} ) do | result, kv_array | result.merge( kv_array[ 1 ] ) end end |
#not_found(ident) ⇒ Object
Add the standard error message ‘generic.not_found
’ to this response. Used during a ‘show’ call when the requested resource does not exist.
ident
-
The identifier of the resource which was not found
Low level example:
return response.not_found( ident ) if resource.nil?
High level example for resource implementations with a context
available, through which this method is called automatically:
resource = SomeModel.acquire_in!( context )
return if context.response.halt_processing?
See also:
-
#contemporary_exists
-
Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in
322 323 324 |
# File 'lib/hoodoo/services/services/response.rb', line 322 def not_found( ident ) @errors.add_error( 'generic.not_found', :reference => { :ident => ident } ) end |
#set_estimated_resources(array, estimated_dataset_size = nil) ⇒ Object
A companion to #set_resources. See the documentation of that method for background information.
If the persistence layer in use and data volumes expected for a given resource make accurate counting too slow to compute, your persistence layer might support a mechanism for producing an estimated count quickly instead. For example, PostgreSQL 9’s row counting can be slow due to MVCC but there are PostgreSQL-specific ways of obtaining a row count estimation quickly. If this applies to you, call here to correctly specify the estimation in a way that makes it clear to the calling client that it’s not an accurate result.
Technically you could call both this and #set_resources to set both an accurate and an estimated count, though it’s hard to imagine a use case for this outside of testing scenarios; but note that each call will override any previous setting of the #body property.
If using the Hoodoo::ActiveRecord extensions for your persistence layer, then please also see Hoodoo::ActiveRecord::Finder::ClassMethods::estimated_dataset_size.
array
-
Array of resource representations (Ruby Array with Ruby Hash entries representing rendered resources, ideally through the Hoodoo::Presenters framework).
estimated_dataset_size
-
Optional total number of items in the entire dataset of which
array
is, most likely, just a subset due to paginated lists via offset and limit parameters; this value is an estimation with undefined accuracy.
180 181 182 183 |
# File 'lib/hoodoo/services/services/response.rb', line 180 def set_estimated_resources( array, estimated_dataset_size = nil ) self.body = array @estimated_dataset_size = estimated_dataset_size end |
#set_resources(array, dataset_size = nil) ⇒ Object
Similar to #body and #set_resource, but used when you are returning an array of items. Although you can just assign an array to either of #body or #set_resource, calling #set_resources is more semantically correct and provides an additional feature; you can specify the total number of items in the dataset either precisely, or as an estimation.
For example, if you were listing a page of 50 resource instances but the total matching dataset of that list included 344 instances, you would pass 344 in the dataset_size
input parameter. This is optional but highly recommended as it is often very useful for calling clients.
If for any reason you aren’t able to quickly produce an accurate count but can produce an estimation, call #set_estimated_resources instead.
array
-
Array of resource representations (Ruby Array with Ruby Hash entries representing rendered resources, ideally through the Hoodoo::Presenters framework).
dataset_size
-
Optional total number of items in the entire dataset of which
array
is, most likely, just a subset due to paginated lists via offset and limit parameters. This value was accurate at the instant of counting.
143 144 145 146 |
# File 'lib/hoodoo/services/services/response.rb', line 143 def set_resources( array, dataset_size = nil ) self.body = array @dataset_size = dataset_size end |