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,194 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: auto_session.rb
|
3
|
+
# (C):: Loyalty New Zealand 2015
|
4
|
+
#
|
5
|
+
# Purpose:: Resource endpoint definition.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 12-Mar-2015 (ADH): Created.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
module Hoodoo
|
11
|
+
class Client # Just used as a namespace here
|
12
|
+
class Endpoint # Just used as a namespace here
|
13
|
+
|
14
|
+
# This endpoint wraps something which does _actual_ communication but
|
15
|
+
# requires a session; it maintains a valid session for that wrapped
|
16
|
+
# endpoint automatically. It implements the following model:
|
17
|
+
#
|
18
|
+
# - It requires a Caller ID and Caller Authentication Secret to be
|
19
|
+
# instantiated (via the +options+ parameter).
|
20
|
+
#
|
21
|
+
# - If there is no session ID available, it creates one using the above
|
22
|
+
# details. Otherwise, it tries to use the given session.
|
23
|
+
#
|
24
|
+
# - If a particular request leads to an 'invalid session' response, the
|
25
|
+
# request is marked for retry. A new session is obtained first, the
|
26
|
+
# retry happens and if it fails this time, the failure is returned to
|
27
|
+
# the caller.
|
28
|
+
#
|
29
|
+
# Since it wraps another endpoint and requires Caller information to be
|
30
|
+
# able to build sessions, instantiation requirements are rather unusual
|
31
|
+
# - see #configure_with for details.
|
32
|
+
#
|
33
|
+
class AutoSession < Hoodoo::Client::Endpoint
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
# See Hoodoo::Client::Endpoint#configure_with.
|
38
|
+
#
|
39
|
+
# Configuration option keys which _must_ be supplied are:
|
40
|
+
#
|
41
|
+
# +caller_id+:: The UUID of the Caller instance to be used
|
42
|
+
# for session creation.
|
43
|
+
#
|
44
|
+
# +caller_secret+:: The authentication secret of the Caller
|
45
|
+
# instance to be used for session creation.
|
46
|
+
#
|
47
|
+
# +session_endpoint:: A Hooodo::Client::Endpoint subclass which
|
48
|
+
# can be used for talking to the Session
|
49
|
+
# endpoint (for obvious reasons!).
|
50
|
+
#
|
51
|
+
# +discovery_result+:: A Hoodoo::Services::Discovery::ForRemote
|
52
|
+
# instance describing the required, remotely
|
53
|
+
# available resource endpoint.
|
54
|
+
#
|
55
|
+
# The pattern for creating and using this instance is:
|
56
|
+
#
|
57
|
+
# * Discover the location of the remote resource.
|
58
|
+
#
|
59
|
+
# * Use the discovery result to build an appropriate Endpoint
|
60
|
+
# subclass instance, e.g. Hoodoo::Client::Endpoint::HTTP.
|
61
|
+
#
|
62
|
+
# * Create a Hoodoo::Services::Discovery::ForRemote instance which
|
63
|
+
# describes the above endpoint via the +wrapped_endpoint+ option.
|
64
|
+
#
|
65
|
+
# * Build an instance of this auto-session Endpoint subclass,
|
66
|
+
# giving it the above object as the +discovery_result+.
|
67
|
+
#
|
68
|
+
# * Use this endpoint in the normal fashion. All the special
|
69
|
+
# mechanics of session management are handled here.
|
70
|
+
#
|
71
|
+
def configure_with( resource, version, options )
|
72
|
+
@caller_id = options[ :caller_id ]
|
73
|
+
@caller_secret = options[ :caller_secret ]
|
74
|
+
@session_endpoint = options[ :session_endpoint ]
|
75
|
+
@wrapped_endpoint = @discovery_result.wrapped_endpoint
|
76
|
+
end
|
77
|
+
|
78
|
+
public
|
79
|
+
|
80
|
+
# See Hoodoo::Client::Endpoint#list.
|
81
|
+
#
|
82
|
+
def list( query_hash = nil )
|
83
|
+
return auto_retry( :list, query_hash )
|
84
|
+
end
|
85
|
+
|
86
|
+
# See Hoodoo::Client::Endpoint#show.
|
87
|
+
#
|
88
|
+
def show( ident, query_hash = nil )
|
89
|
+
return auto_retry( :show, ident, query_hash )
|
90
|
+
end
|
91
|
+
|
92
|
+
# See Hoodoo::Client::Endpoint#create.
|
93
|
+
#
|
94
|
+
def create( body_hash, query_hash = nil )
|
95
|
+
return auto_retry( :create, body_hash, query_hash )
|
96
|
+
end
|
97
|
+
|
98
|
+
# See Hoodoo::Client::Endpoint#update.
|
99
|
+
#
|
100
|
+
def update( ident, body_hash, query_hash = nil )
|
101
|
+
return auto_retry( :update, ident, body_hash, query_hash )
|
102
|
+
end
|
103
|
+
|
104
|
+
# See Hoodoo::Client::Endpoint#delete.
|
105
|
+
#
|
106
|
+
def delete( ident, query_hash = nil )
|
107
|
+
return auto_retry( :delete, ident, query_hash )
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Try to perform an action through the wrapped endpoint, acquiring a
|
113
|
+
# session first if need be or if necessary reacquiring a session and
|
114
|
+
# retrying the request.
|
115
|
+
#
|
116
|
+
# +action+:: The name of the method to call in the wrapped endpoint
|
117
|
+
# - see Hoodoo::Services::Middleware::ALLOWED_ACTIONS.
|
118
|
+
#
|
119
|
+
# *args:: Any other arguments to pass to +action+.
|
120
|
+
#
|
121
|
+
def auto_retry( action, *args )
|
122
|
+
|
123
|
+
copy_updated_options_to( @wrapped_endpoint )
|
124
|
+
|
125
|
+
# We use the session endpoint as a session ID cache, in essence,
|
126
|
+
# storing the acquired ID there and passing it into the wrapped
|
127
|
+
# endpoint for the 'real' calls.
|
128
|
+
|
129
|
+
if @session_endpoint.session_id.nil?
|
130
|
+
session_creation_result = acquire_session_for( action )
|
131
|
+
return session_creation_result unless session_creation_result.nil?
|
132
|
+
else
|
133
|
+
@wrapped_endpoint.session_id = @session_endpoint.session_id
|
134
|
+
end
|
135
|
+
|
136
|
+
result = @wrapped_endpoint.send( action, *args )
|
137
|
+
|
138
|
+
if result.platform_errors.has_errors? &&
|
139
|
+
result.platform_errors.errors.size == 1 &&
|
140
|
+
result.platform_errors.errors[ 0 ][ 'code' ] == 'platform.invalid_session'
|
141
|
+
|
142
|
+
session_creation_result = acquire_session_for( action )
|
143
|
+
return session_creation_result unless session_creation_result.nil?
|
144
|
+
return @wrapped_endpoint.send( action, *args )
|
145
|
+
else
|
146
|
+
return result
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Acquire a sessino using the configured session endpoint. If this
|
151
|
+
# fails, the failure result is returned. If it seems to succeed but
|
152
|
+
# a session ID cannot be found, an internal 'generic.malformed'
|
153
|
+
# result is generated and returned.
|
154
|
+
#
|
155
|
+
# The returned data uses an appropriate response class for the
|
156
|
+
# action at hand - an augmented array for lists, else an augmented
|
157
|
+
# hash. It can be returned directly up to the calling layer.
|
158
|
+
#
|
159
|
+
# Returns +nil+ if all goes well; #session_id will be updated.
|
160
|
+
#
|
161
|
+
# +action+:: As given to #auto_retry.
|
162
|
+
#
|
163
|
+
def acquire_session_for( action )
|
164
|
+
session_creation_result = @session_endpoint.create(
|
165
|
+
'caller_id' => @caller_id,
|
166
|
+
'authentication_secret' => @caller_secret
|
167
|
+
)
|
168
|
+
|
169
|
+
if session_creation_result.platform_errors.has_errors?
|
170
|
+
data = response_class_for( action ).new
|
171
|
+
data.platform_errors.merge!( session_creation_result.platform_errors )
|
172
|
+
return data
|
173
|
+
end
|
174
|
+
|
175
|
+
@session_endpoint.session_id = session_creation_result[ 'id' ]
|
176
|
+
|
177
|
+
if @session_endpoint.session_id.nil? || @session_endpoint.session_id.empty?
|
178
|
+
data = response_class_for( action ).new
|
179
|
+
data.platform_errors.add_error(
|
180
|
+
'generic.malformed',
|
181
|
+
'message' => 'Received bad session description from Session endpoint despite "200" response code'
|
182
|
+
)
|
183
|
+
|
184
|
+
return data
|
185
|
+
else
|
186
|
+
@wrapped_endpoint.session_id = @session_endpoint.session_id
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: http.rb
|
3
|
+
# (C):: Loyalty New Zealand 2015
|
4
|
+
#
|
5
|
+
# Purpose:: Resource endpoint definition.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 05-Mar-2015 (ADH): Created.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
require 'net/http'
|
11
|
+
require 'net/https'
|
12
|
+
|
13
|
+
module Hoodoo
|
14
|
+
class Client # Just used as a namespace here
|
15
|
+
class Endpoint # Just used as a namespace here
|
16
|
+
|
17
|
+
# Talk to a resource that is contacted over HTTP or HTTPS.
|
18
|
+
#
|
19
|
+
# Configured with a Hoodoo::Services::Discovery::ForHTTP discovery
|
20
|
+
# result instance.
|
21
|
+
#
|
22
|
+
class HTTP < Hoodoo::Client::Endpoint::HTTPBased
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# See Hoodoo::Client::Endpoint#configure_with.
|
27
|
+
#
|
28
|
+
# Requires a Hoodoo::Services::Discovery::ForHTTP instance in the
|
29
|
+
# +discovery_result+ field of the +options+ Hash.
|
30
|
+
#
|
31
|
+
def configure_with( resource, version, options )
|
32
|
+
unless @discovery_result.is_a?( Hoodoo::Services::Discovery::ForHTTP )
|
33
|
+
raise "Hoodoo::Client::Endpoint::HTTP must be configured with a Hoodoo::Services::Discovery::ForHTTP instance - got '#{ @discovery_result.class.name }'"
|
34
|
+
end
|
35
|
+
|
36
|
+
@description = Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfRequest.new
|
37
|
+
@description.discovery_result = @discovery_result
|
38
|
+
@description.endpoint_uri = @discovery_result.endpoint_uri
|
39
|
+
@description.proxy_uri = @discovery_result.proxy_uri
|
40
|
+
@description.ca_file = @discovery_result.ca_file
|
41
|
+
@description.http_timeout = @discovery_result.http_timeout
|
42
|
+
end
|
43
|
+
|
44
|
+
public
|
45
|
+
|
46
|
+
# See Hoodoo::Client::Endpoint#list.
|
47
|
+
#
|
48
|
+
def list( query_hash = nil )
|
49
|
+
d = @description.dup # This does NOT dup the objects to which @description points
|
50
|
+
d.action = :list
|
51
|
+
d.query_hash = query_hash
|
52
|
+
|
53
|
+
return do_http( d )
|
54
|
+
end
|
55
|
+
|
56
|
+
# See Hoodoo::Client::Endpoint#show.
|
57
|
+
#
|
58
|
+
def show( ident, query_hash = nil )
|
59
|
+
d = @description.dup
|
60
|
+
d.action = :show
|
61
|
+
d.ident = ident
|
62
|
+
d.query_hash = query_hash
|
63
|
+
|
64
|
+
return do_http( d )
|
65
|
+
end
|
66
|
+
|
67
|
+
# See Hoodoo::Client::Endpoint#create.
|
68
|
+
#
|
69
|
+
def create( body_hash, query_hash = nil )
|
70
|
+
d = @description.dup
|
71
|
+
d.action = :create
|
72
|
+
d.body_hash = body_hash
|
73
|
+
d.query_hash = query_hash
|
74
|
+
|
75
|
+
return do_http( d )
|
76
|
+
end
|
77
|
+
|
78
|
+
# See Hoodoo::Client::Endpoint#update.
|
79
|
+
#
|
80
|
+
def update( ident, body_hash, query_hash = nil )
|
81
|
+
d = @description.dup
|
82
|
+
d.action = :update
|
83
|
+
d.ident = ident
|
84
|
+
d.body_hash = body_hash
|
85
|
+
d.query_hash = query_hash
|
86
|
+
|
87
|
+
return do_http( d )
|
88
|
+
end
|
89
|
+
|
90
|
+
# See Hoodoo::Client::Endpoint#delete.
|
91
|
+
#
|
92
|
+
def delete( ident, query_hash = nil )
|
93
|
+
d = @description.dup
|
94
|
+
d.action = :delete
|
95
|
+
d.ident = ident
|
96
|
+
d.query_hash = query_hash
|
97
|
+
|
98
|
+
return do_http( d )
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Make real HTTP(S) request to a target resource and return the
|
104
|
+
# result as a Hoodoo::Client::AugmentedArray (for 'list' calls) or
|
105
|
+
# Hoodoo::Client::AugumentedHash (for all other calls) instance.
|
106
|
+
#
|
107
|
+
# +description_of_request+:: A Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfRequest
|
108
|
+
# instance with all the request details
|
109
|
+
# set inside. The +discovery_data+ field
|
110
|
+
# must refer to a
|
111
|
+
# Hoodoo::Services::Discovery::ForHTTP
|
112
|
+
# instance (not re-checked internally).
|
113
|
+
#
|
114
|
+
def do_http( description_of_request )
|
115
|
+
|
116
|
+
data = get_data_for_request( description_of_request )
|
117
|
+
|
118
|
+
action = description_of_request.action
|
119
|
+
proxy = description_of_request.proxy_uri
|
120
|
+
ca_file = description_of_request.ca_file
|
121
|
+
http_timeout = description_of_request.http_timeout
|
122
|
+
|
123
|
+
proxy_host = :ENV
|
124
|
+
proxy_port = proxy_user = proxy_pass = nil
|
125
|
+
|
126
|
+
unless proxy.nil?
|
127
|
+
proxy_host = proxy.host
|
128
|
+
proxy_port = proxy.port
|
129
|
+
proxy_user = proxy.user
|
130
|
+
proxy_pass = proxy.password
|
131
|
+
end
|
132
|
+
|
133
|
+
http = Net::HTTP.new(
|
134
|
+
data.full_uri.host,
|
135
|
+
data.full_uri.port,
|
136
|
+
proxy_host,
|
137
|
+
proxy_port,
|
138
|
+
proxy_user,
|
139
|
+
proxy_pass
|
140
|
+
)
|
141
|
+
|
142
|
+
if data.full_uri.scheme == 'https'
|
143
|
+
http.use_ssl = true
|
144
|
+
|
145
|
+
# The verify_mode is *important* - VERIFY_PEER ensures that we always validate
|
146
|
+
# the connection, *and* that the presented SSL Certificate by the endpoint is
|
147
|
+
# verifiable through our CA certificate trust store.
|
148
|
+
#
|
149
|
+
# To use a self-signed cert, you may configure the ca_file to a CA that
|
150
|
+
# includes the self-signed cert, but the verify_mode setting should remain.
|
151
|
+
#
|
152
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
153
|
+
|
154
|
+
if ca_file
|
155
|
+
http.ca_file = ca_file
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
http.read_timeout = http_timeout unless http_timeout.nil?
|
160
|
+
|
161
|
+
request_class = {
|
162
|
+
:create => Net::HTTP::Post,
|
163
|
+
:update => Net::HTTP::Patch,
|
164
|
+
:delete => Net::HTTP::Delete
|
165
|
+
}[ action ] || Net::HTTP::Get
|
166
|
+
|
167
|
+
request = request_class.new( data.full_uri.request_uri() )
|
168
|
+
request.body = data.body_string unless data.body_string.empty?
|
169
|
+
|
170
|
+
request.initialize_http_header( data.header_hash )
|
171
|
+
|
172
|
+
description_of_response = DescriptionOfResponse.new
|
173
|
+
description_of_response.action = action
|
174
|
+
description_of_response.http_headers = {}
|
175
|
+
|
176
|
+
begin
|
177
|
+
http_response = http.request( request )
|
178
|
+
|
179
|
+
description_of_response.http_status_code = http_response.code.to_i
|
180
|
+
description_of_response.http_headers = http_response
|
181
|
+
description_of_response.raw_body_data = http_response.body
|
182
|
+
|
183
|
+
rescue Errno::ECONNREFUSED => e
|
184
|
+
description_of_response.http_status_code = 404
|
185
|
+
description_of_response.raw_body_data = ''
|
186
|
+
|
187
|
+
rescue Net::ReadTimeout => e
|
188
|
+
description_of_response.http_status_code = 408
|
189
|
+
description_of_response.raw_body_data = ''
|
190
|
+
|
191
|
+
rescue => e
|
192
|
+
description_of_response.http_status_code = 500
|
193
|
+
description_of_response.raw_body_data = e.message
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
return get_data_for_response( description_of_response )
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,367 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: http_based.rb
|
3
|
+
# (C):: Loyalty New Zealand 2015
|
4
|
+
#
|
5
|
+
# Purpose:: Resource endpoint definition.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 05-Mar-2015 (ADH): Created.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
module Hoodoo
|
13
|
+
class Client # Just used as a namespace here
|
14
|
+
class Endpoint # Just used as a namespace here
|
15
|
+
|
16
|
+
# Base class for endpoints that have an HTTP basis to their request
|
17
|
+
# and responses, even if the underlying transport is not HTTP. This
|
18
|
+
# is basically a collection of library-like routines useful to such
|
19
|
+
# classes and specifically excludes the part which actually makes
|
20
|
+
# an HTTP call (or AMQP call, or whatever) to a resource. That's up
|
21
|
+
# to the subclass.
|
22
|
+
#
|
23
|
+
# This must never be instantiated directly as an endpoint. Instead,
|
24
|
+
# instantiate a subclass such as Hoodoo::Client::Endpoint::HTTP or
|
25
|
+
# Hoodoo::Client::Endpoint::AMQP.
|
26
|
+
#
|
27
|
+
class HTTPBased < Hoodoo::Client::Endpoint
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# Describe a request for HTTP-like endpoints.
|
32
|
+
#
|
33
|
+
class DescriptionOfRequest
|
34
|
+
|
35
|
+
# The action to perform - a Symbol from
|
36
|
+
# Hoodoo::Services::Middleware::ALLOWED_ACTIONS.
|
37
|
+
#
|
38
|
+
attr_accessor :action
|
39
|
+
|
40
|
+
# A Hoodoo::Services::Discovery "For..." family member instance
|
41
|
+
# giving information required to 'find' the target resource. The
|
42
|
+
# required class instance depends upon the endpoint in use.
|
43
|
+
#
|
44
|
+
attr_accessor :discovery_result
|
45
|
+
|
46
|
+
# The full HTTP URI (or equivalent HTTP URI for HTTP-like, but
|
47
|
+
# non-HTTP systems like AMQP) at which the endpoint is found.
|
48
|
+
# Excludes any query string or resource identifier portion (it
|
49
|
+
# is the "list" action URI without query data, in essence)
|
50
|
+
#
|
51
|
+
attr_accessor :endpoint_uri
|
52
|
+
|
53
|
+
# Full URI (as a URI object) of an HTTP proxy to use as an
|
54
|
+
# override to <tt>ENV['HTTP_PROXY']</tt> which Ruby itself
|
55
|
+
# will otherwise read. Will be +nil+ for no proxy override.
|
56
|
+
#
|
57
|
+
attr_accessor :proxy_uri
|
58
|
+
|
59
|
+
# An optional String indicating a relative or absolute file
|
60
|
+
# path to the location of a .pem format Certificate
|
61
|
+
# Authority file (trust store), which may include multliple
|
62
|
+
# certificates. The certificates in the file will be used
|
63
|
+
# by Net::HTTP to validate the SSL Ceritificate Chain
|
64
|
+
# presented by remote servers, when calling endpoints over
|
65
|
+
# HTTPS with Hoodoo::Client.
|
66
|
+
#
|
67
|
+
# Default +nil+ value should be used in nearly all cases
|
68
|
+
# and uses Ruby OpenSSL defaults which are generally
|
69
|
+
# Operating System provided.
|
70
|
+
#
|
71
|
+
attr_accessor :ca_file
|
72
|
+
|
73
|
+
# Optional Float indicating the Net::HTTP read timeout value.
|
74
|
+
# This operates at the HTTP transport level and is independent
|
75
|
+
# of any timeouts set within the API providing server.
|
76
|
+
#
|
77
|
+
attr_accessor :http_timeout
|
78
|
+
|
79
|
+
# Optional Hash of query data.
|
80
|
+
#
|
81
|
+
attr_accessor :query_hash
|
82
|
+
|
83
|
+
# Optional Hash of body data for actions +:create+ and +:update+.
|
84
|
+
#
|
85
|
+
attr_accessor :body_hash
|
86
|
+
|
87
|
+
# Optional resource identifier for actions +:show+, +:update+ and
|
88
|
+
# +:delete+:
|
89
|
+
#
|
90
|
+
attr_accessor :ident
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
# Description of data that will be used for request - essentially a
|
95
|
+
# compilation of a DescriptionOfRequest instance produced via a call
|
96
|
+
# to #get_data_for_request.
|
97
|
+
#
|
98
|
+
class DataForRequest
|
99
|
+
|
100
|
+
# The full HTTP URI (or equivalent HTTP URI for HTTP-like, but
|
101
|
+
# non-HTTP systems like AMQP) for the call, including any resource
|
102
|
+
# identifier and query data.
|
103
|
+
#
|
104
|
+
attr_accessor :full_uri
|
105
|
+
|
106
|
+
# String of compiled body data for all actions (may be empty).
|
107
|
+
#
|
108
|
+
attr_accessor :body_string
|
109
|
+
|
110
|
+
# Hash of headers; keys are HTTP header names as a Strings (e.g.
|
111
|
+
# "Content-Type", "X-Interaction-ID"), values are header values
|
112
|
+
# as Strings.
|
113
|
+
#
|
114
|
+
attr_accessor :header_hash
|
115
|
+
|
116
|
+
# Hash of query; keys are query keys as Strings (e.g.
|
117
|
+
# "search"), values are query values as URL encoded Strings. (e.g.
|
118
|
+
# "outlet_id%3Dd32a0e15754a486989fdde2b0830fe12")
|
119
|
+
#
|
120
|
+
attr_accessor :query_hash
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# Description of data describing an HTTP response. Used by
|
125
|
+
# #get_data_for_response to generate a response array or hash
|
126
|
+
# (see #response_class_for).
|
127
|
+
#
|
128
|
+
class DescriptionOfResponse
|
129
|
+
|
130
|
+
# The action that was performed - a Symbol from
|
131
|
+
# Hoodoo::Services::Middleware::ALLOWED_ACTIONS.
|
132
|
+
#
|
133
|
+
attr_accessor :action
|
134
|
+
|
135
|
+
# The HTTP status code _as an Integer_.
|
136
|
+
#
|
137
|
+
attr_accessor :http_status_code
|
138
|
+
|
139
|
+
# The raw ("unparsed") returned body data as a String.
|
140
|
+
#
|
141
|
+
attr_accessor :raw_body_data
|
142
|
+
|
143
|
+
# An object that will allow Hash-like lookup and iteration of
|
144
|
+
# key/value pairs via +each+. A raw Net::HTTPOK (sic.) instance
|
145
|
+
# from a successful response is an example of such an object.
|
146
|
+
#
|
147
|
+
# May be unset (+nil+) or empty, especially for error cases.
|
148
|
+
#
|
149
|
+
attr_accessor :http_headers
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
# Preprocess a high level request description, returning HTTP
|
154
|
+
# orientated compiled data as a DataForRequest instance.
|
155
|
+
#
|
156
|
+
# +description_of_request+:: DescriptionOfRequest instance.
|
157
|
+
#
|
158
|
+
def get_data_for_request( description_of_request )
|
159
|
+
body_hash = Hoodoo::Utilities.stringify( description_of_request.body_hash )
|
160
|
+
query_hash = Hoodoo::Utilities.stringify( description_of_request.query_hash )
|
161
|
+
ident = description_of_request.ident.to_s
|
162
|
+
|
163
|
+
body_data = body_hash.nil? ? '' : ::JSON.generate( body_hash )
|
164
|
+
|
165
|
+
# Amazingly, there's no fast way to deep clone a URI. Long story
|
166
|
+
# short - Marshal.load(Marshal.dump(uri)) takes, astonishingly,
|
167
|
+
# twice as long to execute as URI.parse(uri.to_s). I have no idea
|
168
|
+
# how that's possible. The Addressable gem is even slower.
|
169
|
+
#
|
170
|
+
# require 'benchmark'
|
171
|
+
# require 'addressable/uri' # Assuming gem is present
|
172
|
+
#
|
173
|
+
# s='http://user:password@pond.org.uk:9924/foo/bar.baz?thing=that'
|
174
|
+
# u=URI.parse(s)
|
175
|
+
# a=Addressable::URI.parse(s)
|
176
|
+
#
|
177
|
+
# Benchmark.realtime { 1000000.times { u2=URI.parse(u.to_s) } }
|
178
|
+
# # => 14.110195
|
179
|
+
# Benchmark.realtime { 1000000.times { a2=a.dup } }
|
180
|
+
# # => 26.530487
|
181
|
+
# Benchmark.realtime { 1000000.times { u2=Marshal.load(Marshal.dump(u)) } }
|
182
|
+
# # => 22.048637
|
183
|
+
#
|
184
|
+
# ...repeatably.
|
185
|
+
#
|
186
|
+
# TODO: Is it possible to improve this? It's truly awful, to the
|
187
|
+
# extent I'm almost motivated to write a URI handler gem.
|
188
|
+
# The core library URI API is tragically bad.
|
189
|
+
|
190
|
+
remote_uri = URI.parse( description_of_request.endpoint_uri.to_s )
|
191
|
+
|
192
|
+
# Now we've a copy, we can use high level URI methods to manipulate
|
193
|
+
# it to form the full request URI.
|
194
|
+
|
195
|
+
remote_uri.path << "/#{ URI::escape( ident ) }" unless ident.nil?
|
196
|
+
|
197
|
+
# Grey area over whether this encodes spaces as "%20" or "+", but
|
198
|
+
# so long as the middleware consistently uses the URI encode/decode
|
199
|
+
# calls, it should work out in the end anyway.
|
200
|
+
|
201
|
+
unless query_hash.nil?
|
202
|
+
query_hash = query_hash.dup
|
203
|
+
query_hash[ 'search' ] = URI.encode_www_form( query_hash[ 'search' ] ) if ( query_hash[ 'search' ].is_a?( ::Hash ) )
|
204
|
+
query_hash[ 'filter' ] = URI.encode_www_form( query_hash[ 'filter' ] ) if ( query_hash[ 'filter' ].is_a?( ::Hash ) )
|
205
|
+
|
206
|
+
query_hash[ '_embed' ] = query_hash[ '_embed' ].join( ',' ) if ( query_hash[ '_embed' ].is_a?( ::Array ) )
|
207
|
+
query_hash[ '_reference' ] = query_hash[ '_reference' ].join( ',' ) if ( query_hash[ '_reference' ].is_a?( ::Array ) )
|
208
|
+
|
209
|
+
query_hash.delete( 'search' ) if query_hash[ 'search' ].nil? || query_hash[ 'search' ].empty?
|
210
|
+
query_hash.delete( 'filter' ) if query_hash[ 'filter' ].nil? || query_hash[ 'filter' ].empty?
|
211
|
+
query_hash.delete( '_embed' ) if query_hash[ '_embed' ].nil? || query_hash[ '_embed' ].empty?
|
212
|
+
query_hash.delete( '_reference' ) if query_hash[ '_reference' ].nil? || query_hash[ '_reference' ].empty?
|
213
|
+
end
|
214
|
+
|
215
|
+
remote_uri.query = URI.encode_www_form( query_hash ) unless query_hash.nil? || query_hash.empty?
|
216
|
+
|
217
|
+
headers = {
|
218
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
219
|
+
'Content-Language' => self.locale() || 'en-nz', # Locale comes from Endpoint superclass
|
220
|
+
'Accept-Language' => self.locale() || 'en-nz'
|
221
|
+
}
|
222
|
+
|
223
|
+
# Interaction comes from Endpoint superclass.
|
224
|
+
#
|
225
|
+
# TODO: Can anything be done about inbound X-Interaction-ID
|
226
|
+
# headers or interaction ID values specified by the
|
227
|
+
# calling client which would be stripped by an Alchemy
|
228
|
+
# architecture but not by conventional HTTP servers?
|
229
|
+
#
|
230
|
+
unless self.interaction().nil?
|
231
|
+
headers[ 'X-Interaction-ID' ] = self.interaction().interaction_id
|
232
|
+
end
|
233
|
+
|
234
|
+
# Session ID comes from Endpoint superclass.
|
235
|
+
#
|
236
|
+
unless self.session_id().nil?
|
237
|
+
headers[ 'X-Session-ID'] = self.session_id()
|
238
|
+
end
|
239
|
+
|
240
|
+
# A suite of options is defined by a constant in the Endpoint
|
241
|
+
# superclass.
|
242
|
+
#
|
243
|
+
Hoodoo::Client::Headers::HEADER_TO_PROPERTY.each do | rack_header, description |
|
244
|
+
header_name = description[ :header ]
|
245
|
+
header_proc = description[ :header_proc ]
|
246
|
+
property = description[ :property ]
|
247
|
+
|
248
|
+
property_value = self.send( property )
|
249
|
+
|
250
|
+
unless property_value.nil?
|
251
|
+
headers[ header_name ] = header_proc.call( property_value )
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
data = DataForRequest.new
|
256
|
+
data.full_uri = remote_uri
|
257
|
+
data.body_string = body_data
|
258
|
+
data.header_hash = headers
|
259
|
+
data.query_hash = query_hash
|
260
|
+
|
261
|
+
return data
|
262
|
+
end
|
263
|
+
|
264
|
+
# Process a raw HTTP response description, returning an instance of
|
265
|
+
# Hoodoo::Client::AugmentedArray or Hoodoo::Client::AugmentedHash
|
266
|
+
# with either processed body data inside, or error data associated.
|
267
|
+
#
|
268
|
+
# +description_of_response+:: DescriptionOfResponse instance.
|
269
|
+
#
|
270
|
+
def get_data_for_response( description_of_response )
|
271
|
+
code = description_of_response.http_status_code
|
272
|
+
body = description_of_response.raw_body_data
|
273
|
+
|
274
|
+
begin
|
275
|
+
parsed = ::JSON.parse(
|
276
|
+
body,
|
277
|
+
:object_class => Hoodoo::Client::AugmentedHash,
|
278
|
+
:array_class => Hoodoo::Client::AugmentedArray
|
279
|
+
)
|
280
|
+
|
281
|
+
rescue => e
|
282
|
+
data = response_class_for( description_of_response.action ).new
|
283
|
+
data.response_options = Hoodoo::Client::Headers.x_header_to_options(
|
284
|
+
description_of_response.http_headers
|
285
|
+
)
|
286
|
+
|
287
|
+
case code
|
288
|
+
when 404
|
289
|
+
return generate_404_response_for( description_of_response.action )
|
290
|
+
when 408
|
291
|
+
data.platform_errors.add_error( 'platform.timeout' )
|
292
|
+
when 200
|
293
|
+
data.platform_errors.add_error(
|
294
|
+
'platform.fault',
|
295
|
+
:message => 'Could not parse body data returned from inter-resource call despite receiving HTTP status code 200',
|
296
|
+
:reference => { :exception => RuntimeError.new( "#{ body }" ) }
|
297
|
+
)
|
298
|
+
when 204
|
299
|
+
if data.response_options[ 'deja_vu' ] != 'confirmed'
|
300
|
+
data.platform_errors.add_error(
|
301
|
+
'platform.fault',
|
302
|
+
:message => "Unexpected raw HTTP status code 204 with 'X-Deja-Vu: confirmed' not present",
|
303
|
+
:reference => { :exception => RuntimeError.new( '204' ) }
|
304
|
+
)
|
305
|
+
end # Else do nothing; keep the empty 'data'
|
306
|
+
else
|
307
|
+
data.platform_errors.add_error(
|
308
|
+
'platform.fault',
|
309
|
+
:message => "Unexpected raw HTTP status code #{ code } with non-JSON response",
|
310
|
+
:reference => { :exception => RuntimeError.new( "#{ body }" ) }
|
311
|
+
)
|
312
|
+
end
|
313
|
+
|
314
|
+
return data
|
315
|
+
end
|
316
|
+
|
317
|
+
# Just in case someone changes JSON parsers under us and the
|
318
|
+
# replacement doesn't support the options used above...
|
319
|
+
|
320
|
+
unless parsed.is_a?( Hoodoo::Client::AugmentedHash )
|
321
|
+
raise "Hoodoo::Services::Middleware: Incompatible JSON implementation in use which doesn't understand 'object_class' or 'array_class' options"
|
322
|
+
end
|
323
|
+
|
324
|
+
# If the parsed data wrapped an array, extract just the array
|
325
|
+
# part, else the hash part.
|
326
|
+
|
327
|
+
if ( parsed[ '_data' ].is_a?( ::Array ) )
|
328
|
+
size = parsed[ '_dataset_size' ]
|
329
|
+
parsed = parsed[ '_data' ]
|
330
|
+
parsed.dataset_size = size
|
331
|
+
|
332
|
+
elsif ( parsed[ 'kind' ] == 'Errors' )
|
333
|
+
|
334
|
+
# This isn't an array, it's an AugmentedHash describing errors.
|
335
|
+
# Turn this into a formal errors collection.
|
336
|
+
|
337
|
+
errors_from_resource = Hoodoo::Errors.new()
|
338
|
+
|
339
|
+
parsed[ 'errors' ].each do | error |
|
340
|
+
errors_from_resource.add_precompiled_error(
|
341
|
+
error[ 'code' ],
|
342
|
+
error[ 'message' ],
|
343
|
+
error[ 'reference' ],
|
344
|
+
code
|
345
|
+
)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Use a 'clean' copy of the response class rather than keeping
|
349
|
+
# the originating data. People will not make assumptions about
|
350
|
+
# error payloads and trip over with the early return 404 stuff
|
351
|
+
# etc. this way.
|
352
|
+
|
353
|
+
parsed = response_class_for( description_of_response.action ).new
|
354
|
+
parsed.set_platform_errors( errors_from_resource )
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
parsed.response_options = Hoodoo::Client::Headers.x_header_to_options(
|
359
|
+
description_of_response.http_headers
|
360
|
+
)
|
361
|
+
|
362
|
+
return parsed
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|