hoodoo 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +8 -8
  2. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +43 -25
  3. data/lib/hoodoo/discovery.rb +1 -1
  4. data/lib/hoodoo/services/discovery/discoverers/by_flux.rb +130 -0
  5. data/lib/hoodoo/services/discovery/results/for_amqp.rb +15 -21
  6. data/lib/hoodoo/services/discovery/results/for_local.rb +17 -0
  7. data/lib/hoodoo/services/middleware/amqp_log_message.rb +92 -154
  8. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +25 -50
  9. data/lib/hoodoo/services/middleware/middleware.rb +75 -25
  10. data/lib/hoodoo/services/services/service.rb +2 -2
  11. data/lib/hoodoo/version.rb +1 -1
  12. data/spec/services/discovery/discoverers/by_flux_spec.rb +134 -0
  13. data/spec/services/discovery/results/for_amqp_spec.rb +4 -7
  14. data/spec/services/discovery/results/for_local_spec.rb +4 -0
  15. data/spec/services/middleware/amqp_log_message_spec.rb +32 -34
  16. data/spec/services/middleware/amqp_log_writer_spec.rb +2 -5
  17. data/spec/services/middleware/middleware_exotic_communication_spec.rb +147 -143
  18. data/spec/services/middleware/middleware_logging_spec.rb +10 -10
  19. data/spec/services/middleware/middleware_multi_remote_spec.rb +1 -1
  20. data/spec/services/middleware/middleware_public_spec.rb +73 -18
  21. data/spec/services/middleware/middleware_spec.rb +4 -4
  22. data/spec/services/services/application_spec.rb +3 -3
  23. data/spec/spec_helper.rb +3 -8
  24. metadata +19 -7
  25. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +0 -66
  26. data/spec/alchemy/alchemy-amq.rb +0 -33
  27. data/spec/services/discovery/discoverers/by_consul_spec.rb +0 -29
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzEwZWNjMTA5OTJhODlhN2VkZGQxMDQ0NzNhOGVjZjUxMTM2ZmM1Nw==
4
+ MTFiNjAzNDk4MDdmOTFmNWRkZTg1MmY3ODExMmY3ZGU4MTU3YjkzNg==
5
5
  data.tar.gz: !binary |-
6
- OTA1MzJjZTZlYzFhY2FiNWUxZWM3MDBiN2QwYzEwNDYyNDM2Y2ZjOA==
6
+ MjMwNDRjODEzZDU5YmRmM2E0YmRlNTlmYTVkYzA3NmIxMDA3ZjE1Yg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDQ4YmVkYmQ3YWEwMWY1NGU1ZDZlYTA2NjAxNzM2NTFiYzk5NjI1NTNmZWE0
10
- NGYwYjhlMmIxZmY0YTViZjk4Zjg4ODgzODRjNGQ5MDhmNjEwZjVmZDJhM2Yx
11
- ODQzZjliM2NhYTE1NTQ2ZjdjNzk1YTZmNmE3NTM2MGQ2NWFhNjE=
9
+ ZjEzZDYxZDJjZGI3MWU0NDFmYTYwNzQ2ZmYwZGY4MmQ2Zjc0NWJhZmRiMjli
10
+ ZjQ0MTA1MzkzYTFkN2RhZTA1YWFkNjc1MDI4MDgwNzYyZDk5YmFhZTdmZDE1
11
+ ZTJlYjRjZDc5N2IzYmNiNTc0M2Q0NzMzNDhlODk3OTEzZjc0YTU=
12
12
  data.tar.gz: !binary |-
13
- ODhiYjgzMTFmYTZhOGFhZGM4NDM2OWU3N2MwNjE3OGVhMTk0MGFkNGFhZTg5
14
- Y2E5Mzg0YjI3OTBkNzcyNjJhZGI5MjYyN2M5YTYxOWYyYjVjMTgxMGRiM2Ix
15
- MGQ4YTBiNWY4MzMzNTI3NWJkODI3ZTE0YWJmYjlkYjI3YWQ4MTg=
13
+ MzMwNGY1MGZlOTFiZjE3YmI0MDgxYjcwYTUxYTliNTQxNDFjODMwNThkMGUx
14
+ ZDk5NzE0ZDcwMWY1YzZhMTIxMzgyY2ZiMWJiMTFiZjk4ZmNlMTE0N2JmZThj
15
+ ZDIxZWM4MzQ0ODc0ODdmNWU5OWEzYmU3ODlkMTE4OTI1ZTNiYWM=
@@ -39,7 +39,7 @@ module Hoodoo
39
39
  # to keep Rack happy.
40
40
 
41
41
  endpoint_uri = URI.parse( 'http://localhost:80' )
42
- endpoint_uri.path = @discovery_result.equivalent_path
42
+ endpoint_uri.path = @discovery_result.routing_path
43
43
 
44
44
  @description = Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfRequest.new
45
45
  @description.discovery_result = @discovery_result
@@ -136,40 +136,58 @@ module Hoodoo
136
136
  action = description_of_request.action
137
137
  data = get_data_for_request( description_of_request )
138
138
 
139
- # Host and port are just there to keep Rack happy at the
139
+ # Host and port are only provided to keep Rack happy at the
140
140
  # receiving end of the over-AMQP synthesised HTTP request.
141
141
 
142
- alchemy_options = {
143
- :host => description_of_request.endpoint_uri.host,
144
- :port => description_of_request.endpoint_uri.port,
145
- :query => data.query_hash,
146
- :body => data.body_string,
147
- :headers => data.header_hash
142
+ http_method =
143
+ {
144
+ :create => 'POST',
145
+ :update => 'PATCH',
146
+ :delete => 'DELETE'
147
+ }[ action ] || 'GET'
148
+
149
+ http_message =
150
+ {
151
+ 'scheme' => 'http',
152
+ 'verb' => http_method,
153
+
154
+ 'host' => description_of_request.endpoint_uri.host,
155
+ 'port' => description_of_request.endpoint_uri.port,
156
+ 'path' => data.full_uri.path,
157
+ 'query' => data.query_hash,
158
+
159
+ 'headers' => data.header_hash,
160
+ 'body' => data.body_string
148
161
  }
149
162
 
150
163
  unless self.session_id().nil? # Session comes from Endpoint superclass
151
- alchemy_options[ :session_id ] = self.session_id()
164
+ http_message[ 'session_id' ] = self.session_id()
152
165
  end
153
166
 
154
- http_method = {
155
- :create => 'POST',
156
- :update => 'PATCH',
157
- :delete => 'DELETE'
158
- }[ action ] || 'GET'
167
+ amqp_response = self.alchemy().send_message_to_resource( http_message )
168
+
169
+ description_of_response = DescriptionOfResponse.new
170
+ description_of_response.action = action
171
+ description_of_response.http_headers = {}
159
172
 
160
- description_of_response = DescriptionOfResponse.new
161
- description_of_response.action = action
173
+ if amqp_response == AlchemyFlux::TimeoutError
174
+ description_of_response.http_status_code = 408
175
+ description_of_response.raw_body_data = '408 Timeout'
162
176
 
163
- amqp_response = self.alchemy().http_request(
164
- description_of_request.discovery_result.queue_name,
165
- http_method,
166
- data.full_uri.path,
167
- alchemy_options
168
- )
177
+ elsif amqp_response == AlchemyFlux::MessageNotDeliveredError
178
+ description_of_response.http_status_code = 404
179
+ description_of_response.raw_body_data = '404 Not Found'
169
180
 
170
- description_of_response.http_status_code = amqp_response.status_code.to_i
171
- description_of_response.http_headers = amqp_response.headers || {}
172
- description_of_response.raw_body_data = amqp_response.body
181
+ elsif amqp_response.is_a?( Hash )
182
+ description_of_response.http_status_code = amqp_response[ 'status_code' ].to_i
183
+ description_of_response.http_headers = amqp_response[ 'headers' ] || {}
184
+ description_of_response.raw_body_data = amqp_response[ 'body' ]
185
+
186
+ else
187
+ description_of_response.http_status_code = 500
188
+ description_of_response.raw_body_data = '500 Internal Server Error'
189
+
190
+ end
173
191
 
174
192
  return get_data_for_response( description_of_response )
175
193
  end
@@ -15,6 +15,6 @@ require 'hoodoo/services/discovery/results/for_local'
15
15
  require 'hoodoo/services/discovery/results/for_remote'
16
16
 
17
17
  require 'hoodoo/services/discovery/discoverers/by_convention'
18
- require 'hoodoo/services/discovery/discoverers/by_consul'
18
+ require 'hoodoo/services/discovery/discoverers/by_flux'
19
19
  require 'hoodoo/services/discovery/discoverers/by_drb/drb_server'
20
20
  require 'hoodoo/services/discovery/discoverers/by_drb/by_drb'
@@ -0,0 +1,130 @@
1
+ ########################################################################
2
+ # File:: by_flux.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Discover resource endpoint locations via Alchemy Flux.
6
+ # ----------------------------------------------------------------------
7
+ # 03-Mar-2015 (ADH): Created.
8
+ # 21-Jan-2016 (ADH): Reimplemented for Alchemy Flux.
9
+ ########################################################################
10
+
11
+ module Hoodoo
12
+ module Services
13
+ class Discovery # Just used as a namespace here
14
+
15
+ # Discover resource endpoint locations via Alchemy Flux.
16
+ #
17
+ # For Flux, it's less about discovery as it is about convention
18
+ # and announcing. We have to set some system variables when the
19
+ # application starts up, _before_ the Rack `run` call gets as
20
+ # far as the Alchemy Flux server's implementation - in practice
21
+ # this means announcement needs to happen from the Hoodoo
22
+ # middleware's constructor, synchronously. The environment
23
+ # variables tell Flux about this local service's URI-located
24
+ # endpoints and derive a consistent, replicable 'service name'
25
+ # from the resources which the service implements.
26
+ #
27
+ # Once all that is set up, the local Alchemy instance knows how
28
+ # to listen for relevant messages for 'this' service on the queue
29
+ # and Hoodoo in 'this' service knowns which resources are local,
30
+ # or which are remote; and it knows that Flux is able in turn to
31
+ # use URI-based to-resource communications for inter-resource
32
+ # calls without any further explicit discovery within Hoodoo
33
+ # beyond simply saying "here's the AMQP Flux endpoint class".
34
+ #
35
+ class ByFlux < Hoodoo::Services::Discovery
36
+
37
+ protected
38
+
39
+ # Announce the location of an instance to Alchemy Flux.
40
+ #
41
+ # Call via Hoodoo::Services::Discovery::Base#announce.
42
+ #
43
+ # +resource+:: Passed to #discover_remote.
44
+ # +version+:: Passed to #discover_remote.
45
+ # +options+:: See below.
46
+ #
47
+ # The Options hash informs the announcer of the intended endpoint
48
+ # base URI for the resource and also, where available, provides a
49
+ # head-up on the full range of resource _names_ that will be
50
+ # present in this single service application (see
51
+ # Hoodoo::Services::Service::comprised_of). Keys MUST be Symbols.
52
+ # Associated required values are:
53
+ #
54
+ # +services+:: Array of Hoodoo::Services::Discovery::ForLocal
55
+ # instances describing available resources in this
56
+ # local service.
57
+ #
58
+ def announce_remote( resource, version, options = {} )
59
+
60
+ alchemy_resource_paths = ENV[ 'ALCHEMY_RESOURCE_PATHS' ]
61
+ alchemy_service_name = ENV[ 'ALCHEMY_SERVICE_NAME' ]
62
+
63
+ # Under Flux, we "announce" via a local environment variable when
64
+ # this service awakens which tells Flux what to listen for on the
65
+ # AMQP queue.
66
+ #
67
+ # Since inbound HTTP calls into the architecture are based on URIs
68
+ # and paths, there needs to be a mapping at that point to queue
69
+ # endpoints. Historically Hoodoo adopted an (in hindsight, unwise)
70
+ # approach of "/v<version>/<pluralised_resource>" c.f. Rails,
71
+ # rather than just "/<version>/<resource>" - e.g. there was
72
+ # "/v1/members" instead of "/1/Member". This means things like the
73
+ # "ByConvention" discoverer have to use pluralisation rules and
74
+ # exceptions. It's messy.
75
+ #
76
+ # To clean things up, the work on Alchemy Flux sets up *two* paths
77
+ # in Hoodoo - the old one for backwards compatibility, and a new
78
+ # one of the above simpler form. Now it's easy to go from version
79
+ # and resource name to path or back internally with no mappings.
80
+ #
81
+ if ( alchemy_resource_paths.nil? ||
82
+ alchemy_resource_paths.strip.empty? )
83
+
84
+ services = options[ :services ] || []
85
+ paths = []
86
+
87
+ services.each do | service |
88
+ custom_path = service.base_path
89
+ de_facto_path = service.de_facto_base_path
90
+
91
+ paths << custom_path << de_facto_path
92
+ end
93
+
94
+ ENV[ 'ALCHEMY_RESOURCE_PATHS' ] = paths.join( ',' )
95
+ end
96
+
97
+ if ( alchemy_service_name.nil? ||
98
+ alchemy_service_name.strip.empty? )
99
+ ENV[ 'ALCHEMY_SERVICE_NAME' ] = Hoodoo::Services::Middleware::service_name()
100
+ end
101
+
102
+ return discover_remote( resource, version )
103
+ end
104
+
105
+ # Discover the location of an instance.
106
+ #
107
+ # Returns a Hoodoo::Services::Discovery::ForAMQP instance if
108
+ # the endpoint is found, else +nil+.
109
+ #
110
+ # Call via Hoodoo::Services::Discovery::Base#announce.
111
+ #
112
+ # +resource+:: Passed to #discover_remote.
113
+ # +version+:: Passed to #discover_remote.
114
+ #
115
+ def discover_remote( resource, version )
116
+ de_facto_path = Hoodoo::Services::Middleware::de_facto_path_for(
117
+ resource,
118
+ version
119
+ )
120
+
121
+ return Hoodoo::Services::Discovery::ForAMQP.new(
122
+ resource: resource,
123
+ version: version
124
+ )
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+ end
@@ -25,32 +25,26 @@ module Hoodoo
25
25
  #
26
26
  attr_accessor :version
27
27
 
28
- # Queue name for the target service implementation, as a
29
- # String (e.g. "service.account").
28
+ # Path at which the resource is expected to be found on the queue
29
+ # (routing via Topic Exchange and Alchemy Flux's translations of
30
+ # paths to keys).
30
31
  #
31
- attr_accessor :queue_name
32
-
33
- # URL path equivalent that should be mapped to the queue in
34
- # #queue_name, as a String (e.g. "/v2/accounts").
35
- #
36
- attr_accessor :equivalent_path
32
+ attr_reader :routing_path
37
33
 
38
34
  # Create an instance with named parameters as follows:
39
35
  #
40
- # +resource+:: See #resource.
41
- # +version+:: See #version.
42
- # +queue_name+:: See #queue_name.
43
- # +equivalent_pat+:: See #equivalent_pat.
36
+ # +resource+:: See #resource.
37
+ # +version+:: See #version.
44
38
  #
45
- def initialize( resource:,
46
- version:,
47
- queue_name:,
48
- equivalent_path: )
49
-
50
- self.resource = resource.to_sym
51
- self.version = version.to_i
52
- self.queue_name = queue_name
53
- self.equivalent_path = equivalent_path
39
+ def initialize( resource:, version: )
40
+
41
+ @resource = resource.to_sym
42
+ @version = version.to_i
43
+ @routing_path = Hoodoo::Services::Middleware.de_facto_path_for(
44
+ resource,
45
+ version
46
+ )
47
+
54
48
  end
55
49
  end
56
50
  end
@@ -46,6 +46,17 @@ module Hoodoo
46
46
  #
47
47
  attr_accessor :routing_regexp
48
48
 
49
+ # The de facto routing equivalent of #base_path. This is not
50
+ # a custom path based on the interface's declared endpoint; it
51
+ # is derived directlyf rom resource or version - for example,
52
+ # "/1/Product" or "/2/Member". String.
53
+ #
54
+ attr_accessor :de_facto_base_path
55
+
56
+ # As #routing_regexp, but matches #de_facto_base_path.
57
+ #
58
+ attr_accessor :de_facto_routing_regexp
59
+
49
60
  # The Hoodoo::Services::Interface subclass _class_ describing the
50
61
  # resource interface.
51
62
  #
@@ -62,6 +73,8 @@ module Hoodoo
62
73
  # +version+:: See #version.
63
74
  # +base_path+:: See #base_path.
64
75
  # +routing_regexp+:: See #routing_regexp.
76
+ # +de_facto_base_path+:: See #de_facto_base_path.
77
+ # +de_facto_routing_regexp+:: See #de_facto_routing_regexp.
65
78
  # +interface_class+:: See #interface_class.
66
79
  # +implementation_instance+:: See #implementation_instance.
67
80
  #
@@ -69,6 +82,8 @@ module Hoodoo
69
82
  version:,
70
83
  base_path:,
71
84
  routing_regexp:,
85
+ de_facto_base_path:,
86
+ de_facto_routing_regexp:,
72
87
  interface_class:,
73
88
  implementation_instance: )
74
89
 
@@ -76,6 +91,8 @@ module Hoodoo
76
91
  self.version = version.to_i
77
92
  self.base_path = base_path
78
93
  self.routing_regexp = routing_regexp
94
+ self.de_facto_base_path = de_facto_base_path
95
+ self.de_facto_routing_regexp = de_facto_routing_regexp
79
96
  self.interface_class = interface_class
80
97
  self.implementation_instance = implementation_instance
81
98
  end
@@ -3,8 +3,9 @@
3
3
  # (C):: Loyalty New Zealand 2014
4
4
  #
5
5
  # Purpose:: Structured logging onto an AMQP-based queue, via the
6
- # AlchemyAMQ gem. Optional; class is defined only if the
7
- # supporting AlchemyAMQ gem's classes are defined.
6
+ # Alchemy Flux gem. This class just exists to rationalise
7
+ # any parameters inbound in order to generate a clean
8
+ # representation for Flux.
8
9
  #
9
10
  # The middleware uses this to put log and error messages
10
11
  # on the queue. Interested services use this to read such
@@ -13,173 +14,110 @@
13
14
  # 20-Nov-2014 (ADH): Created.
14
15
  ########################################################################
15
16
 
16
- # TODO: See spec/alchemy/alchemy-amq.rb. Remove this once 'real' Alchemy
17
- # is opened.
18
- #
19
- $LOAD_PATH.unshift File.join( File.dirname( __FILE__ ), 'alchemy' )
20
-
21
17
  module Hoodoo; module Services
22
18
  class Middleware
23
19
 
24
- begin
25
- require 'alchemy-amq' # Optional
20
+ # A representation of a log message placed on an AMQP-based queue.
21
+ # The primary expected communications interface is Alchemy Flux at
22
+ # the time of writing.
23
+ #
24
+ class AMQPLogMessage
26
25
 
27
- # For AlchemyAMQ gem users, the AMQPLogMessage class provides an
28
- # AlchemyAMQ::Message subclass used for sending structured log data to
29
- # the queue. Hoodoo::Logger uses this.
26
+ # The Time +strftime+ formatter used for string conversions in this
27
+ # class.
30
28
  #
31
- # See the AlchemyAMQ gem for more details.
29
+ TIME_FORMATTER = '%Y-%m-%d %H:%M:%S.%12N %Z'
30
+
31
+ # A UUID to assign to this log message. See Hoodoo::UUID::generate.
32
32
  #
33
- class AMQPLogMessage < ::AlchemyAMQ::Message
34
-
35
- # The named "type" of this message, to be registered with AlchemyAMQ.
36
- #
37
- TYPE = 'hoodoo_service_middleware_amqp_log_message'
38
-
39
- # The Time +strftime+ formatter used for string conversions in this
40
- # class.
41
- #
42
- TIME_FORMATTER = '%Y-%m-%d %H:%M:%S.%12N %Z'
43
-
44
- # This line of code registers wth AlchemyAMQ, but also makes RDoc
45
- # screw up. RDoc decides that we have a new module,
46
- # Hoodoo::Services::Middleware::AMQPLogMessage::AlchemyAMQ. Very
47
- # strange...
48
- #
49
- ::AlchemyAMQ::Message.register_type( TYPE, self )
50
-
51
- # ...so do _this_ purely so that we can get 100% real documentation
52
- # coverage without it being clouded by RDoc's hiccups.
53
-
54
- # This documentation exists purely to work around an RDoc hiccup where
55
- # it thinks such a module exists.
56
- #
57
- # See file "services/middleware/amqp_log_message.rb" for details.
58
- #
59
- module AlchemyAMQ
60
- end
33
+ attr_accessor :id
61
34
 
62
- # A UUID to assign to this log message. See Hoodoo::UUID::generate.
63
- #
64
- attr_accessor :id
65
-
66
- # Logging level. See Hoodoo::Logger.
67
- #
68
- attr_accessor :level
69
-
70
- # Logging component. See Hoodoo::Logger.
71
- #
72
- attr_accessor :component
73
-
74
- # Component log code. See Hoodoo::Logger.
75
- #
76
- attr_accessor :code
77
-
78
- # The time at which this log message is being reported to the Logger
79
- # instance. This is a formatted *String* to high accuracy. See also
80
- # #reported_at=.
81
- #
82
- attr_reader :reported_at
83
-
84
- # Set the time read back by #reported_at using a Time instance. This
85
- # is formatted internally as a String via TIME_FORMATTER and reported
86
- # as such in subsequent calls to #reported_at.
87
- #
88
- # Conversion from Time to String is done here, rather than by the
89
- # caller setting this instance's variables, so that we can internally
90
- # enforce the accuracy required for this field.
91
- #
92
- # +time+:: The Time instance to set (and process into a string
93
- # internally via TIME_FORMATTER), *or* a String instance
94
- # already so formatted, *or* +nil+ to clear the value.
95
- #
96
- def reported_at=( time )
97
- if time.is_a?( String )
98
- @reported_at = time
99
- elsif time.is_a?( Time )
100
- @reported_at = time.strftime( TIME_FORMATTER )
101
- else
102
- @reported_at = nil
103
- end
104
- end
35
+ # Logging level. See Hoodoo::Logger.
36
+ #
37
+ attr_accessor :level
105
38
 
106
- # Log payload. See Hoodoo::Logger.
107
- #
108
- attr_accessor :data
109
-
110
- # Optional calling Caller ID, via session data inside the payload - see
111
- # Hoodoo::Logger.
112
- #
113
- attr_accessor :caller_id
114
-
115
- # Optional interaction UUID, via session data inside the payload - see
116
- # Hoodoo::Logger.
117
- #
118
- attr_accessor :interaction_id
119
-
120
- # Optional hash of identity properties from the session data inside the
121
- # payload - see Hoodoo::Logger.
122
- #
123
- attr_accessor :identity
124
-
125
- # Create an instance with options keyed on the attributes defined for
126
- # the class.
127
- #
128
- def initialize(options = {})
129
- update( options ) # Should be called before 'super'
130
- super( options )
131
-
132
- @type = AMQPLogMessage::TYPE
133
- end
39
+ # Logging component. See Hoodoo::Logger.
40
+ #
41
+ attr_accessor :component
134
42
 
135
- # Seralize this instance. See the AlchemyAMQ gem and
136
- # AlchemyAMQ::Message#serialize.
137
- #
138
- def serialize
139
- @content = {
140
- :id => @id,
141
- :level => @level,
142
- :component => @component,
143
- :code => @code,
144
- :reported_at => @reported_at,
145
-
146
- :data => @data,
147
-
148
- :interaction_id => @interaction_id,
149
- :caller_id => @caller_id,
150
- :identity => @identity
151
- }
152
-
153
- super
154
- end
43
+ # Component log code. See Hoodoo::Logger.
44
+ #
45
+ attr_accessor :code
155
46
 
156
- # Unpack a serialized representation into this instance. See the
157
- # AlchemyAMQ gem and AlchemyAMQ::Message#deserialize.
158
- #
159
- def deserialize
160
- super
161
- update( @content )
47
+ # The time at which this log message is being reported to the Logger
48
+ # instance. This is a formatted *String* to high accuracy. See also
49
+ # #reported_at=.
50
+ #
51
+ attr_reader :reported_at
52
+
53
+ # Set the time read back by #reported_at using a Time instance. This
54
+ # is formatted internally as a String via TIME_FORMATTER and reported
55
+ # as such in subsequent calls to #reported_at.
56
+ #
57
+ # Conversion from Time to String is done here, rather than by the
58
+ # caller setting this instance's variables, so that we can internally
59
+ # enforce the accuracy required for this field.
60
+ #
61
+ # +time+:: The Time instance to set (and process into a string
62
+ # internally via TIME_FORMATTER), *or* a String instance
63
+ # already so formatted, *or* +nil+ to clear the value.
64
+ #
65
+ def reported_at=( time )
66
+ if time.is_a?( String )
67
+ @reported_at = time
68
+ elsif time.is_a?( Time )
69
+ @reported_at = time.strftime( TIME_FORMATTER )
70
+ else
71
+ @reported_at = nil
162
72
  end
73
+ end
74
+
75
+ # Log payload. See Hoodoo::Logger.
76
+ #
77
+ attr_accessor :data
163
78
 
164
- # Set public attribute values according to an options hash keyed on
165
- # the attributes defined for the class.
166
- #
167
- def update( options )
168
- self.id = options[ :id ]
169
- self.level = options[ :level ]
170
- self.component = options[ :component ]
171
- self.code = options[ :code ]
172
- self.reported_at = options[ :reported_at ]
173
-
174
- self.data = options[ :data ]
175
-
176
- self.interaction_id = options[ :interaction_id ]
177
- self.caller_id = options[ :caller_id ]
178
- self.identity = options[ :identity ]
79
+ # Optional calling Caller ID, via session data inside the payload - see
80
+ # Hoodoo::Logger.
81
+ #
82
+ attr_accessor :caller_id
83
+
84
+ # Optional interaction UUID, via session data inside the payload - see
85
+ # Hoodoo::Logger.
86
+ #
87
+ attr_accessor :interaction_id
88
+
89
+ # Optional hash of identity properties from the session data inside the
90
+ # payload - see Hoodoo::Logger.
91
+ #
92
+ attr_accessor :identity
93
+
94
+ # Create an instance with options keyed on the attributes defined for
95
+ # the class. Option keys may be Strings or Symbols but must only match
96
+ # defined attribute names.
97
+ #
98
+ def initialize( options = {} )
99
+ options.each do | name, value |
100
+ send( "#{ name }=", value )
179
101
  end
180
102
  end
181
103
 
182
- rescue LoadError # Optional file 'alchemy-amq' is absent
104
+ # Retrieve a simple Hash representation of this instance.
105
+ #
106
+ def to_h
107
+ {
108
+ 'id' => @id,
109
+ 'level' => @level,
110
+ 'component' => @component,
111
+ 'code' => @code,
112
+ 'reported_at' => @reported_at,
113
+
114
+ 'data' => @data,
115
+
116
+ 'interaction_id' => @interaction_id,
117
+ 'caller_id' => @caller_id,
118
+ 'identity' => Hoodoo::Utilities.stringify( @identity )
119
+ }
120
+ end
183
121
  end
184
122
 
185
123
  end