scotttam-RocketAMF 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +45 -0
- data/Rakefile +54 -0
- data/lib/rocketamf.rb +128 -0
- data/lib/rocketamf/class_mapping.rb +231 -0
- data/lib/rocketamf/constants.rb +46 -0
- data/lib/rocketamf/pure.rb +28 -0
- data/lib/rocketamf/pure/deserializer.rb +419 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +134 -0
- data/lib/rocketamf/pure/serializer.rb +433 -0
- data/lib/rocketamf/remoting.rb +144 -0
- data/lib/rocketamf/values/array_collection.rb +13 -0
- data/lib/rocketamf/values/messages.rb +133 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/spec/amf/class_mapping_spec.rb +150 -0
- data/spec/amf/deserializer_spec.rb +367 -0
- data/spec/amf/remoting_spec.rb +132 -0
- data/spec/amf/serializer_spec.rb +384 -0
- data/spec/amf/values/array_collection_spec.rb +19 -0
- data/spec/amf/values/messages_spec.rb +31 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complexEncodedStringArray.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-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-xmlDoc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-arrayCollection.bin +2 -0
- data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byteArray.bin +0 -0
- data/spec/fixtures/objects/amf3-byteArrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-complexEncodedStringArray.bin +1 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
- data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyDictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
- data/spec/fixtures/objects/amf3-encodedStringRef.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-graphMember.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
- data/spec/fixtures/objects/amf3-largeMin.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-mixedArray.bin +11 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-objRef.bin +0 -0
- data/spec/fixtures/objects/amf3-primArray.bin +1 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-traitRef.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/objects/amf3-xmlDoc.bin +1 -0
- data/spec/fixtures/objects/amf3-xmlRef.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/commandMessage.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +31 -0
- metadata +153 -0
@@ -0,0 +1,144 @@
|
|
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. Returns self for easy
|
13
|
+
# 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
|
21
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Serializes the envelope to a string and returns it
|
25
|
+
#--
|
26
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
|
27
|
+
def serialize
|
28
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Builds response from the request, iterating over each method call and using
|
32
|
+
# the return value as the method call's return value
|
33
|
+
#--
|
34
|
+
# Iterate over all the sent messages. If they're somthing we can handle, like
|
35
|
+
# a command message, then simply add the response message ourselves. If it's
|
36
|
+
# a method call, then call the block with the method and args, catching errors
|
37
|
+
# for handling. Then create the appropriate response message using the return
|
38
|
+
# value of the block as the return value for the method call.
|
39
|
+
def each_method_call request, &block
|
40
|
+
raise 'Response already constructed' if @constructed
|
41
|
+
|
42
|
+
# Set version from response
|
43
|
+
# Can't just copy version because FMS sends version as 1
|
44
|
+
@amf_version = request.amf_version == 3 ? 3 : 0
|
45
|
+
|
46
|
+
request.messages.each do |m|
|
47
|
+
# What's the request body?
|
48
|
+
case m.data
|
49
|
+
when Values::CommandMessage
|
50
|
+
# Pings should be responded to with an AcknowledgeMessage built using the ping
|
51
|
+
# Everything else is unsupported
|
52
|
+
command_msg = m.data
|
53
|
+
if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION
|
54
|
+
response_value = Values::AcknowledgeMessage.new(command_msg)
|
55
|
+
else
|
56
|
+
e = Exception.new("CommandMessage #{command_msg.operation} not implemented")
|
57
|
+
e.set_backtrace ["RocketAMF::Envelope each_method_call"]
|
58
|
+
response_value = Values::ErrorMessage.new(command_msg, e)
|
59
|
+
end
|
60
|
+
when Values::RemotingMessage
|
61
|
+
# Using RemoteObject style message calls
|
62
|
+
remoting_msg = m.data
|
63
|
+
acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg)
|
64
|
+
method_base = remoting_msg.source.to_s.empty? ? '' : remoting_msg.source+'.'
|
65
|
+
body = dispatch_call :method => method_base+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
|
66
|
+
|
67
|
+
# Response should be the bare ErrorMessage if there was an error
|
68
|
+
if body.is_a?(Values::ErrorMessage)
|
69
|
+
response_value = body
|
70
|
+
else
|
71
|
+
acknowledge_msg.body = body
|
72
|
+
response_value = acknowledge_msg
|
73
|
+
end
|
74
|
+
else
|
75
|
+
# Standard response message
|
76
|
+
response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
|
77
|
+
end
|
78
|
+
|
79
|
+
target_uri = m.response_uri
|
80
|
+
target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult'
|
81
|
+
@messages << ::RocketAMF::Message.new(target_uri, '', response_value)
|
82
|
+
end
|
83
|
+
|
84
|
+
@constructed = true
|
85
|
+
end
|
86
|
+
|
87
|
+
# Whether or not the response has been constructed. Can be used to prevent
|
88
|
+
# serialization when no processing has taken place.
|
89
|
+
def constructed?
|
90
|
+
@constructed
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return the serialized envelope as a string
|
94
|
+
def to_s
|
95
|
+
serialize
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def dispatch_call p
|
100
|
+
begin
|
101
|
+
p[:block].call(p[:method], p[:args])
|
102
|
+
rescue Exception => e
|
103
|
+
# Create ErrorMessage object using the source message as the base
|
104
|
+
Values::ErrorMessage.new(p[:source], e)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Request < Envelope #:nodoc:
|
110
|
+
def initialize props={}
|
111
|
+
$stderr.puts("DEPRECATION WARNING: Use RocketAMF::Envelope instead of RocketAMF::Request")
|
112
|
+
super(props)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Response < Envelope #:nodoc:
|
117
|
+
def initialize props={}
|
118
|
+
$stderr.puts("DEPRECATION WARNING: Use RocketAMF::Envelope instead of RocketAMF::Request")
|
119
|
+
super(props)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# RocketAMF::Envelope header
|
124
|
+
class Header
|
125
|
+
attr_accessor :name, :must_understand, :data
|
126
|
+
|
127
|
+
def initialize name, must_understand, data
|
128
|
+
@name = name
|
129
|
+
@must_understand = must_understand
|
130
|
+
@data = data
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# RocketAMF::Envelope message
|
135
|
+
class Message
|
136
|
+
attr_accessor :target_uri, :response_uri, :data
|
137
|
+
|
138
|
+
def initialize target_uri, response_uri, data
|
139
|
+
@target_uri = target_uri
|
140
|
+
@response_uri = response_uri
|
141
|
+
@data = data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,133 @@
|
|
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
|
+
attr_accessor :clientId
|
7
|
+
attr_accessor :destination
|
8
|
+
attr_accessor :messageId
|
9
|
+
attr_accessor :timestamp
|
10
|
+
attr_accessor :timeToLive
|
11
|
+
attr_accessor :headers
|
12
|
+
attr_accessor :body
|
13
|
+
|
14
|
+
protected
|
15
|
+
def rand_uuid
|
16
|
+
[8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def rand_hex_3(l)
|
20
|
+
"%0#{l}x" % rand(1 << l*4)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
|
25
|
+
class RemotingMessage < AbstractMessage
|
26
|
+
# The name of the service to be called including package name
|
27
|
+
attr_accessor :source
|
28
|
+
|
29
|
+
# The name of the method to be called
|
30
|
+
attr_accessor :operation
|
31
|
+
|
32
|
+
# The arguments to call the method with
|
33
|
+
attr_accessor :parameters
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@clientId = rand_uuid
|
37
|
+
@destination = nil
|
38
|
+
@messageId = rand_uuid
|
39
|
+
@timestamp = Time.new.to_i*100
|
40
|
+
@timeToLive = 0
|
41
|
+
@headers = {}
|
42
|
+
@body = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
|
47
|
+
class AsyncMessage < AbstractMessage
|
48
|
+
attr_accessor :correlationId
|
49
|
+
end
|
50
|
+
|
51
|
+
# Maps to <tt>flex.messaging.messages.CommandMessage</tt>
|
52
|
+
class CommandMessage < AsyncMessage
|
53
|
+
SUBSCRIBE_OPERATION = 0
|
54
|
+
UNSUSBSCRIBE_OPERATION = 1
|
55
|
+
POLL_OPERATION = 2
|
56
|
+
CLIENT_SYNC_OPERATION = 4
|
57
|
+
CLIENT_PING_OPERATION = 5
|
58
|
+
CLUSTER_REQUEST_OPERATION = 7
|
59
|
+
LOGIN_OPERATION = 8
|
60
|
+
LOGOUT_OPERATION = 9
|
61
|
+
SESSION_INVALIDATE_OPERATION = 10
|
62
|
+
MULTI_SUBSCRIBE_OPERATION = 11
|
63
|
+
DISCONNECT_OPERATION = 12
|
64
|
+
UNKNOWN_OPERATION = 10000
|
65
|
+
|
66
|
+
attr_accessor :operation
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@operation = UNKNOWN_OPERATION
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
|
74
|
+
class AcknowledgeMessage < AsyncMessage
|
75
|
+
def initialize message=nil
|
76
|
+
@clientId = rand_uuid
|
77
|
+
@destination = nil
|
78
|
+
@messageId = rand_uuid
|
79
|
+
@timestamp = Time.new.to_i*100
|
80
|
+
@timeToLive = 0
|
81
|
+
@headers = {}
|
82
|
+
@body = nil
|
83
|
+
|
84
|
+
if message.is_a?(AbstractMessage)
|
85
|
+
@correlationId = message.messageId
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
|
91
|
+
class ErrorMessage < AcknowledgeMessage
|
92
|
+
# Extended data that will facilitate custom error processing on the client
|
93
|
+
attr_accessor :extendedData
|
94
|
+
|
95
|
+
# The fault code for the error, which defaults to the class name of the
|
96
|
+
# causing exception
|
97
|
+
attr_accessor :faultCode
|
98
|
+
|
99
|
+
# Detailed description of what caused the error
|
100
|
+
attr_accessor :faultDetail
|
101
|
+
|
102
|
+
# A simple description of the error
|
103
|
+
attr_accessor :faultString
|
104
|
+
|
105
|
+
# Optional "root cause" of the error
|
106
|
+
attr_accessor :rootCause
|
107
|
+
|
108
|
+
def initialize message=nil, exception=nil
|
109
|
+
super message
|
110
|
+
|
111
|
+
unless exception.nil?
|
112
|
+
@e = exception
|
113
|
+
@faultCode = @e.class.name
|
114
|
+
@faultDetail = @e.backtrace.join("\n")
|
115
|
+
@faultString = @e.message
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def encode_amf serializer
|
120
|
+
if serializer.version == 0
|
121
|
+
data = {
|
122
|
+
:faultCode => @faultCode,
|
123
|
+
:faultDetail => @faultDetail,
|
124
|
+
:faultString => @faultString
|
125
|
+
}
|
126
|
+
serializer.write_hash(data)
|
127
|
+
else
|
128
|
+
serializer.write_object(self)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
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,150 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
class ClassMappingTest
|
4
|
+
attr_accessor :prop_a
|
5
|
+
attr_accessor :prop_b
|
6
|
+
attr_accessor :prop_c
|
7
|
+
end
|
8
|
+
class ClassMappingTest2 < ClassMappingTest; end;
|
9
|
+
|
10
|
+
module ANamespace; class TestRubyClass; end; end
|
11
|
+
|
12
|
+
describe RocketAMF::ClassMapping do
|
13
|
+
before :each do
|
14
|
+
@mapper = RocketAMF::ClassMapping.new
|
15
|
+
@mapper.define do |m|
|
16
|
+
m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "class name mapping" do
|
21
|
+
it "should allow resetting of mappings back to defaults" do
|
22
|
+
@mapper.reset
|
23
|
+
@mapper.get_as_class_name('ClassMappingTest').should be_nil
|
24
|
+
@mapper.get_as_class_name('RocketAMF::Values::AcknowledgeMessage').should_not be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return AS class name for ruby objects" do
|
28
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
|
29
|
+
@mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
|
30
|
+
@mapper.get_as_class_name('BadClass').should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should instantiate a ruby class" do
|
34
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should properly instantiate namespaced classes" do
|
38
|
+
@mapper.define {|m| m.map :as => 'ASClass', :ruby => 'ANamespace::TestRubyClass'}
|
39
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ANamespace::TestRubyClass)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return a hash with original type if not mapped" do
|
43
|
+
obj = @mapper.get_ruby_obj('UnmappedClass')
|
44
|
+
obj.should be_a(RocketAMF::Values::TypedHash)
|
45
|
+
obj.type.should == 'UnmappedClass'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should map special classes from AS by default" do
|
49
|
+
as_classes = [
|
50
|
+
'flex.messaging.messages.AcknowledgeMessage',
|
51
|
+
'flex.messaging.messages.CommandMessage',
|
52
|
+
'flex.messaging.messages.RemotingMessage',
|
53
|
+
'flex.messaging.io.ArrayCollection'
|
54
|
+
]
|
55
|
+
|
56
|
+
as_classes.each do |as_class|
|
57
|
+
@mapper.get_ruby_obj(as_class).should_not be_a(RocketAMF::Values::TypedHash)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should map special classes from ruby by default" do
|
62
|
+
ruby_classes = [
|
63
|
+
'RocketAMF::Values::AcknowledgeMessage',
|
64
|
+
'RocketAMF::Values::ErrorMessage',
|
65
|
+
'RocketAMF::Values::ArrayCollection'
|
66
|
+
]
|
67
|
+
|
68
|
+
ruby_classes.each do |obj|
|
69
|
+
@mapper.get_as_class_name(obj).should_not be_nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should allow config modification" do
|
74
|
+
@mapper.define do |m|
|
75
|
+
m.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
|
76
|
+
end
|
77
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "ruby object populator" do
|
82
|
+
it "should populate a ruby class" do
|
83
|
+
obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
|
84
|
+
obj.prop_a.should == 'Data'
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should populate a typed hash" do
|
88
|
+
obj = @mapper.populate_ruby_obj RocketAMF::Values::TypedHash.new('UnmappedClass'), {:prop_a => 'Data'}
|
89
|
+
obj[:prop_a].should == 'Data'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should allow custom populators" do
|
93
|
+
class CustomPopulator
|
94
|
+
def can_handle? obj
|
95
|
+
true
|
96
|
+
end
|
97
|
+
def populate obj, props, dynamic_props
|
98
|
+
obj[:populated] = true
|
99
|
+
obj.merge! props
|
100
|
+
obj.merge! dynamic_props if dynamic_props
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
@mapper.object_populators << CustomPopulator.new
|
105
|
+
obj = @mapper.populate_ruby_obj({}, {:prop_a => 'Data'})
|
106
|
+
obj[:populated].should == true
|
107
|
+
obj[:prop_a].should == 'Data'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "property extractor" do
|
112
|
+
it "should extract hash properties" do
|
113
|
+
hash = {:a => 'test1', 'b' => 'test2'}
|
114
|
+
props = @mapper.props_for_serialization(hash)
|
115
|
+
props.should == {'a' => 'test1', 'b' => 'test2'}
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should extract object properties" do
|
119
|
+
obj = ClassMappingTest.new
|
120
|
+
obj.prop_a = 'Test A'
|
121
|
+
obj.prop_b = 'Test B'
|
122
|
+
|
123
|
+
hash = @mapper.props_for_serialization obj
|
124
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => 'Test B', 'prop_c' => nil}
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should extract inherited object properties" do
|
128
|
+
obj = ClassMappingTest2.new
|
129
|
+
obj.prop_a = 'Test A'
|
130
|
+
obj.prop_b = 'Test B'
|
131
|
+
|
132
|
+
hash = @mapper.props_for_serialization obj
|
133
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => 'Test B', 'prop_c' => nil}
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should allow custom serializers" do
|
137
|
+
class CustomSerializer
|
138
|
+
def can_handle? obj
|
139
|
+
true
|
140
|
+
end
|
141
|
+
def serialize obj
|
142
|
+
{:success => true}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
@mapper.object_serializers << CustomSerializer.new
|
147
|
+
@mapper.props_for_serialization(nil).should == {:success => true}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|