scotttam-RocketAMF 0.2.2
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 +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
|