scalm-RocketAMF 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.
Files changed (106) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +59 -0
  3. data/RocketAMF.gemspec +20 -0
  4. data/benchmark.rb +74 -0
  5. data/ext/rocketamf_ext/class_mapping.c +484 -0
  6. data/ext/rocketamf_ext/constants.h +52 -0
  7. data/ext/rocketamf_ext/deserializer.c +776 -0
  8. data/ext/rocketamf_ext/deserializer.h +28 -0
  9. data/ext/rocketamf_ext/extconf.rb +18 -0
  10. data/ext/rocketamf_ext/remoting.c +184 -0
  11. data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
  12. data/ext/rocketamf_ext/serializer.c +834 -0
  13. data/ext/rocketamf_ext/serializer.h +29 -0
  14. data/ext/rocketamf_ext/utility.h +4 -0
  15. data/lib/rocketamf.rb +216 -0
  16. data/lib/rocketamf/class_mapping.rb +237 -0
  17. data/lib/rocketamf/constants.rb +50 -0
  18. data/lib/rocketamf/ext.rb +28 -0
  19. data/lib/rocketamf/extensions.rb +22 -0
  20. data/lib/rocketamf/pure.rb +24 -0
  21. data/lib/rocketamf/pure/deserializer.rb +455 -0
  22. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  23. data/lib/rocketamf/pure/remoting.rb +117 -0
  24. data/lib/rocketamf/pure/serializer.rb +474 -0
  25. data/lib/rocketamf/remoting.rb +196 -0
  26. data/lib/rocketamf/values/messages.rb +214 -0
  27. data/lib/rocketamf/values/typed_hash.rb +13 -0
  28. data/spec/class_mapping_spec.rb +110 -0
  29. data/spec/deserializer_spec.rb +455 -0
  30. data/spec/fast_class_mapping_spec.rb +144 -0
  31. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  32. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  33. data/spec/fixtures/objects/amf0-date.bin +0 -0
  34. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  35. data/spec/fixtures/objects/amf0-empty-string-key-hash.bin +0 -0
  36. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  37. data/spec/fixtures/objects/amf0-null.bin +1 -0
  38. data/spec/fixtures/objects/amf0-number.bin +0 -0
  39. data/spec/fixtures/objects/amf0-object.bin +0 -0
  40. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  41. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  42. data/spec/fixtures/objects/amf0-string.bin +0 -0
  43. data/spec/fixtures/objects/amf0-time.bin +0 -0
  44. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  45. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  46. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  47. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  48. data/spec/fixtures/objects/amf3-0.bin +0 -0
  49. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  50. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  52. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  53. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  54. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  55. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  56. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  57. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  58. data/spec/fixtures/objects/amf3-date.bin +0 -0
  59. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  60. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  61. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  62. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  63. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  64. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  65. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  66. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  67. data/spec/fixtures/objects/amf3-false.bin +1 -0
  68. data/spec/fixtures/objects/amf3-float.bin +0 -0
  69. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  70. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  71. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  72. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  73. data/spec/fixtures/objects/amf3-max.bin +1 -0
  74. data/spec/fixtures/objects/amf3-min.bin +0 -0
  75. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  76. data/spec/fixtures/objects/amf3-null.bin +1 -0
  77. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  78. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  79. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  80. data/spec/fixtures/objects/amf3-string.bin +1 -0
  81. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  82. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  83. data/spec/fixtures/objects/amf3-true.bin +1 -0
  84. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  85. data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
  86. data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
  87. data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
  88. data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
  89. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  90. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  91. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  92. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  93. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  94. data/spec/fixtures/request/blaze-response.bin +0 -0
  95. data/spec/fixtures/request/commandMessage.bin +0 -0
  96. data/spec/fixtures/request/flex-request.bin +0 -0
  97. data/spec/fixtures/request/multiple-simple-request.bin +0 -0
  98. data/spec/fixtures/request/remotingMessage.bin +0 -0
  99. data/spec/fixtures/request/simple-request.bin +0 -0
  100. data/spec/fixtures/request/simple-response.bin +0 -0
  101. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  102. data/spec/messages_spec.rb +39 -0
  103. data/spec/remoting_spec.rb +196 -0
  104. data/spec/serializer_spec.rb +503 -0
  105. data/spec/spec_helper.rb +55 -0
  106. metadata +164 -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,214 @@
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
+ break if flags[i].nil?
54
+
55
+ list.each_with_index do |name, j|
56
+ if flags[i] & 2**j != 0
57
+ send("#{name}=", des.read_object)
58
+ end
59
+ end
60
+
61
+ # Read remaining flags even though we don't recognize them
62
+ # Zero out high bit, as it's the has-next-field marker
63
+ f = (flags[i] & ~128) >> list.length
64
+ while f > 0
65
+ des.read_object if (f & 1) != 0
66
+ f >>= 1
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
73
+ class RemotingMessage < AbstractMessage
74
+ # The name of the service to be called including package name
75
+ attr_accessor :source
76
+
77
+ # The name of the method to be called
78
+ attr_accessor :operation
79
+
80
+ def initialize
81
+ @clientId = nil
82
+ @destination = nil
83
+ @messageId = rand_uuid
84
+ @timestamp = Time.new.to_i*100
85
+ @timeToLive = 0
86
+ @headers = {}
87
+ @body = nil
88
+ end
89
+ end
90
+
91
+ # Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
92
+ class AsyncMessage < AbstractMessage
93
+ EXTERNALIZABLE_FIELDS = [
94
+ %w[ correlationId correlationIdBytes]
95
+ ]
96
+ attr_accessor :correlationId
97
+
98
+ def correlationIdBytes= bytes
99
+ @correlationId = pretty_uuid(bytes) unless bytes.nil?
100
+ end
101
+
102
+ def read_external des
103
+ super des
104
+ read_external_fields des, EXTERNALIZABLE_FIELDS
105
+ end
106
+ end
107
+
108
+ class AsyncMessageExt < AsyncMessage #:nodoc:
109
+ end
110
+
111
+ # Maps to <tt>flex.messaging.messages.CommandMessage</tt>
112
+ class CommandMessage < AsyncMessage
113
+ SUBSCRIBE_OPERATION = 0
114
+ UNSUSBSCRIBE_OPERATION = 1
115
+ POLL_OPERATION = 2
116
+ CLIENT_SYNC_OPERATION = 4
117
+ CLIENT_PING_OPERATION = 5
118
+ CLUSTER_REQUEST_OPERATION = 7
119
+ LOGIN_OPERATION = 8
120
+ LOGOUT_OPERATION = 9
121
+ SESSION_INVALIDATE_OPERATION = 10
122
+ MULTI_SUBSCRIBE_OPERATION = 11
123
+ DISCONNECT_OPERATION = 12
124
+ UNKNOWN_OPERATION = 10000
125
+
126
+ EXTERNALIZABLE_FIELDS = [
127
+ %w[ operation ]
128
+ ]
129
+ attr_accessor :operation
130
+
131
+ def initialize
132
+ @operation = UNKNOWN_OPERATION
133
+ end
134
+
135
+ def read_external des
136
+ super des
137
+ read_external_fields des, EXTERNALIZABLE_FIELDS
138
+ end
139
+ end
140
+
141
+ class CommandMessageExt < CommandMessage #:nodoc:
142
+ end
143
+
144
+ # Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
145
+ class AcknowledgeMessage < AsyncMessage
146
+ EXTERNALIZABLE_FIELDS = [[]]
147
+
148
+ def initialize message=nil
149
+ @clientId = rand_uuid
150
+ @destination = nil
151
+ @messageId = rand_uuid
152
+ @timestamp = Time.new.to_i*100
153
+ @timeToLive = 0
154
+ @headers = {}
155
+ @body = nil
156
+
157
+ if message.is_a?(AbstractMessage)
158
+ @correlationId = message.messageId
159
+ end
160
+ end
161
+
162
+ def read_external des
163
+ super des
164
+ read_external_fields des, EXTERNALIZABLE_FIELDS
165
+ end
166
+ end
167
+
168
+ class AcknowledgeMessageExt < AcknowledgeMessage #:nodoc:
169
+ end
170
+
171
+ # Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
172
+ class ErrorMessage < AcknowledgeMessage
173
+ # Extended data that will facilitate custom error processing on the client
174
+ attr_accessor :extendedData
175
+
176
+ # The fault code for the error, which defaults to the class name of the
177
+ # causing exception
178
+ attr_accessor :faultCode
179
+
180
+ # Detailed description of what caused the error
181
+ attr_accessor :faultDetail
182
+
183
+ # A simple description of the error
184
+ attr_accessor :faultString
185
+
186
+ # Optional "root cause" of the error
187
+ attr_accessor :rootCause
188
+
189
+ def initialize message=nil, exception=nil
190
+ super message
191
+
192
+ unless exception.nil?
193
+ @e = exception
194
+ @faultCode = @e.class.name
195
+ @faultDetail = @e.backtrace.join("\n")
196
+ @faultString = @e.message
197
+ end
198
+ end
199
+
200
+ def encode_amf serializer
201
+ if serializer.version == 0
202
+ data = {
203
+ :faultCode => @faultCode,
204
+ :faultDetail => @faultDetail,
205
+ :faultString => @faultString
206
+ }
207
+ serializer.write_object(data)
208
+ else
209
+ serializer.write_object(self)
210
+ end
211
+ end
212
+ end
213
+ end
214
+ 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,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