activecypher 0.0.0 → 0.3.0
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 +4 -4
- data/lib/active_cypher/associations/collection_proxy.rb +144 -0
- data/lib/active_cypher/associations.rb +537 -0
- data/lib/active_cypher/base.rb +47 -0
- data/lib/active_cypher/bolt/connection.rb +525 -0
- data/lib/active_cypher/bolt/driver.rb +144 -0
- data/lib/active_cypher/bolt/handlers.rb +10 -0
- data/lib/active_cypher/bolt/message_reader.rb +100 -0
- data/lib/active_cypher/bolt/message_writer.rb +53 -0
- data/lib/active_cypher/bolt/messaging.rb +307 -0
- data/lib/active_cypher/bolt/packstream.rb +319 -0
- data/lib/active_cypher/bolt/result.rb +82 -0
- data/lib/active_cypher/bolt/session.rb +201 -0
- data/lib/active_cypher/bolt/transaction.rb +211 -0
- data/lib/active_cypher/bolt/version_encoding.rb +41 -0
- data/lib/active_cypher/bolt.rb +7 -0
- data/lib/active_cypher/connection_adapters/abstract_adapter.rb +75 -0
- data/lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb +178 -0
- data/lib/active_cypher/connection_adapters/memgraph_adapter.rb +44 -0
- data/lib/active_cypher/connection_adapters/neo4j_adapter.rb +58 -0
- data/lib/active_cypher/connection_factory.rb +130 -0
- data/lib/active_cypher/connection_handler.rb +9 -0
- data/lib/active_cypher/connection_pool.rb +123 -0
- data/lib/active_cypher/connection_url_resolver.rb +137 -0
- data/lib/active_cypher/cypher_config.rb +61 -0
- data/lib/active_cypher/generators/install_generator.rb +23 -0
- data/lib/active_cypher/generators/node_generator.rb +32 -0
- data/lib/active_cypher/generators/relationship_generator.rb +33 -0
- data/lib/active_cypher/generators/templates/application_graph_node.rb +5 -0
- data/lib/active_cypher/generators/templates/application_graph_relationship.rb +5 -0
- data/lib/active_cypher/generators/templates/cypher_databases.yml +16 -0
- data/lib/active_cypher/generators/templates/node.rb.erb +10 -0
- data/lib/active_cypher/generators/templates/relationship.rb.erb +11 -0
- data/lib/active_cypher/logging.rb +44 -0
- data/lib/active_cypher/model/abstract.rb +87 -0
- data/lib/active_cypher/model/attributes.rb +24 -0
- data/lib/active_cypher/model/callbacks.rb +44 -0
- data/lib/active_cypher/model/connection_handling.rb +76 -0
- data/lib/active_cypher/model/connection_owner.rb +50 -0
- data/lib/active_cypher/model/core.rb +45 -0
- data/lib/active_cypher/model/countable.rb +30 -0
- data/lib/active_cypher/model/destruction.rb +49 -0
- data/lib/active_cypher/model/inspectable.rb +28 -0
- data/lib/active_cypher/model/persistence.rb +182 -0
- data/lib/active_cypher/model/querying.rb +67 -0
- data/lib/active_cypher/railtie.rb +34 -0
- data/lib/active_cypher/relation.rb +190 -0
- data/lib/active_cypher/relationship.rb +233 -0
- data/lib/active_cypher/runtime_registry.rb +8 -0
- data/lib/active_cypher/scoping.rb +97 -0
- data/lib/active_cypher/utils/logger.rb +100 -0
- data/lib/active_cypher/version.rb +5 -0
- data/lib/activecypher.rb +108 -0
- data/lib/cyrel/call_procedure.rb +29 -0
- data/lib/cyrel/clause/call.rb +46 -0
- data/lib/cyrel/clause/call_subquery.rb +40 -0
- data/lib/cyrel/clause/create.rb +33 -0
- data/lib/cyrel/clause/delete.rb +41 -0
- data/lib/cyrel/clause/limit.rb +33 -0
- data/lib/cyrel/clause/match.rb +40 -0
- data/lib/cyrel/clause/merge.rb +34 -0
- data/lib/cyrel/clause/order_by.rb +78 -0
- data/lib/cyrel/clause/remove.rb +75 -0
- data/lib/cyrel/clause/return.rb +90 -0
- data/lib/cyrel/clause/set.rb +97 -0
- data/lib/cyrel/clause/skip.rb +34 -0
- data/lib/cyrel/clause/where.rb +42 -0
- data/lib/cyrel/clause/with.rb +94 -0
- data/lib/cyrel/clause.rb +25 -0
- data/lib/cyrel/direction.rb +18 -0
- data/lib/cyrel/expression/alias.rb +27 -0
- data/lib/cyrel/expression/base.rb +101 -0
- data/lib/cyrel/expression/case.rb +45 -0
- data/lib/cyrel/expression/comparison.rb +60 -0
- data/lib/cyrel/expression/exists.rb +42 -0
- data/lib/cyrel/expression/function_call.rb +57 -0
- data/lib/cyrel/expression/literal.rb +33 -0
- data/lib/cyrel/expression/logical.rb +38 -0
- data/lib/cyrel/expression/operator.rb +27 -0
- data/lib/cyrel/expression/pattern_comprehension.rb +44 -0
- data/lib/cyrel/expression/property_access.rb +25 -0
- data/lib/cyrel/expression.rb +56 -0
- data/lib/cyrel/functions.rb +116 -0
- data/lib/cyrel/node.rb +397 -0
- data/lib/cyrel/parameterizable.rb +20 -0
- data/lib/cyrel/pattern/node.rb +66 -0
- data/lib/cyrel/pattern/path.rb +41 -0
- data/lib/cyrel/pattern/relationship.rb +74 -0
- data/lib/cyrel/pattern.rb +8 -0
- data/lib/cyrel/query.rb +497 -0
- data/lib/cyrel/return_only.rb +26 -0
- data/lib/cyrel/types/hash_type.rb +22 -0
- data/lib/cyrel/types/symbol_type.rb +13 -0
- data/lib/cyrel.rb +72 -0
- data/sig/activecypher.rbs +4 -0
- metadata +172 -10
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module ActiveCypher
|
6
|
+
module Bolt
|
7
|
+
# Handles decoding chunked Packstream data into Bolt messages for Bolt v5.x.
|
8
|
+
class MessageReader
|
9
|
+
MAX_CHUNK_SIZE = 65_535 # Maximum size for a single chunk (unsigned 16-bit int)
|
10
|
+
READ_TIMEOUT = 15 # Seconds to wait for a read operation
|
11
|
+
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
@buffer = StringIO.new(+'', 'rb+') # Internal buffer for chunked data
|
15
|
+
end
|
16
|
+
|
17
|
+
# Reads and decodes the next Bolt message from the stream.
|
18
|
+
# Handles Bolt's chunking mechanism.
|
19
|
+
#
|
20
|
+
# @return [Messaging::Message] The decoded message object.
|
21
|
+
# @raise [ProtocolError] If decoding fails or an unknown message type is received.
|
22
|
+
# @raise [ConnectionError] If the connection is lost during reading.
|
23
|
+
def read_message
|
24
|
+
message_bytes = read_message_chunks
|
25
|
+
return nil if message_bytes.nil? || message_bytes.empty?
|
26
|
+
|
27
|
+
unpacker = Packstream::Unpacker.new(StringIO.new(message_bytes, 'rb'))
|
28
|
+
signature, fields = unpacker.unpack
|
29
|
+
|
30
|
+
klass = find_message_class(signature) or
|
31
|
+
raise ProtocolError, "Unknown message signature 0x#{signature.to_s(16)}"
|
32
|
+
|
33
|
+
klass.new(*fields)
|
34
|
+
rescue EOFError => e
|
35
|
+
raise ConnectionError, "Connection closed while reading message: #{e.message}"
|
36
|
+
rescue ConnectionError
|
37
|
+
raise
|
38
|
+
rescue StandardError => e
|
39
|
+
raise ProtocolError, "Failed to decode message: #{e.class} - #{e.message}"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def read_raw_from_io(n)
|
45
|
+
Async::Task.current.with_timeout(READ_TIMEOUT) do
|
46
|
+
data = @io.read_exactly(n)
|
47
|
+
raise EOFError, 'Connection closed during read' unless data
|
48
|
+
|
49
|
+
return data
|
50
|
+
end
|
51
|
+
rescue Async::TimeoutError
|
52
|
+
raise ConnectionError, "Read operation timed out after #{READ_TIMEOUT}s"
|
53
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, IOError, EOFError => e
|
54
|
+
raise ConnectionError, "Connection lost: #{e.message}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reads message chunks from the IO stream until a zero chunk is found.
|
58
|
+
# @return [String] The concatenated bytes of the message.
|
59
|
+
def read_message_chunks
|
60
|
+
@buffer.rewind
|
61
|
+
@buffer.truncate(0)
|
62
|
+
|
63
|
+
loop do
|
64
|
+
chunk_size_bytes = read_raw_from_io(2)
|
65
|
+
chunk_size = chunk_size_bytes.unpack1('n')
|
66
|
+
|
67
|
+
break if chunk_size.zero?
|
68
|
+
raise ProtocolError, "Chunk too large (#{chunk_size})" if chunk_size > MAX_CHUNK_SIZE
|
69
|
+
|
70
|
+
@buffer.write(read_raw_from_io(chunk_size))
|
71
|
+
end
|
72
|
+
|
73
|
+
@buffer.string
|
74
|
+
end
|
75
|
+
|
76
|
+
# Finds the message class corresponding to a signature byte.
|
77
|
+
def find_message_class(signature)
|
78
|
+
case signature
|
79
|
+
when Messaging::Success::SIGNATURE then Messaging::Success
|
80
|
+
when Messaging::Failure::SIGNATURE then Messaging::Failure
|
81
|
+
when Messaging::Ignored::SIGNATURE then Messaging::Ignored
|
82
|
+
when Messaging::Hello::SIGNATURE then Messaging::Hello # Technically shouldn't receive HELLO, its old stuff
|
83
|
+
when Messaging::Run::SIGNATURE then Messaging::Run # Shouldn't receive RUN either
|
84
|
+
when Messaging::Pull::SIGNATURE then Messaging::Pull # Shouldn't receive PULL
|
85
|
+
when Messaging::Discard::SIGNATURE then Messaging::Discard # Shouldn't receive DISCARD
|
86
|
+
when Messaging::Record::SIGNATURE then Messaging::Record
|
87
|
+
when Messaging::Begin::SIGNATURE then Messaging::Begin # Shouldn't receive BEGIN
|
88
|
+
when Messaging::Commit::SIGNATURE then Messaging::Commit # Shouldn't receive COMMIT
|
89
|
+
when Messaging::Rollback::SIGNATURE then Messaging::Rollback # Shouldn't receive ROLLBACK
|
90
|
+
when Messaging::Goodbye::SIGNATURE then Messaging::Goodbye
|
91
|
+
when Messaging::Logon::SIGNATURE then Messaging::Logon
|
92
|
+
when Messaging::Logoff::SIGNATURE then Messaging::Logoff
|
93
|
+
when Messaging::Route::SIGNATURE then Messaging::Route
|
94
|
+
when Messaging::Reset::SIGNATURE then Messaging::Reset
|
95
|
+
when Messaging::Telemetry::SIGNATURE then Messaging::Telemetry
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveCypher
|
4
|
+
module Bolt
|
5
|
+
# Handles encoding Bolt messages into Packstream format for Bolt v5.0
|
6
|
+
class MessageWriter
|
7
|
+
# Structure Markers
|
8
|
+
TINY_STRUCT_MARKER_BASE = 0xB0
|
9
|
+
STRUCT_8_MARKER = 0xDC
|
10
|
+
STRUCT_16_MARKER = 0xDD
|
11
|
+
# STRUCT_32_MARKER = 0xDE # Not implementing 32-bit sizes for now
|
12
|
+
|
13
|
+
def initialize(io)
|
14
|
+
@packer = Packstream::Packer.new(io)
|
15
|
+
@io = io # Keep a reference for direct writing if needed
|
16
|
+
end
|
17
|
+
|
18
|
+
# Encodes and writes a Bolt message to the underlying IO stream.
|
19
|
+
# @param message [Messaging::Message] The message object to write.
|
20
|
+
def write(message)
|
21
|
+
# Bolt 4.3 requires different chunking
|
22
|
+
size = message.fields.size
|
23
|
+
|
24
|
+
# Write structure header with size and signature
|
25
|
+
if size < 16
|
26
|
+
write_marker([TINY_STRUCT_MARKER_BASE | size].pack('C'))
|
27
|
+
else
|
28
|
+
write_marker([STRUCT_8_MARKER, size].pack('CC'))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Write signature
|
32
|
+
write_marker([message.signature].pack('C'))
|
33
|
+
|
34
|
+
# Pack fields with careful handling of nils
|
35
|
+
message.fields.each do |field|
|
36
|
+
if field.nil?
|
37
|
+
@packer.pack(nil)
|
38
|
+
else
|
39
|
+
@packer.pack(field)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Method removed as it's not used anymore - we're using the new write method above
|
47
|
+
|
48
|
+
def write_marker(marker_bytes)
|
49
|
+
@io.write(marker_bytes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
|
6
|
+
module ActiveCypher
|
7
|
+
module Bolt
|
8
|
+
# @!parse
|
9
|
+
# # Messaging: Because every protocol needs a registry, and every registry needs a protocol.
|
10
|
+
module Messaging
|
11
|
+
# Internal registry: signature byte → Message subclass
|
12
|
+
@registry = {}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# For introspection
|
16
|
+
def registry
|
17
|
+
@registry.dup
|
18
|
+
end
|
19
|
+
|
20
|
+
# Lookup-and-instantiate based on signature + raw fields
|
21
|
+
#
|
22
|
+
# @param signature [Integer] the Bolt message signature
|
23
|
+
# @param fields [Array] the decoded fields for this message
|
24
|
+
# @return [Message] instance of the right subclass, or generic Message
|
25
|
+
def for_signature(signature, *fields)
|
26
|
+
if (klass = @registry[signature])
|
27
|
+
klass.new(*fields)
|
28
|
+
else
|
29
|
+
Message.new(signature, fields)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Register a subclass if it defines SIGNATURE
|
34
|
+
def register(subclass)
|
35
|
+
return unless subclass.const_defined?(:SIGNATURE)
|
36
|
+
|
37
|
+
sig = subclass.const_get(:SIGNATURE)
|
38
|
+
@registry[sig] = subclass
|
39
|
+
end
|
40
|
+
|
41
|
+
# Normalize any metadata or parameters map
|
42
|
+
def normalize_map(map)
|
43
|
+
(map || {}).with_indifferent_access
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Base class — automatically registers subclasses with SIGNATURE
|
48
|
+
# Because inheritance hierarchies are the only thing deeper than this protocol.
|
49
|
+
class Message
|
50
|
+
attr_reader :signature, :fields
|
51
|
+
|
52
|
+
def initialize(signature, fields)
|
53
|
+
@signature = signature
|
54
|
+
@fields = fields
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
other.class == self.class &&
|
59
|
+
other.signature == signature &&
|
60
|
+
other.fields == fields
|
61
|
+
end
|
62
|
+
alias eql? ==
|
63
|
+
|
64
|
+
def self.inherited(subclass)
|
65
|
+
Messaging.register(subclass)
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The HELLO message. Because every protocol needs to start with a greeting before the disappointment.
|
71
|
+
class Hello < Message
|
72
|
+
SIGNATURE = 0x01
|
73
|
+
|
74
|
+
def initialize(metadata)
|
75
|
+
meta = Messaging.normalize_map(metadata)
|
76
|
+
super(SIGNATURE, [meta])
|
77
|
+
end
|
78
|
+
|
79
|
+
def metadata
|
80
|
+
fields.first
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# The GOODBYE message. For when you've had enough of this session, or life.
|
85
|
+
class Goodbye < Message
|
86
|
+
SIGNATURE = 0x02
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
super(SIGNATURE, [])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# The RESET message. Because sometimes you just want to pretend nothing ever happened.
|
94
|
+
class Reset < Message
|
95
|
+
SIGNATURE = 0x0F
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
super(SIGNATURE, [])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# The RUN message. Because what else would you do with a database connection?
|
103
|
+
class Run < Message
|
104
|
+
SIGNATURE = 0x10
|
105
|
+
|
106
|
+
# metadata may include bookmarks, tx_timeout, tx_metadata, mode, db
|
107
|
+
def initialize(query, parameters, metadata = {})
|
108
|
+
meta = Messaging.normalize_map(metadata)
|
109
|
+
params = Messaging.normalize_map(parameters)
|
110
|
+
|
111
|
+
# Neo4j mode normalization: single-char 'r' or 'w'
|
112
|
+
meta['mode'] = meta['mode'][0] if meta['mode'].is_a?(String) && meta['mode'].length > 1
|
113
|
+
|
114
|
+
super(SIGNATURE, [query, params, meta])
|
115
|
+
end
|
116
|
+
|
117
|
+
def query = fields[0]
|
118
|
+
def parameters = fields[1]
|
119
|
+
def metadata = fields[2]
|
120
|
+
end
|
121
|
+
|
122
|
+
# The BEGIN message. Because transactions are just promises waiting to be broken.
|
123
|
+
class Begin < Message
|
124
|
+
SIGNATURE = 0x11
|
125
|
+
|
126
|
+
# metadata may include mode, db, tx_metadata, etc.
|
127
|
+
def initialize(metadata = {})
|
128
|
+
meta = Messaging.normalize_map(metadata)
|
129
|
+
|
130
|
+
# Never set db to neo4j for memgraph
|
131
|
+
if meta['adapter'] == 'memgraph'
|
132
|
+
# For Memgraph, remove db key entirely if present
|
133
|
+
meta.delete('db')
|
134
|
+
elsif meta['mode'].is_a?(String) && meta['mode'].length == 1
|
135
|
+
# This is for Neo4j only
|
136
|
+
meta['db'] ||= 'neo4j'
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set default mode if not present
|
140
|
+
meta['mode'] ||= 'write'
|
141
|
+
|
142
|
+
super(SIGNATURE, [meta])
|
143
|
+
end
|
144
|
+
|
145
|
+
def metadata
|
146
|
+
fields.first
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# The COMMIT message. For when you want to pretend your changes are permanent.
|
151
|
+
class Commit < Message
|
152
|
+
SIGNATURE = 0x12
|
153
|
+
|
154
|
+
def initialize
|
155
|
+
super(SIGNATURE, [])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# The ROLLBACK message. Because sometimes you just want to undo your mistakes.
|
160
|
+
class Rollback < Message
|
161
|
+
SIGNATURE = 0x13
|
162
|
+
|
163
|
+
def initialize
|
164
|
+
super(SIGNATURE, [])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# The DISCARD message. For when you want to throw away results, or your hopes.
|
169
|
+
class Discard < Message
|
170
|
+
SIGNATURE = 0x2F
|
171
|
+
|
172
|
+
# metadata: { n: <N>, qid: <QID> }, where n = -1 means all
|
173
|
+
def initialize(metadata)
|
174
|
+
meta = Messaging.normalize_map(metadata)
|
175
|
+
super(SIGNATURE, [meta])
|
176
|
+
end
|
177
|
+
|
178
|
+
def metadata = fields.first
|
179
|
+
def n = metadata[:n] || metadata['n']
|
180
|
+
def qid = metadata[:qid] || metadata['qid']
|
181
|
+
end
|
182
|
+
|
183
|
+
# The PULL message. Because sometimes you just want to see what you got.
|
184
|
+
class Pull < Message
|
185
|
+
SIGNATURE = 0x3F
|
186
|
+
|
187
|
+
def initialize(metadata)
|
188
|
+
meta = Messaging.normalize_map(metadata)
|
189
|
+
super(SIGNATURE, [meta])
|
190
|
+
end
|
191
|
+
|
192
|
+
def metadata
|
193
|
+
fields.first
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# The ROUTE message. For when you want to pretend you have control over routing.
|
198
|
+
class Route < Message
|
199
|
+
SIGNATURE = 0x66
|
200
|
+
|
201
|
+
def initialize(metadata)
|
202
|
+
meta = Messaging.normalize_map(metadata)
|
203
|
+
super(SIGNATURE, [meta])
|
204
|
+
end
|
205
|
+
|
206
|
+
def metadata
|
207
|
+
fields.first
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# The LOGON message. Because authentication is just another chance to be rejected.
|
212
|
+
class Logon < Message
|
213
|
+
SIGNATURE = 0x6A
|
214
|
+
|
215
|
+
def initialize(metadata)
|
216
|
+
meta = Messaging.normalize_map(metadata)
|
217
|
+
super(SIGNATURE, [meta])
|
218
|
+
end
|
219
|
+
|
220
|
+
def metadata
|
221
|
+
fields.first
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# The LOGOFF message. For when you want to leave quietly, without making a scene.
|
226
|
+
class Logoff < Message
|
227
|
+
SIGNATURE = 0x6B
|
228
|
+
|
229
|
+
def initialize
|
230
|
+
super(SIGNATURE, [])
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# The TELEMETRY message. Because someone, somewhere, cares about your metrics. Probably.
|
235
|
+
class Telemetry < Message
|
236
|
+
SIGNATURE = 0x54
|
237
|
+
|
238
|
+
def initialize(metadata)
|
239
|
+
meta = Messaging.normalize_map(metadata)
|
240
|
+
super(SIGNATURE, [meta])
|
241
|
+
end
|
242
|
+
|
243
|
+
def metadata
|
244
|
+
fields.first
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# The SUCCESS message. The rarest of all Bolt messages.
|
249
|
+
class Success < Message
|
250
|
+
SIGNATURE = 0x70
|
251
|
+
|
252
|
+
def initialize(metadata)
|
253
|
+
meta = Messaging.normalize_map(metadata)
|
254
|
+
super(SIGNATURE, [meta])
|
255
|
+
end
|
256
|
+
|
257
|
+
def metadata
|
258
|
+
fields.first
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# The RECORD message. For when you actually get data back, against all odds.
|
263
|
+
class Record < Message
|
264
|
+
SIGNATURE = 0x71
|
265
|
+
|
266
|
+
def initialize(values)
|
267
|
+
super(SIGNATURE, [values])
|
268
|
+
end
|
269
|
+
|
270
|
+
def values
|
271
|
+
fields.first
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# The IGNORED message. For when the server just can't be bothered.
|
276
|
+
class Ignored < Message
|
277
|
+
SIGNATURE = 0x7E
|
278
|
+
|
279
|
+
def initialize
|
280
|
+
super(SIGNATURE, [])
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# The FAILURE message. The most honest message in the protocol.
|
285
|
+
class Failure < Message
|
286
|
+
SIGNATURE = 0x7F
|
287
|
+
|
288
|
+
def initialize(metadata)
|
289
|
+
meta = Messaging.normalize_map(metadata)
|
290
|
+
super(SIGNATURE, [meta])
|
291
|
+
end
|
292
|
+
|
293
|
+
def metadata
|
294
|
+
fields.first
|
295
|
+
end
|
296
|
+
|
297
|
+
def code
|
298
|
+
metadata['code']
|
299
|
+
end
|
300
|
+
|
301
|
+
def message
|
302
|
+
metadata['message']
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|