A generic message type for evented systems
- OPTIONAL_FIELDS
An Array of Symbols containing the names of optional fields
- REQUIRED_FIELDS
An Array of Symbols containing the names of required fields
- SIMPLE_TYPES
Object classes which can be serialized as they are (i.e., do not require simplification)
- VALID_FIELDS
An Array of Symbols containing the names of all valid fields
- VALID_TYPE_PATTERN
Validation pattern for the type
- audit_trail R
The message’s audit trail, which is a list of frames from which audit
has been called.
- data R
The payload data associated with the event (a Hash)
- id R
The message’s unique ID
- mtime R
The monotonic time the message was created.
- recast_from RW
The ID of the event this event was recast from, or nil
if it hasn’t been recast
- time R
The time the message was created.
- type RW
The type of event.
Create a new event of the specified type
and with the given payload
.
def initialize( type, fields={} )
raise ArgumentError, "invalid type %p" % [ type ] unless self.class.valid_type?( type )
@type = type
self.log.debug "Fields are: %p" % [ fields ]
@id = fields[ :id ] || Ravn.uuid.generate
@time = fields[ :time ] || Ravn.time
@mtime = fields[ :mtime ] || Ravn.monotonic_time
@data = fields[ :data ] || {}
@recast_from = fields[ :recast_from ]
@audit_trail = []
self.audit {}
end
Return an Array of Symbols that describe which fields are optional for messages.
def self::optional_fields
return OPTIONAL_FIELDS
end
recast( original, new_type ) { |new_instance| ... }
Transform the given original
event into an instance of the receiving class with a new_type
and the equivalent data. Values from the original
‘s data
that correspond to fields in the new type will be converted to fields, and optional fields of the original
that are not present in the new class will be converted to data
values.
def self::recast( original, new_type )
fields = Ravn::DataUtilities.deep_copy( original.fields )
fields[:data] ||= {}
self.log.debug "Recasting fields: %p" % [ fields ] if $VERBOSE
(fields[ :data ].keys & self.valid_fields).each do |field|
self.log.debug "Moving %p from data to a top-level field." % [ field ] if $VERBOSE
value = fields[:data].delete( field )
fields[ field ] ||= value
end
(fields.keys - self.valid_fields).each do |field|
self.log.debug "Moving %p from a top-level field to data." % [ field ] if $VERBOSE
fields[ :data ][ field ] = fields.delete( field )
end
fields[ :recast_from ] = fields.delete( :id )
self.log.debug " fields are now: %p" % [ fields ] if $VERBOSE
new_instance = new( new_type, fields )
yield( new_instance ) if block_given?
new_instance.freeze if original.frozen?
return new_instance
end
Return an Array of Symbols that describe which fields are required for messages.
def self::required_fields
return REQUIRED_FIELDS
end
Return an Array of Symbols that describe which fields are valid for messages.
def self::valid_fields
return self.required_fields | self.optional_fields
end
valid_type?( type_string )
Returns true
if the given type_string
is a valid Helios message type.
def self::valid_type?( type_string )
return VALID_TYPE_PATTERN.match?( type_string )
end
Add a callsite to the message’s audit trail.
def audit( &block )
if block
self.audit_trail << self.current_audit_callsite( block )
else
self.log.warn "Audit with no block!"
end
rescue => err
self.log.error( err )
end
current_audit_callsite( block )
Return a description of the top frame of the current call stack for auditing.
def current_audit_callsite( block )
loc = block.binding
return "%p [%s:%d]" % [ loc.receiver, *loc.source_location ]
end
Return the fields of the message as a Hash.
def fields
return self.optional_fields.merge( self.required_fields )
end
initialize_copy( original )
Copy initializer — copy the internal datastructures too.
def initialize_copy( original )
super
@id = Ravn.uuid.generate
@data = deep_copy( original.data )
end
Return the detail part of the inspect output.
def inspect_details
details = "{%s} id: %s time: %0.3f" % [
self.type,
self.id,
self.time,
]
details << " (recast from %s)" % [ self.recast_from ] if self.recast_from
return details
end
Write a log message containing the Message’s current audit trail.
def log_audit_trail
trail = self.audit_trail.map {|frame| " %s" % [frame] }.join( "\n" )
self.log.debug "Audit trail: \n%s" % [ trail ]
end
Return a Hash of the options fields of the message which have been set.
def optional_fields
return self.class.optional_fields.each_with_object( {} ) do |field, hash|
value = self.public_send( field )
hash[ field ] = simplify( value ) if value
end
end
recast( new_type ) { |new_message| ... }
Return a clone of the receiver, but with a new_type
.
def recast( new_type )
new_message = self.dup
new_message.type = new_type
yield( new_message ) if block_given?
new_message.recast_from = self.id
new_message.freeze if self.frozen?
return new_message
end
Return a Hash of the required fields of the message.
def required_fields
return self.class.required_fields.each_with_object( {} ) do |field, hash|
value = self.public_send( field )
hash[ field ] = simplify( value )
end
end
Return the category
(second segment) part of the message type.
def type_category
match = self.type_match
return match[ :category ]
end
Return the details
(trailing segments) part of the message type as an Array of Strings.
def type_details
match = self.type_match
return match[ :details ]&.split( '.' )[ 1..-1 ]
end
Return the source
(prefix) part of the message type.
def type_source
match = self.type_match
return match[ :source ]
end
Protected Instance Methods
Return a MatchData object from matching the message’s type against the VALID_TYPE_PATTERN
.
def type_match
return self.type.match( VALID_TYPE_PATTERN )
end