hoodoo 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/hoodoo +5 -0
- data/lib/hoodoo.rb +27 -0
- data/lib/hoodoo/active.rb +32 -0
- data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
- data/lib/hoodoo/active/active_record/base.rb +81 -0
- data/lib/hoodoo/active/active_record/creator.rb +134 -0
- data/lib/hoodoo/active/active_record/dated.rb +343 -0
- data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
- data/lib/hoodoo/active/active_record/finder.rb +606 -0
- data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
- data/lib/hoodoo/active/active_record/secure.rb +431 -0
- data/lib/hoodoo/active/active_record/support.rb +106 -0
- data/lib/hoodoo/active/active_record/translated.rb +87 -0
- data/lib/hoodoo/active/active_record/uuid.rb +80 -0
- data/lib/hoodoo/active/active_record/writer.rb +321 -0
- data/lib/hoodoo/client.rb +23 -0
- data/lib/hoodoo/client/augmented_array.rb +29 -0
- data/lib/hoodoo/client/augmented_base.rb +168 -0
- data/lib/hoodoo/client/augmented_hash.rb +23 -0
- data/lib/hoodoo/client/client.rb +354 -0
- data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
- data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
- data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
- data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
- data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
- data/lib/hoodoo/client/headers.rb +269 -0
- data/lib/hoodoo/communicators.rb +23 -0
- data/lib/hoodoo/communicators/fast.rb +44 -0
- data/lib/hoodoo/communicators/pool.rb +601 -0
- data/lib/hoodoo/communicators/slow.rb +84 -0
- data/lib/hoodoo/data.rb +51 -0
- data/lib/hoodoo/data/resources/caller.rb +39 -0
- data/lib/hoodoo/data/resources/errors.rb +28 -0
- data/lib/hoodoo/data/resources/log.rb +31 -0
- data/lib/hoodoo/data/resources/session.rb +26 -0
- data/lib/hoodoo/data/types/error_primitive.rb +27 -0
- data/lib/hoodoo/data/types/permissions.rb +40 -0
- data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
- data/lib/hoodoo/data/types/permissions_full.rb +28 -0
- data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
- data/lib/hoodoo/discovery.rb +20 -0
- data/lib/hoodoo/errors.rb +19 -0
- data/lib/hoodoo/errors/error_descriptions.rb +229 -0
- data/lib/hoodoo/errors/errors.rb +322 -0
- data/lib/hoodoo/generator.rb +139 -0
- data/lib/hoodoo/logger.rb +23 -0
- data/lib/hoodoo/logger/fast_writer.rb +27 -0
- data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
- data/lib/hoodoo/logger/logger.rb +387 -0
- data/lib/hoodoo/logger/slow_writer.rb +49 -0
- data/lib/hoodoo/logger/writer_mixin.rb +52 -0
- data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
- data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
- data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
- data/lib/hoodoo/middleware.rb +33 -0
- data/lib/hoodoo/presenters.rb +45 -0
- data/lib/hoodoo/presenters/base.rb +281 -0
- data/lib/hoodoo/presenters/base_dsl.rb +519 -0
- data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
- data/lib/hoodoo/presenters/embedding.rb +232 -0
- data/lib/hoodoo/presenters/types/array.rb +118 -0
- data/lib/hoodoo/presenters/types/boolean.rb +26 -0
- data/lib/hoodoo/presenters/types/date.rb +26 -0
- data/lib/hoodoo/presenters/types/date_time.rb +26 -0
- data/lib/hoodoo/presenters/types/decimal.rb +47 -0
- data/lib/hoodoo/presenters/types/enum.rb +55 -0
- data/lib/hoodoo/presenters/types/field.rb +158 -0
- data/lib/hoodoo/presenters/types/float.rb +26 -0
- data/lib/hoodoo/presenters/types/hash.rb +361 -0
- data/lib/hoodoo/presenters/types/integer.rb +26 -0
- data/lib/hoodoo/presenters/types/object.rb +117 -0
- data/lib/hoodoo/presenters/types/string.rb +53 -0
- data/lib/hoodoo/presenters/types/tags.rb +24 -0
- data/lib/hoodoo/presenters/types/text.rb +26 -0
- data/lib/hoodoo/presenters/types/uuid.rb +54 -0
- data/lib/hoodoo/services.rb +34 -0
- data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
- data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
- data/lib/hoodoo/services/discovery/discovery.rb +186 -0
- data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
- data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
- data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
- data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
- data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
- data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
- data/lib/hoodoo/services/middleware/interaction.rb +127 -0
- data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
- data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
- data/lib/hoodoo/services/services/context.rb +153 -0
- data/lib/hoodoo/services/services/implementation.rb +132 -0
- data/lib/hoodoo/services/services/interface.rb +934 -0
- data/lib/hoodoo/services/services/permissions.rb +250 -0
- data/lib/hoodoo/services/services/request.rb +189 -0
- data/lib/hoodoo/services/services/response.rb +316 -0
- data/lib/hoodoo/services/services/service.rb +141 -0
- data/lib/hoodoo/services/services/session.rb +729 -0
- data/lib/hoodoo/utilities.rb +12 -0
- data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
- data/lib/hoodoo/utilities/utilities.rb +380 -0
- data/lib/hoodoo/utilities/uuid.rb +44 -0
- data/lib/hoodoo/version.rb +17 -0
- data/spec/active/active_record/base_spec.rb +57 -0
- data/spec/active/active_record/creator_spec.rb +88 -0
- data/spec/active/active_record/dated_spec.rb +248 -0
- data/spec/active/active_record/error_mapping_spec.rb +360 -0
- data/spec/active/active_record/finder_spec.rb +744 -0
- data/spec/active/active_record/search_helper_spec.rb +384 -0
- data/spec/active/active_record/secure_spec.rb +435 -0
- data/spec/active/active_record/support_spec.rb +225 -0
- data/spec/active/active_record/translated_spec.rb +19 -0
- data/spec/active/active_record/uuid_spec.rb +72 -0
- data/spec/active/active_record/writer_spec.rb +272 -0
- data/spec/alchemy/alchemy-amq.rb +33 -0
- data/spec/client/augmented_array_spec.rb +15 -0
- data/spec/client/augmented_base_spec.rb +50 -0
- data/spec/client/augmented_hash_spec.rb +15 -0
- data/spec/client/client_spec.rb +955 -0
- data/spec/client/endpoint/endpoint_spec.rb +70 -0
- data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
- data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
- data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
- data/spec/client/headers_spec.rb +172 -0
- data/spec/communicators/fast_spec.rb +9 -0
- data/spec/communicators/pool_spec.rb +339 -0
- data/spec/communicators/slow_spec.rb +15 -0
- data/spec/data/resources/caller_spec.rb +156 -0
- data/spec/data/resources/errors_spec.rb +22 -0
- data/spec/data/resources/log_spec.rb +20 -0
- data/spec/data/resources/session_spec.rb +15 -0
- data/spec/data/types/error_primitive_spec.rb +15 -0
- data/spec/data/types/permissions_defaults_spec.rb +25 -0
- data/spec/data/types/permissions_full_spec.rb +44 -0
- data/spec/data/types/permissions_resources_spec.rb +34 -0
- data/spec/data/types/permissions_spec.rb +37 -0
- data/spec/errors/error_descriptions_spec.rb +98 -0
- data/spec/errors/errors_spec.rb +346 -0
- data/spec/integration/service_actions_spec.rb +112 -0
- data/spec/logger/fast_writer_spec.rb +18 -0
- data/spec/logger/logger_spec.rb +259 -0
- data/spec/logger/slow_writer_spec.rb +144 -0
- data/spec/logger/writers/file_writer_spec.rb +37 -0
- data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
- data/spec/logger/writers/stream_writer_spec.rb +38 -0
- data/spec/presenters/base_dsl_spec.rb +111 -0
- data/spec/presenters/base_spec.rb +871 -0
- data/spec/presenters/common_resource_fields_spec.rb +30 -0
- data/spec/presenters/embedding_spec.rb +87 -0
- data/spec/presenters/types/array_spec.rb +249 -0
- data/spec/presenters/types/boolean_spec.rb +51 -0
- data/spec/presenters/types/date_spec.rb +57 -0
- data/spec/presenters/types/date_time_spec.rb +59 -0
- data/spec/presenters/types/decimal_spec.rb +58 -0
- data/spec/presenters/types/enum_spec.rb +71 -0
- data/spec/presenters/types/field_spec.rb +77 -0
- data/spec/presenters/types/float_spec.rb +50 -0
- data/spec/presenters/types/hash_spec.rb +1069 -0
- data/spec/presenters/types/integer_spec.rb +50 -0
- data/spec/presenters/types/object_spec.rb +177 -0
- data/spec/presenters/types/string_spec.rb +65 -0
- data/spec/presenters/types/tags_spec.rb +56 -0
- data/spec/presenters/types/text_spec.rb +50 -0
- data/spec/presenters/types/uuid_spec.rb +46 -0
- data/spec/presenters/walk_spec.rb +198 -0
- data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
- data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
- data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
- data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
- data/spec/services/discovery/discovery_spec.rb +73 -0
- data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
- data/spec/services/discovery/results/for_http_spec.rb +37 -0
- data/spec/services/discovery/results/for_local_spec.rb +21 -0
- data/spec/services/discovery/results/for_remote_spec.rb +15 -0
- data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
- data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
- data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
- data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
- data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
- data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
- data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
- data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
- data/spec/services/middleware/middleware_cors_spec.rb +93 -0
- data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
- data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
- data/spec/services/middleware/middleware_logging_spec.rb +356 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
- data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
- data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
- data/spec/services/middleware/middleware_public_spec.rb +238 -0
- data/spec/services/middleware/middleware_spec.rb +1569 -0
- data/spec/services/middleware/string_inquirer_spec.rb +30 -0
- data/spec/services/services/application_spec.rb +74 -0
- data/spec/services/services/context_spec.rb +48 -0
- data/spec/services/services/implementation_spec.rb +45 -0
- data/spec/services/services/interface_spec.rb +262 -0
- data/spec/services/services/permissions_spec.rb +249 -0
- data/spec/services/services/request_spec.rb +95 -0
- data/spec/services/services/response_spec.rb +250 -0
- data/spec/services/services/session_spec.rb +432 -0
- data/spec/spec_helper.rb +298 -0
- data/spec/utilities/utilities_spec.rb +537 -0
- data/spec/utilities/uuid_spec.rb +20 -0
- metadata +615 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: errors.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Include error management classes and data definitions.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 26-Jan-2015 (ADH): Split from top-level inclusion file.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
# Dependencies
|
11
|
+
|
12
|
+
require 'hoodoo/utilities'
|
13
|
+
require 'hoodoo/presenters'
|
14
|
+
require 'hoodoo/data'
|
15
|
+
|
16
|
+
# Error management
|
17
|
+
|
18
|
+
require 'hoodoo/errors/error_descriptions'
|
19
|
+
require 'hoodoo/errors/errors'
|
@@ -0,0 +1,229 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: error_descriptions.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Error descriptions - provide a DSL and a container for a
|
6
|
+
# list of known error codes and associated data. Defines a
|
7
|
+
# platform API's +platform+ and +generic+ domain codes by
|
8
|
+
# default. Services can declare additional errors.
|
9
|
+
# ----------------------------------------------------------------------
|
10
|
+
# 22-Sep-2014 (ADH): Created.
|
11
|
+
# 09-Oct-2014 (ADH): Updated for Preview Release 8.
|
12
|
+
# 16-Oct-2014 (TC): Added session error
|
13
|
+
########################################################################
|
14
|
+
|
15
|
+
module Hoodoo
|
16
|
+
|
17
|
+
# A collection of error descriptions. API service implementations create one
|
18
|
+
# of these, which self-declares platform and generic error domain codes. A
|
19
|
+
# simple DSL is available to declare service-specific errors. Since the
|
20
|
+
# middleware is responsible for instantiating an error collection inside a
|
21
|
+
# response object which service implementations use to signal error
|
22
|
+
# conditions, the service's _interface_ class uses the interface description
|
23
|
+
# DSL to call through to here behind the scenes; for example:
|
24
|
+
#
|
25
|
+
# class TransactionImplementation < Hoodoo::Services::Implementation
|
26
|
+
# # ...
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class TransactionInterface < Hoodoo::Services::Interface
|
30
|
+
# interface :Transaction do
|
31
|
+
# endpoint :transactions, TransactionImplementation
|
32
|
+
# errors_for 'transaction' do
|
33
|
+
# error 'duplicate_transaction', status: 409, message: 'Duplicate transaction', :required => [ :client_uid ]
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# The #errors_for method takes the domain of the error as a string - the
|
39
|
+
# part that comes before the "+.+" in error codes. Then a series of +error+
|
40
|
+
# calls describe the individual error codes. See
|
41
|
+
# Hoodoo::ErrorDescriptions::DomainDescriptions#error for details.
|
42
|
+
#
|
43
|
+
# An instance of the Hoodoo::ErrorDescriptions class gets built behind
|
44
|
+
# the scenes as part of the service interface description. This is found by
|
45
|
+
# the middleware and passed to a Hoodoo::Errors constructor. The result
|
46
|
+
# is stored in a Hoodoo::Services::Response instance and passed to handler
|
47
|
+
# methods in the service's Hoodoo::Services::Implementation subclass for each
|
48
|
+
# request. Service implementations access the errors collection through
|
49
|
+
# Hoodoo::Services::Response#errors and can then add errors using the generic
|
50
|
+
# or platform domains, or whatever additional custom domain(s) they defined
|
51
|
+
# in the service interface subclass.
|
52
|
+
#
|
53
|
+
# For direct callers (e.g. the middleware), there is a shorthand form to
|
54
|
+
# invoke the DSL where the constructor is used in the same way as
|
55
|
+
# #errors_for:
|
56
|
+
#
|
57
|
+
# ERROR_DESCRIPTIONS = Hoodoo::ErrorDescriptions.new( 'transaction' ) do
|
58
|
+
# error 'duplicate_transaction', status: 409, message: 'Duplicate transaction', :required => [ :client_uid ]
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# Either way,
|
62
|
+
#
|
63
|
+
# As per the example above, services can share an instance across requests
|
64
|
+
# (and threads) via a class's variable if the descriptions don't change. You
|
65
|
+
# would use the descriptions to inform a Hoodoo::Errors instance of the
|
66
|
+
# available codes and their requirements:
|
67
|
+
#
|
68
|
+
# @errors = Hoodoo::Errors.new( ERROR_DESCRIPTIONS )
|
69
|
+
#
|
70
|
+
class ErrorDescriptions
|
71
|
+
|
72
|
+
# Create an instance, self-declaring +platform+ and +generic+ domain
|
73
|
+
# errors. You can optionally call the constructor with an error domain
|
74
|
+
# and code block, to declare errors all in one go rather than making a
|
75
|
+
# separate call to #errors_for (but both approaches are valid).
|
76
|
+
#
|
77
|
+
# +domain+:: Optional domain, just as used in #errors_for
|
78
|
+
# &block:: Optional block, just as used in #errors_for
|
79
|
+
#
|
80
|
+
def initialize( domain = nil, &block )
|
81
|
+
|
82
|
+
@descriptions = {}
|
83
|
+
|
84
|
+
# Up to date at Preview Release 9, 2014-11-10.
|
85
|
+
|
86
|
+
errors_for 'platform' do
|
87
|
+
error 'not_found', status: 404, message: 'Not found', reference: [ :entity_name ]
|
88
|
+
error 'malformed', status: 422, message: 'Malformed request'
|
89
|
+
error 'invalid_session', status: 401, message: 'Invalid session'
|
90
|
+
error 'forbidden', status: 403, message: 'Action not authorized'
|
91
|
+
error 'method_not_allowed', status: 405, message: 'Method not allowed'
|
92
|
+
error 'timeout', status: 408, message: 'Request timeout'
|
93
|
+
error 'fault', status: 500, message: 'Internal error', reference: [ :exception ]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Up to date at Preview Release 9, 2014-11-10.
|
97
|
+
|
98
|
+
errors_for 'generic' do
|
99
|
+
error 'not_found', status: 404, message: 'Resource not found', reference: [ :ident ]
|
100
|
+
error 'malformed', status: 422, message: 'Malformed payload'
|
101
|
+
error 'required_field_missing', status: 422, message: 'Required field missing', reference: [ :field_name ]
|
102
|
+
error 'invalid_string', status: 422, message: 'Invalid string format', reference: [ :field_name ]
|
103
|
+
error 'invalid_integer', status: 422, message: 'Invalid integer format', reference: [ :field_name ]
|
104
|
+
error 'invalid_float', status: 422, message: 'Invalid float format', reference: [ :field_name ]
|
105
|
+
error 'invalid_decimal', status: 422, message: 'Invalid decimal format', reference: [ :field_name ]
|
106
|
+
error 'invalid_boolean', status: 422, message: 'Invalid boolean format', reference: [ :field_name ]
|
107
|
+
error 'invalid_enum', status: 422, message: 'Invalid enumeration', reference: [ :field_name ]
|
108
|
+
error 'invalid_date', status: 422, message: 'Invalid date specifier', reference: [ :field_name ]
|
109
|
+
error 'invalid_time', status: 422, message: 'Invalid time specifier', reference: [ :field_name ]
|
110
|
+
error 'invalid_datetime', status: 422, message: 'Invalid date-time specifier', reference: [ :field_name ]
|
111
|
+
error 'invalid_uuid', status: 422, message: 'Invalid UUID', reference: [ :field_name ]
|
112
|
+
error 'invalid_array', status: 422, message: 'Invalid array', reference: [ :field_name ]
|
113
|
+
error 'invalid_object', status: 422, message: 'Invalid object', reference: [ :field_name ]
|
114
|
+
error 'invalid_hash', status: 422, message: 'Invalid hash', reference: [ :field_name ]
|
115
|
+
error 'invalid_duplication', status: 422, message: 'Duplicates not allowed', reference: [ :field_name ]
|
116
|
+
error 'invalid_state', status: 422, message: 'State transition not allowed', reference: [ :destination_state ]
|
117
|
+
error 'invalid_parameters', status: 422, message: 'Invalid parameters'
|
118
|
+
error 'mutually_exclusive', status: 422, message: 'Mutually exclusive parameters', reference: [ :field_names ]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Add caller's custom errors for the shorthand form, if provided.
|
122
|
+
|
123
|
+
if ( domain != nil && domain != '' && block_given?() )
|
124
|
+
errors_for( domain, &block )
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Implement the collection's part of the small DSL used for error
|
129
|
+
# declaration. Call here, passing the error domain (usually the singular
|
130
|
+
# service name or resource name, e.g. "+transaction+" and defined by the
|
131
|
+
# part of the platform API the service is implementing) and a block. The
|
132
|
+
# block makes one or more "+error+" calls, which actually end up calling
|
133
|
+
# Hoodoo::ErrorDescriptions::DomainDescriptions#error behind the scenes.
|
134
|
+
#
|
135
|
+
# See the implementation of #initialize for a worked example.
|
136
|
+
#
|
137
|
+
# +domain+:: Error domain, e.g. +platform+, +transaction+
|
138
|
+
# &block:: Block which makes one or more calls to "+error+"
|
139
|
+
#
|
140
|
+
def errors_for( domain, &block )
|
141
|
+
domain_descriptions = Hoodoo::ErrorDescriptions::DomainDescriptions.new( domain )
|
142
|
+
domain_descriptions.instance_eval( &block )
|
143
|
+
|
144
|
+
@descriptions.merge!( domain_descriptions.descriptions )
|
145
|
+
end
|
146
|
+
|
147
|
+
# Is the given error code recognised? Returns +true+ if so, else +false+.
|
148
|
+
#
|
149
|
+
# +code+:: Error code in full, e.g. +generic.invalid_state'.
|
150
|
+
#
|
151
|
+
def recognised?( code )
|
152
|
+
@descriptions.has_key?( code )
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return the options description hash, as passed to +error+ calls in the
|
156
|
+
# block given to #errors_for, for the given code.
|
157
|
+
#
|
158
|
+
# +code+:: Error code in full, e.g. +generic.invalid_state'.
|
159
|
+
#
|
160
|
+
def describe( code )
|
161
|
+
@descriptions[ code ]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Contain a description of errors for a particular domain, where the domain
|
165
|
+
# is a grouping string such as "platform", "generic", or a short service
|
166
|
+
# name. Usually driven via Hoodoo::ErrorDescriptions, not directly.
|
167
|
+
#
|
168
|
+
class DomainDescriptions
|
169
|
+
|
170
|
+
# Domain name for this description instance (string).
|
171
|
+
#
|
172
|
+
attr_reader( :domain )
|
173
|
+
|
174
|
+
# Hash of all descriptions, keyed by full error code, with options
|
175
|
+
# hash data as values (see #error for details).
|
176
|
+
#
|
177
|
+
attr_reader( :descriptions )
|
178
|
+
|
179
|
+
# Initialize a new instance for the given domain.
|
180
|
+
#
|
181
|
+
# +domain+:: The domain string - for most service-based callers, usually
|
182
|
+
# a short service name like +members+ or +transactions+.
|
183
|
+
#
|
184
|
+
def initialize( domain )
|
185
|
+
@domain = domain
|
186
|
+
@descriptions = {}
|
187
|
+
end
|
188
|
+
|
189
|
+
# Describe an error.
|
190
|
+
#
|
191
|
+
# +name+:: The error name - the bit after the "+.+" in the code, e.g.
|
192
|
+
# +invalid_parameters+.
|
193
|
+
#
|
194
|
+
# +options+:: Options hash. See below.
|
195
|
+
#
|
196
|
+
# The options hash contains symbol keys named as follows, with values as
|
197
|
+
# described:
|
198
|
+
#
|
199
|
+
# +:status+:: The integer or string HTTP status code to be associated
|
200
|
+
# with this error
|
201
|
+
#
|
202
|
+
# +message+:: The +en-nz+ language human-readable error message used
|
203
|
+
# for developers.
|
204
|
+
#
|
205
|
+
# +reference+:: Optional array of required named references. When errors
|
206
|
+
# are added (via Hoodoo::Errors#add_error) to a
|
207
|
+
# collection, required reference(s) from this array must
|
208
|
+
# be provided by the error-adding caller else an exception
|
209
|
+
# will be raised. This ensures correct, fully qualified
|
210
|
+
# error data is logged and sent to clients.
|
211
|
+
#
|
212
|
+
def error( name, options )
|
213
|
+
options = Hoodoo::Utilities.stringify( options )
|
214
|
+
required_keys = [ 'status', 'message' ]
|
215
|
+
|
216
|
+
reference = options[ 'reference' ]
|
217
|
+
options[ 'reference' ] = reference.map( &:to_s ) if reference.is_a?( ::Array )
|
218
|
+
|
219
|
+
required_keys.each do | required_key |
|
220
|
+
unless options.has_key?( required_key )
|
221
|
+
raise "Error description options hash missing required key '#{ required_key }'"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
@descriptions[ "#{ @domain }.#{ name }" ] = options
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: errors.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: A collection of error messages, starting empty, with one
|
6
|
+
# or more messages added to it as errors are encountered by
|
7
|
+
# some processing task. Errors are added according to codes
|
8
|
+
# described by Hoodoo::ErrorDescriptions instances.
|
9
|
+
# ----------------------------------------------------------------------
|
10
|
+
# 22-Sep-2014 (ADH): Created.
|
11
|
+
########################################################################
|
12
|
+
|
13
|
+
module Hoodoo
|
14
|
+
|
15
|
+
# During request processing, API service implementations create an
|
16
|
+
# Hoodoo::Errors instance and add error(s) to the collection as they arise
|
17
|
+
# using #add_error. That same instance can then be returned for the on-error
|
18
|
+
# handling path of whatever wider service framework is in use by the service
|
19
|
+
# code in question. Services should use new instances for each request.
|
20
|
+
#
|
21
|
+
class Errors
|
22
|
+
|
23
|
+
# Custom exception thrown when an unknown error code is added to a
|
24
|
+
# collection.
|
25
|
+
#
|
26
|
+
class UnknownCode < RuntimeError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Custom exception thrown when an error is added to a collection without
|
30
|
+
# including required reference data
|
31
|
+
#
|
32
|
+
class MissingReferenceData < RuntimeError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Default Hoodoo::ErrorDescriptions instance, used if the instantiator
|
36
|
+
# provides no alternative.
|
37
|
+
#
|
38
|
+
DEFAULT_ERROR_DESCRIPTIONS = Hoodoo::ErrorDescriptions.new()
|
39
|
+
|
40
|
+
# Errors are manifestations of the Errors resource. They acquire a UUID
|
41
|
+
# when instantiated, even if the instance is never used or persisted.
|
42
|
+
#
|
43
|
+
attr_reader( :uuid )
|
44
|
+
|
45
|
+
# Array of error data - hashes with +code+, +message+ and +reference+
|
46
|
+
# fields giving the error codes, human-readable messages and
|
47
|
+
# machine-readable reference data, where appropriate.
|
48
|
+
#
|
49
|
+
attr_reader( :errors )
|
50
|
+
|
51
|
+
# HTTP status code associated with the first error in the #errors array,
|
52
|
+
# _as an Integer_.
|
53
|
+
#
|
54
|
+
attr_reader( :http_status_code )
|
55
|
+
|
56
|
+
# The Hoodoo::ErrorDescriptions instance associated with this error
|
57
|
+
# collection. Only error codes that the instance's
|
58
|
+
# Hoodoo::ErrorDescriptions#recognised? method says are recognised
|
59
|
+
# can be added to the error collection, else
|
60
|
+
# Hoodoo::Errors::UnknownCode will be raised.
|
61
|
+
#
|
62
|
+
attr_reader( :descriptions )
|
63
|
+
|
64
|
+
# Create an instance.
|
65
|
+
#
|
66
|
+
# +descriptions+:: (Optional) Hoodoo::ErrorDescriptions instance with
|
67
|
+
# service-domain-specific error descriptions added, or
|
68
|
+
# omit for a default instance describing +platform+ and
|
69
|
+
# +generic+ error domains only.
|
70
|
+
#
|
71
|
+
def initialize( descriptions = DEFAULT_ERROR_DESCRIPTIONS )
|
72
|
+
@uuid = Hoodoo::UUID.generate()
|
73
|
+
@descriptions = descriptions
|
74
|
+
@errors = []
|
75
|
+
@http_status_code = 200
|
76
|
+
@created_at = nil # See #persist!
|
77
|
+
end
|
78
|
+
|
79
|
+
# Add an error instance to this collection.
|
80
|
+
#
|
81
|
+
# +code+:: Error code in full, e.g. +generic.invalid_state'.
|
82
|
+
#
|
83
|
+
# +options+:: An options Hash, optional.
|
84
|
+
#
|
85
|
+
# The options hash contains symbol keys named as follows, with values as
|
86
|
+
# described:
|
87
|
+
#
|
88
|
+
# +reference+:: Reference data Hash, optionality depending upon the error
|
89
|
+
# code and the reference data its error description mandates.
|
90
|
+
# Provide key/value pairs where (symbol) keys are names from
|
91
|
+
# the array of description requirements and values are
|
92
|
+
# strings. All values are concatenated into a single string,
|
93
|
+
# comma-separated. Commas within values are escaped with a
|
94
|
+
# backslash; backslash is itself escaped with a backslash.
|
95
|
+
#
|
96
|
+
# You must provide that data at a minimum, but can provide
|
97
|
+
# additional keys too if you so wish. Required keys are
|
98
|
+
# always included first, in order of appearance in the
|
99
|
+
# requirements array of the error declaration, followed by
|
100
|
+
# any extra values in undefined order.
|
101
|
+
#
|
102
|
+
# See also Hoodoo::ErrorDescriptions::DomainDescriptions#error
|
103
|
+
#
|
104
|
+
# +message+:: Optional human-readable for-developer message, +en-nz+
|
105
|
+
# locale. Default messages are provided for all errors, but
|
106
|
+
# if you think you can provide something more informative,
|
107
|
+
# you can do so through this parameter.
|
108
|
+
#
|
109
|
+
# Example:
|
110
|
+
#
|
111
|
+
# errors.add_error(
|
112
|
+
# 'platform.not_found',
|
113
|
+
# :message => 'Optional custom message',
|
114
|
+
# :reference => { :entity_name => 'mandatory reference data' }
|
115
|
+
# )
|
116
|
+
#
|
117
|
+
# In the above example, the mandatory reference data +entity_name+ comes
|
118
|
+
# from the description for the 'platform.not_found' message - see the
|
119
|
+
# Hoodoo::ErrorDescriptions#initialize _implementation_ and Platform API.
|
120
|
+
#
|
121
|
+
def add_error( code, options = nil )
|
122
|
+
|
123
|
+
options = Hoodoo::Utilities.stringify( options || {} )
|
124
|
+
reference = options[ 'reference' ] || {}
|
125
|
+
message = options[ 'message' ]
|
126
|
+
|
127
|
+
# Make sure nobody uses an undeclared error code.
|
128
|
+
|
129
|
+
raise UnknownCode, "In \#add_error: Unknown error code '#{code}'" unless @descriptions.recognised?( code )
|
130
|
+
|
131
|
+
# If the error description specifies a list of required reference keys,
|
132
|
+
# make sure all are present and complain if not.
|
133
|
+
|
134
|
+
description = @descriptions.describe( code )
|
135
|
+
|
136
|
+
required_keys = description[ 'reference' ] || []
|
137
|
+
actual_keys = reference.keys
|
138
|
+
missing_keys = required_keys - actual_keys
|
139
|
+
|
140
|
+
unless missing_keys.empty?
|
141
|
+
raise MissingReferenceData, "In \#add_error: Reference hash missing required keys: '#{ missing_keys.join( ', ' ) }'"
|
142
|
+
end
|
143
|
+
|
144
|
+
# All good!
|
145
|
+
|
146
|
+
@http_status_code = ( description[ 'status' ] || 200 ).to_i if @errors.empty? # Use first in collection for overall HTTP status code
|
147
|
+
|
148
|
+
error = {
|
149
|
+
'code' => code,
|
150
|
+
'message' => message || description[ 'message' ] || code
|
151
|
+
}
|
152
|
+
|
153
|
+
ordered_keys = required_keys + ( actual_keys - required_keys )
|
154
|
+
ordered_values = ordered_keys.map { | key | escape_commas( reference[ key ].to_s ) }
|
155
|
+
|
156
|
+
# See #unjoin_and_unescape_commas to undo the join below.
|
157
|
+
|
158
|
+
error[ 'reference' ] = ordered_values.join( ',' ) unless ordered_values.empty?
|
159
|
+
|
160
|
+
@errors << error
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add a precompiled error to the error collection. Pass error code,
|
164
|
+
# error message and reference data directly.
|
165
|
+
#
|
166
|
+
# In most cases you should be calling #add_error instead, *NOT* here.
|
167
|
+
#
|
168
|
+
# *No* *validation* is performed. You should only really call here if
|
169
|
+
# storing an error / errors from another, trusted source with assumed
|
170
|
+
# validity (e.g. another service called remotely with errors in the JSON
|
171
|
+
# response). It's possible to store invalid error data using this call,
|
172
|
+
# which means counter-to-documentation results could be returned to API
|
173
|
+
# clients. That is Very Bad.
|
174
|
+
#
|
175
|
+
# Pass optionally the HTTP status code to use if this happens to be the
|
176
|
+
# first stored error. If this is omitted, 500 is kept as the default.
|
177
|
+
#
|
178
|
+
def add_precompiled_error( code, message, reference, http_status = 500 )
|
179
|
+
@http_status_code = http_status.to_i if @errors.empty?
|
180
|
+
|
181
|
+
error = {
|
182
|
+
'code' => code,
|
183
|
+
'message' => message
|
184
|
+
}
|
185
|
+
|
186
|
+
error[ 'reference' ] = reference unless reference.nil? || reference.empty?
|
187
|
+
|
188
|
+
@errors << error
|
189
|
+
end
|
190
|
+
|
191
|
+
# Merge the contents of a source error object with this one, adding its
|
192
|
+
# errors to this collection. No checks are made for duplicates (in part
|
193
|
+
# because, depending on error code and source/target contexts, a
|
194
|
+
# duplicate may be a valid thing to have).
|
195
|
+
#
|
196
|
+
# +source+:: Hoodoo::Errors instance to merge into the error collection
|
197
|
+
# of 'this' target object.
|
198
|
+
#
|
199
|
+
# Returns +true+ if errors were merged, else +false+ (the source
|
200
|
+
# collection was empty).
|
201
|
+
#
|
202
|
+
def merge!( source )
|
203
|
+
source_errors = source.errors
|
204
|
+
|
205
|
+
source_errors.each do | hash |
|
206
|
+
add_precompiled_error(
|
207
|
+
hash[ 'code' ],
|
208
|
+
hash[ 'message' ],
|
209
|
+
hash[ 'reference' ],
|
210
|
+
source.http_status_code
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
return ! source_errors.empty?
|
215
|
+
end
|
216
|
+
|
217
|
+
# Does this instance have any errors added? Returns +true+ if so,
|
218
|
+
# else +false+.
|
219
|
+
#
|
220
|
+
def has_errors?
|
221
|
+
! @errors.empty?
|
222
|
+
end
|
223
|
+
|
224
|
+
# Clear (delete) all previously added errors (if any). After calling here,
|
225
|
+
# #has_errors? would always return +false+.
|
226
|
+
#
|
227
|
+
def clear_errors
|
228
|
+
@errors = []
|
229
|
+
@http_status_code = 200
|
230
|
+
end
|
231
|
+
|
232
|
+
# Return a Hash rendered through the Hoodoo::Data::Resources::Errors
|
233
|
+
# collection representing the formalised resource.
|
234
|
+
#
|
235
|
+
# +interaction_id+:: Mandatory Interaction ID (UUID) to associate with
|
236
|
+
# the collection.
|
237
|
+
#
|
238
|
+
def render( interaction_id )
|
239
|
+
unless Hoodoo::UUID.valid?( interaction_id )
|
240
|
+
raise "Hoodoo::Errors\#render must be given a valid Interaction ID (got '#{ interaction_id.inspect }')"
|
241
|
+
end
|
242
|
+
|
243
|
+
@created_at ||= Time.now
|
244
|
+
|
245
|
+
Hoodoo::Data::Resources::Errors.render(
|
246
|
+
{
|
247
|
+
'interaction_id' => interaction_id,
|
248
|
+
'errors' => @errors
|
249
|
+
},
|
250
|
+
@uuid,
|
251
|
+
@created_at
|
252
|
+
)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Make life easier for debugging on the console by having the object
|
256
|
+
# represent itself more concisely.
|
257
|
+
#
|
258
|
+
def inspect
|
259
|
+
@errors.to_s
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
# DEVELOPER: In the function comment below, RDoc escaping has to be done
|
264
|
+
# for RDocs to make sense. Read every "\\" as a single "\" (or read the
|
265
|
+
# generated docs instead of reading the source code comment below).
|
266
|
+
|
267
|
+
|
268
|
+
# When reference data is specified for errors, the reference values are
|
269
|
+
# concatenated together into a comma-separated string. Since reference
|
270
|
+
# values can themselves contain commas, comma is escaped with "\\," and
|
271
|
+
# "\\" escaped with "\\\\".
|
272
|
+
#
|
273
|
+
# Call here with such a string; return an array of 'unescaped' values.
|
274
|
+
#
|
275
|
+
# +str+:: Value-escaped ("\\\\" / "\\,") comma-separated string. Unescaped
|
276
|
+
# commas separate individual values.
|
277
|
+
#
|
278
|
+
def unjoin_and_unescape_commas( str )
|
279
|
+
|
280
|
+
# In Ruby regular expressions, '(?<!pat)' is a negative lookbehind
|
281
|
+
# assertion, making sure that the preceding characters do not match
|
282
|
+
# 'pat'. To split the string joined on ',' to an array but not splitting
|
283
|
+
# any escaped '\,', then, we can use this rather opaque split regexp:
|
284
|
+
#
|
285
|
+
# error[ 'reference' ].split( /(?<!\\),/ )
|
286
|
+
#
|
287
|
+
# I.e. split on ',', provided it is not preceded by a '\' (escaped in the
|
288
|
+
# regexp to '\\').
|
289
|
+
|
290
|
+
ary = str.split( /(?<!\\),/ )
|
291
|
+
ary.map { | entry | unescape_commas( entry ) }
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
295
|
+
|
296
|
+
|
297
|
+
# DEVELOPER: In the function comment below, RDoc escaping has to be done
|
298
|
+
# for RDocs to make sense. Read every "\\" as a single "\" (or read the
|
299
|
+
# generated docs instead of reading the source code comment below).
|
300
|
+
|
301
|
+
|
302
|
+
# Given a string, escape "," to "\\," and "\\" to "\\\\", returning the result.
|
303
|
+
#
|
304
|
+
# +str+:: String to escape.
|
305
|
+
#
|
306
|
+
def escape_commas( str )
|
307
|
+
# "\" in replacement strings gets evaluated twice, once for string
|
308
|
+
# literals (leaving '\\\\') then again for regexp group references like
|
309
|
+
# "\1" work (thus leaving '\\').
|
310
|
+
#
|
311
|
+
str.gsub( "\\", "\\\\\\\\" ).gsub( ",", "\\," )
|
312
|
+
end
|
313
|
+
|
314
|
+
# Given a string escaped via #escape_commas, unescape it.
|
315
|
+
#
|
316
|
+
# +str+:: String to escape.
|
317
|
+
#
|
318
|
+
def unescape_commas( str )
|
319
|
+
str.gsub( "\\,", "," ).gsub( "\\\\", "\\\\" )
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|