rocketamf_pure 1.0.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.
- data/README.rdoc +47 -0
- data/Rakefile +9 -0
- data/benchmark.rb +73 -0
- data/lib/rocketamf.rb +212 -0
- data/lib/rocketamf/class_mapping.rb +237 -0
- data/lib/rocketamf/constants.rb +46 -0
- data/lib/rocketamf/ext.rb +28 -0
- data/lib/rocketamf/extensions.rb +22 -0
- data/lib/rocketamf/pure.rb +24 -0
- data/lib/rocketamf/pure/deserializer.rb +417 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +117 -0
- data/lib/rocketamf/pure/serializer.rb +474 -0
- data/lib/rocketamf/remoting.rb +196 -0
- data/lib/rocketamf/values/messages.rb +212 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/lib/rocketamf_pure.rb +1 -0
- data/spec/class_mapping_spec.rb +110 -0
- data/spec/deserializer_spec.rb +423 -0
- data/spec/fast_class_mapping_spec.rb +144 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
- data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
- data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-float.bin +0 -0
- data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
- data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/blaze-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/flex-request.bin +0 -0
- data/spec/fixtures/request/multiple-simple-request.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-request.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/messages_spec.rb +39 -0
- data/spec/remoting_spec.rb +196 -0
- data/spec/serializer_spec.rb +503 -0
- data/spec/spec_helper.rb +55 -0
- metadata +148 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
# Container for the AMF request/response.
|
3
|
+
class Envelope
|
4
|
+
attr_reader :amf_version, :headers, :messages
|
5
|
+
|
6
|
+
def initialize props={}
|
7
|
+
@amf_version = props[:amf_version] || 0
|
8
|
+
@headers = props[:headers] || {}
|
9
|
+
@messages = props[:messages] || []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Populates the envelope from the given stream or string using the given
|
13
|
+
# class mapper, or creates a new one. Returns self for easy chaining.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# req = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
|
18
|
+
#--
|
19
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
|
20
|
+
def populate_from_stream stream, class_mapper=nil
|
21
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates the appropriate message and adds it to <tt>messages</tt> to call
|
25
|
+
# the given target using the standard (old) remoting APIs. You can call multiple
|
26
|
+
# targets in the same request, unlike with the flex remotings APIs.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# req = RocketAMF::Envelope.new
|
31
|
+
# req.call 'test', "arg_1", ["args", "args"]
|
32
|
+
# req.call 'Controller.action'
|
33
|
+
def call target, *args
|
34
|
+
raise "Cannot use different call types" unless @call_type.nil? || @call_type == :simple
|
35
|
+
@call_type = :simple
|
36
|
+
|
37
|
+
msg_num = messages.length+1
|
38
|
+
@messages << RocketAMF::Message.new(target, "/#{msg_num}", args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates the appropriate message and adds it to <tt>messages</tt> using the
|
42
|
+
# new flex (RemoteObject) remoting APIs. You can only make one flex remoting
|
43
|
+
# call per envelope, and the AMF version must be set to 3.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# req = RocketAMF::Envelope.new :amf_version => 3
|
48
|
+
# req.call_flex 'Controller.action', "arg_1", ["args", "args"]
|
49
|
+
def call_flex target, *args
|
50
|
+
raise "Can only call one flex target per request" if @call_type == :flex
|
51
|
+
raise "Cannot use different call types" if @call_type == :simple
|
52
|
+
raise "Cannot use flex remoting calls with AMF0" if @amf_version != 3
|
53
|
+
@call_type = :flex
|
54
|
+
|
55
|
+
flex_msg = RocketAMF::Values::RemotingMessage.new
|
56
|
+
target_parts = target.split(".")
|
57
|
+
flex_msg.operation = target_parts.pop # Use pop so that a missing source is possible without issues
|
58
|
+
flex_msg.source = target_parts.pop
|
59
|
+
flex_msg.body = args
|
60
|
+
@messages << RocketAMF::Message.new('null', '/2', flex_msg) # /2 because it always sends a command message before
|
61
|
+
end
|
62
|
+
|
63
|
+
# Serializes the envelope to a string using the given class mapper, or creates
|
64
|
+
# a new one, and returns the result
|
65
|
+
#--
|
66
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
|
67
|
+
def serialize class_mapper=nil
|
68
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
69
|
+
end
|
70
|
+
|
71
|
+
# Builds response from the request, iterating over each method call and using
|
72
|
+
# the return value as the method call's return value. Marks as envelope as
|
73
|
+
# constructed after running.
|
74
|
+
#--
|
75
|
+
# Iterate over all the sent messages. If they're somthing we can handle, like
|
76
|
+
# a command message, then simply add the response message ourselves. If it's
|
77
|
+
# a method call, then call the block with the method and args, catching errors
|
78
|
+
# for handling. Then create the appropriate response message using the return
|
79
|
+
# value of the block as the return value for the method call.
|
80
|
+
def each_method_call request, &block
|
81
|
+
raise 'Response already constructed' if @constructed
|
82
|
+
|
83
|
+
# Set version from response
|
84
|
+
# Can't just copy version because FMS sends version as 1
|
85
|
+
@amf_version = request.amf_version == 3 ? 3 : 0
|
86
|
+
|
87
|
+
request.messages.each do |m|
|
88
|
+
# What's the request body?
|
89
|
+
case m.data
|
90
|
+
when Values::CommandMessage
|
91
|
+
# Pings should be responded to with an AcknowledgeMessage built using the ping
|
92
|
+
# Everything else is unsupported
|
93
|
+
command_msg = m.data
|
94
|
+
if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION
|
95
|
+
response_value = Values::AcknowledgeMessage.new(command_msg)
|
96
|
+
else
|
97
|
+
e = Exception.new("CommandMessage #{command_msg.operation} not implemented")
|
98
|
+
e.set_backtrace ["RocketAMF::Envelope each_method_call"]
|
99
|
+
response_value = Values::ErrorMessage.new(command_msg, e)
|
100
|
+
end
|
101
|
+
when Values::RemotingMessage
|
102
|
+
# Using RemoteObject style message calls
|
103
|
+
remoting_msg = m.data
|
104
|
+
acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg)
|
105
|
+
method_base = remoting_msg.source.to_s.empty? ? '' : remoting_msg.source+'.'
|
106
|
+
body = dispatch_call :method => method_base+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
|
107
|
+
|
108
|
+
# Response should be the bare ErrorMessage if there was an error
|
109
|
+
if body.is_a?(Values::ErrorMessage)
|
110
|
+
response_value = body
|
111
|
+
else
|
112
|
+
acknowledge_msg.body = body
|
113
|
+
response_value = acknowledge_msg
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# Standard response message
|
117
|
+
response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
|
118
|
+
end
|
119
|
+
|
120
|
+
target_uri = m.response_uri
|
121
|
+
target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult'
|
122
|
+
@messages << ::RocketAMF::Message.new(target_uri, '', response_value)
|
123
|
+
end
|
124
|
+
|
125
|
+
@constructed = true
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the result of a response envelope, or an array of results if there
|
129
|
+
# are multiple action call messages. It automatically unwraps flex-style
|
130
|
+
# RemoteObject response messages, where the response result is inside a
|
131
|
+
# RocketAMF::Values::AcknowledgeMessage.
|
132
|
+
#
|
133
|
+
# Example:
|
134
|
+
#
|
135
|
+
# req = RocketAMF::Envelope.new
|
136
|
+
# req.call('TestController.test', 'first_arg', 'second_arg')
|
137
|
+
# res = RocketAMF::Envelope.new
|
138
|
+
# res.each_method_call req do |method, args|
|
139
|
+
# ['a', 'b']
|
140
|
+
# end
|
141
|
+
# res.result #=> ['a', 'b']
|
142
|
+
def result
|
143
|
+
results = []
|
144
|
+
messages.each do |msg|
|
145
|
+
if msg.data.is_a?(Values::AcknowledgeMessage)
|
146
|
+
results << msg.data.body
|
147
|
+
else
|
148
|
+
results << msg.data
|
149
|
+
end
|
150
|
+
end
|
151
|
+
results.length > 1 ? results : results[0]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Whether or not the response has been constructed. Can be used to prevent
|
155
|
+
# serialization when no processing has taken place.
|
156
|
+
def constructed?
|
157
|
+
@constructed
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return the serialized envelope as a string
|
161
|
+
def to_s
|
162
|
+
serialize
|
163
|
+
end
|
164
|
+
|
165
|
+
def dispatch_call p #:nodoc:
|
166
|
+
begin
|
167
|
+
p[:block].call(p[:method], p[:args])
|
168
|
+
rescue Exception => e
|
169
|
+
# Create ErrorMessage object using the source message as the base
|
170
|
+
Values::ErrorMessage.new(p[:source], e)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# RocketAMF::Envelope header
|
176
|
+
class Header
|
177
|
+
attr_accessor :name, :must_understand, :data
|
178
|
+
|
179
|
+
def initialize name, must_understand, data
|
180
|
+
@name = name
|
181
|
+
@must_understand = must_understand
|
182
|
+
@data = data
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# RocketAMF::Envelope message
|
187
|
+
class Message
|
188
|
+
attr_accessor :target_uri, :response_uri, :data
|
189
|
+
|
190
|
+
def initialize target_uri, response_uri, data
|
191
|
+
@target_uri = target_uri
|
192
|
+
@response_uri = response_uri
|
193
|
+
@data = data
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Base class for all special AS3 response messages. Maps to
|
4
|
+
# <tt>flex.messaging.messages.AbstractMessage</tt>.
|
5
|
+
class AbstractMessage
|
6
|
+
EXTERNALIZABLE_FIELDS = [
|
7
|
+
%w[ body clientId destination headers messageId timestamp timeToLive ],
|
8
|
+
%w[ clientIdBytes messageIdBytes ]
|
9
|
+
]
|
10
|
+
attr_accessor :clientId
|
11
|
+
attr_accessor :destination
|
12
|
+
attr_accessor :messageId
|
13
|
+
attr_accessor :timestamp
|
14
|
+
attr_accessor :timeToLive
|
15
|
+
attr_accessor :headers
|
16
|
+
attr_accessor :body
|
17
|
+
|
18
|
+
def clientIdBytes= bytes
|
19
|
+
@clientId = pretty_uuid(bytes) unless bytes.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def messageIdBytes= bytes
|
23
|
+
@messageId = pretty_uuid(bytes) unless bytes.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_external des
|
27
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def rand_uuid
|
32
|
+
[8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def rand_hex_3(l)
|
36
|
+
"%0#{l}x" % rand(1 << l*4)
|
37
|
+
end
|
38
|
+
|
39
|
+
def pretty_uuid bytes
|
40
|
+
"%08x-%04x-%04x-%04x-%08x%04x" % bytes.string.unpack("NnnnNn")
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_external_fields des, fields
|
44
|
+
# Read flags
|
45
|
+
flags = []
|
46
|
+
loop do
|
47
|
+
flags << des.source.read(1).unpack('C').first
|
48
|
+
break if flags.last < 128
|
49
|
+
end
|
50
|
+
|
51
|
+
# Read fields and any remaining unmapped fields in a byte-set
|
52
|
+
fields.each_with_index do |list, i|
|
53
|
+
list.each_with_index do |name, j|
|
54
|
+
if flags[i] & 2**j != 0
|
55
|
+
send("#{name}=", des.read_object)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Read remaining flags even though we don't recognize them
|
60
|
+
# Zero out high bit, as it's the has-next-field marker
|
61
|
+
f = (flags[i] & ~128) >> list.length
|
62
|
+
while f > 0
|
63
|
+
des.read_object if (f & 1) != 0
|
64
|
+
f >>= 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
|
71
|
+
class RemotingMessage < AbstractMessage
|
72
|
+
# The name of the service to be called including package name
|
73
|
+
attr_accessor :source
|
74
|
+
|
75
|
+
# The name of the method to be called
|
76
|
+
attr_accessor :operation
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@clientId = nil
|
80
|
+
@destination = nil
|
81
|
+
@messageId = rand_uuid
|
82
|
+
@timestamp = Time.new.to_i*100
|
83
|
+
@timeToLive = 0
|
84
|
+
@headers = {}
|
85
|
+
@body = nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
|
90
|
+
class AsyncMessage < AbstractMessage
|
91
|
+
EXTERNALIZABLE_FIELDS = [
|
92
|
+
%w[ correlationId correlationIdBytes]
|
93
|
+
]
|
94
|
+
attr_accessor :correlationId
|
95
|
+
|
96
|
+
def correlationIdBytes= bytes
|
97
|
+
@correlationId = pretty_uuid(bytes) unless bytes.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_external des
|
101
|
+
super des
|
102
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class AsyncMessageExt < AsyncMessage #:nodoc:
|
107
|
+
end
|
108
|
+
|
109
|
+
# Maps to <tt>flex.messaging.messages.CommandMessage</tt>
|
110
|
+
class CommandMessage < AsyncMessage
|
111
|
+
SUBSCRIBE_OPERATION = 0
|
112
|
+
UNSUSBSCRIBE_OPERATION = 1
|
113
|
+
POLL_OPERATION = 2
|
114
|
+
CLIENT_SYNC_OPERATION = 4
|
115
|
+
CLIENT_PING_OPERATION = 5
|
116
|
+
CLUSTER_REQUEST_OPERATION = 7
|
117
|
+
LOGIN_OPERATION = 8
|
118
|
+
LOGOUT_OPERATION = 9
|
119
|
+
SESSION_INVALIDATE_OPERATION = 10
|
120
|
+
MULTI_SUBSCRIBE_OPERATION = 11
|
121
|
+
DISCONNECT_OPERATION = 12
|
122
|
+
UNKNOWN_OPERATION = 10000
|
123
|
+
|
124
|
+
EXTERNALIZABLE_FIELDS = [
|
125
|
+
%w[ operation ]
|
126
|
+
]
|
127
|
+
attr_accessor :operation
|
128
|
+
|
129
|
+
def initialize
|
130
|
+
@operation = UNKNOWN_OPERATION
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_external des
|
134
|
+
super des
|
135
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class CommandMessageExt < CommandMessage #:nodoc:
|
140
|
+
end
|
141
|
+
|
142
|
+
# Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
|
143
|
+
class AcknowledgeMessage < AsyncMessage
|
144
|
+
EXTERNALIZABLE_FIELDS = [[]]
|
145
|
+
|
146
|
+
def initialize message=nil
|
147
|
+
@clientId = rand_uuid
|
148
|
+
@destination = nil
|
149
|
+
@messageId = rand_uuid
|
150
|
+
@timestamp = Time.new.to_i*100
|
151
|
+
@timeToLive = 0
|
152
|
+
@headers = {}
|
153
|
+
@body = nil
|
154
|
+
|
155
|
+
if message.is_a?(AbstractMessage)
|
156
|
+
@correlationId = message.messageId
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_external des
|
161
|
+
super des
|
162
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class AcknowledgeMessageExt < AcknowledgeMessage #:nodoc:
|
167
|
+
end
|
168
|
+
|
169
|
+
# Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
|
170
|
+
class ErrorMessage < AcknowledgeMessage
|
171
|
+
# Extended data that will facilitate custom error processing on the client
|
172
|
+
attr_accessor :extendedData
|
173
|
+
|
174
|
+
# The fault code for the error, which defaults to the class name of the
|
175
|
+
# causing exception
|
176
|
+
attr_accessor :faultCode
|
177
|
+
|
178
|
+
# Detailed description of what caused the error
|
179
|
+
attr_accessor :faultDetail
|
180
|
+
|
181
|
+
# A simple description of the error
|
182
|
+
attr_accessor :faultString
|
183
|
+
|
184
|
+
# Optional "root cause" of the error
|
185
|
+
attr_accessor :rootCause
|
186
|
+
|
187
|
+
def initialize message=nil, exception=nil
|
188
|
+
super message
|
189
|
+
|
190
|
+
unless exception.nil?
|
191
|
+
@e = exception
|
192
|
+
@faultCode = @e.class.name
|
193
|
+
@faultDetail = @e.backtrace.join("\n")
|
194
|
+
@faultString = @e.message
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def encode_amf serializer
|
199
|
+
if serializer.version == 0
|
200
|
+
data = {
|
201
|
+
:faultCode => @faultCode,
|
202
|
+
:faultDetail => @faultDetail,
|
203
|
+
:faultString => @faultString
|
204
|
+
}
|
205
|
+
serializer.write_object(data)
|
206
|
+
else
|
207
|
+
serializer.write_object(self)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Hash-like object that can store a type string. Used to preserve type information
|
4
|
+
# for unmapped objects after deserialization.
|
5
|
+
class TypedHash < Hash
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize type
|
9
|
+
@type = type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "rocketamf"
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "spec_helper.rb"
|
2
|
+
|
3
|
+
describe RocketAMF::ClassMapping do
|
4
|
+
before :each do
|
5
|
+
RocketAMF::ClassMapping.reset
|
6
|
+
RocketAMF::ClassMapping.define do |m|
|
7
|
+
m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
|
8
|
+
end
|
9
|
+
@mapper = RocketAMF::ClassMapping.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "class name mapping" do
|
13
|
+
it "should allow resetting of mappings back to defaults" do
|
14
|
+
@mapper.get_as_class_name('ClassMappingTest').should_not be_nil
|
15
|
+
RocketAMF::ClassMapping.reset
|
16
|
+
@mapper = RocketAMF::ClassMapping.new
|
17
|
+
@mapper.get_as_class_name('ClassMappingTest').should be_nil
|
18
|
+
@mapper.get_as_class_name('RocketAMF::Values::AcknowledgeMessage').should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return AS class name for ruby objects" do
|
22
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
|
23
|
+
@mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
|
24
|
+
@mapper.get_as_class_name(RocketAMF::Values::TypedHash.new('ClassMappingTest')).should == 'ASClass'
|
25
|
+
@mapper.get_as_class_name('BadClass').should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should instantiate a ruby class" do
|
29
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should properly instantiate namespaced classes" do
|
33
|
+
RocketAMF::ClassMapping.mappings.map :as => 'ASClass', :ruby => 'ANamespace::TestRubyClass'
|
34
|
+
@mapper = RocketAMF::ClassMapping.new
|
35
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ANamespace::TestRubyClass)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return a hash with original type if not mapped" do
|
39
|
+
obj = @mapper.get_ruby_obj('UnmappedClass')
|
40
|
+
obj.should be_a(RocketAMF::Values::TypedHash)
|
41
|
+
obj.type.should == 'UnmappedClass'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should map special classes from AS by default" do
|
45
|
+
as_classes = [
|
46
|
+
'flex.messaging.messages.AcknowledgeMessage',
|
47
|
+
'flex.messaging.messages.CommandMessage',
|
48
|
+
'flex.messaging.messages.RemotingMessage'
|
49
|
+
]
|
50
|
+
|
51
|
+
as_classes.each do |as_class|
|
52
|
+
@mapper.get_ruby_obj(as_class).should_not be_a(RocketAMF::Values::TypedHash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should map special classes from ruby by default" do
|
57
|
+
ruby_classes = [
|
58
|
+
'RocketAMF::Values::AcknowledgeMessage',
|
59
|
+
'RocketAMF::Values::ErrorMessage'
|
60
|
+
]
|
61
|
+
|
62
|
+
ruby_classes.each do |obj|
|
63
|
+
@mapper.get_as_class_name(obj).should_not be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow config modification" do
|
68
|
+
RocketAMF::ClassMapping.mappings.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
|
69
|
+
@mapper = RocketAMF::ClassMapping.new
|
70
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "ruby object populator" do
|
75
|
+
it "should populate a ruby class" do
|
76
|
+
obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
|
77
|
+
obj.prop_a.should == 'Data'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should populate a typed hash" do
|
81
|
+
obj = @mapper.populate_ruby_obj RocketAMF::Values::TypedHash.new('UnmappedClass'), {:prop_a => 'Data'}
|
82
|
+
obj[:prop_a].should == 'Data'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "property extractor" do
|
87
|
+
it "should extract hash properties" do
|
88
|
+
hash = {:a => 'test1', 'b' => 'test2'}
|
89
|
+
props = @mapper.props_for_serialization(hash)
|
90
|
+
props.should == {'a' => 'test1', 'b' => 'test2'}
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should extract object properties" do
|
94
|
+
obj = ClassMappingTest.new
|
95
|
+
obj.prop_a = 'Test A'
|
96
|
+
|
97
|
+
hash = @mapper.props_for_serialization obj
|
98
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => nil}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should extract inherited object properties" do
|
102
|
+
obj = ClassMappingTest2.new
|
103
|
+
obj.prop_a = 'Test A'
|
104
|
+
obj.prop_c = 'Test C'
|
105
|
+
|
106
|
+
hash = @mapper.props_for_serialization obj
|
107
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => nil, 'prop_c' => 'Test C'}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|