rapuncel 0.0.5.RC1 → 0.0.5.RC2
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/Gemfile.lock +30 -0
- data/README.md +44 -22
- data/lib/rapuncel/base_64_string.rb +17 -12
- data/lib/rapuncel/client.rb +29 -4
- data/lib/rapuncel/connection.rb +2 -1
- data/lib/rapuncel/proxy.rb +10 -3
- data/lib/rapuncel/request.rb +0 -2
- data/lib/rapuncel/response.rb +5 -6
- data/lib/rapuncel/xml_rpc/deserializer.rb +113 -0
- data/lib/rapuncel/xml_rpc/serializer.rb +153 -0
- data/rapuncel-0.0.5.RC1.gem +0 -0
- data/rapuncel.gemspec +1 -1
- data/spec/unit/array_spec.rb +2 -2
- data/spec/unit/base64_spec.rb +2 -2
- data/spec/unit/boolean_spec.rb +5 -5
- data/spec/unit/custom_serialization_spec.rb +23 -0
- data/spec/unit/float_spec.rb +5 -5
- data/spec/unit/hash_spec.rb +2 -2
- data/spec/unit/int_spec.rb +3 -3
- data/spec/unit/nil_spec.rb +1 -1
- data/spec/unit/object_spec.rb +23 -2
- data/spec/unit/request_spec.rb +1 -1
- data/spec/unit/response_spec.rb +3 -3
- data/spec/unit/string_spec.rb +5 -5
- data/spec/unit/time_spec.rb +2 -2
- metadata +92 -51
- data/lib/rapuncel/xml_rpc_deserializer.rb +0 -110
- data/lib/rapuncel/xml_rpc_serializer.rb +0 -148
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rapuncel (0.0.5.RC1)
|
5
|
+
activesupport (>= 3.0.0)
|
6
|
+
nokogiri
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://www.rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (3.0.9)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
nokogiri (1.5.0)
|
14
|
+
rake (0.9.2)
|
15
|
+
rspec (2.6.0)
|
16
|
+
rspec-core (~> 2.6.0)
|
17
|
+
rspec-expectations (~> 2.6.0)
|
18
|
+
rspec-mocks (~> 2.6.0)
|
19
|
+
rspec-core (2.6.4)
|
20
|
+
rspec-expectations (2.6.0)
|
21
|
+
diff-lcs (~> 1.1.2)
|
22
|
+
rspec-mocks (2.6.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
rake
|
29
|
+
rapuncel!
|
30
|
+
rspec (>= 2.6.0)
|
data/README.md
CHANGED
@@ -6,10 +6,8 @@ It's based on Nokogiri for XML parsing and thus provides a major performance imp
|
|
6
6
|
## I need your help
|
7
7
|
I currently have exactly 1 application for Rapuncel, and that's [Kangaroo](https://github.com/cice/kangARoo), i.e.
|
8
8
|
the OpenERP XMLRPC service, where it works absolutely fine. To improve Rapuncel i need your experience with
|
9
|
-
other services and their quirks.
|
9
|
+
other services and their quirks. Just open a feature request or file a bug report.
|
10
10
|
|
11
|
-
This Readme is for the upcoming 0.1 release, 0.0.x Readme [here](https://github.com/cice/rapuncel/blob/be19d4427dba14dbc656de1d90501f9d42aa4ef8/README.md)
|
12
|
-
|
13
11
|
## Installation
|
14
12
|
|
15
13
|
### Rails
|
@@ -45,29 +43,32 @@ First you have to create a client with the connection details, e.g.
|
|
45
43
|
Available options are:
|
46
44
|
|
47
45
|
* **host**
|
48
|
-
hostname or ip-address,
|
49
|
-
_default_: localhost
|
46
|
+
hostname or ip-address,
|
47
|
+
_default_: `localhost`
|
50
48
|
* **port**
|
51
|
-
port where your XMLRPC service is listening,
|
52
|
-
_default_: 8080
|
49
|
+
port where your XMLRPC service is listening,
|
50
|
+
_default_: `8080`
|
53
51
|
* **path**
|
54
|
-
path to the service,
|
55
|
-
_default_:
|
52
|
+
path to the service,
|
53
|
+
_default_: `/`
|
56
54
|
* **user**
|
57
|
-
Username for HTTP Authentication
|
55
|
+
Username for HTTP Authentication
|
58
56
|
_default_: _empty_
|
59
57
|
* **password**
|
60
|
-
Password for HTTP Authentication
|
58
|
+
Password for HTTP Authentication
|
61
59
|
_default_: _empty_
|
62
60
|
* **headers**
|
63
|
-
Hash to set additional HTTP headers for the request, e.g. to send an X-ApiKey header for authentication
|
64
|
-
_default_: {}
|
61
|
+
Hash to set additional HTTP headers for the request, e.g. to send an X-ApiKey header for authentication
|
62
|
+
_default_: `{}`
|
65
63
|
* **ssl**
|
66
|
-
Flag wether to use SSL
|
67
|
-
_default_: false
|
64
|
+
Flag wether to use SSL
|
65
|
+
_default_: `false`
|
68
66
|
* **raise_on**
|
69
|
-
Lets you define the behavior on errors or faults, if set to
|
67
|
+
Lets you define the behavior on errors or faults, if set to `:fault`, `:error` or `:both`,
|
70
68
|
an Exception will be raised if something goes wrong
|
69
|
+
* **serialization**
|
70
|
+
Use your own (extended) (De)Serializers. See Custom Serialization
|
71
|
+
_default_: `Rapuncel::XmlRpc`
|
71
72
|
|
72
73
|
### Get a proxy object and ...
|
73
74
|
A proxy object receives ruby method calls, redirects them to your XMLRPC service and returns the response as ruby objects!
|
@@ -91,8 +92,8 @@ Rapuncel supports natively following object-types (and all their subclasses):
|
|
91
92
|
* Array
|
92
93
|
* Hash
|
93
94
|
* TrueClass, FalseClass
|
94
|
-
* Float
|
95
|
-
* BigDecimal (treated like Float)
|
95
|
+
* Float / Double
|
96
|
+
* BigDecimal (treated like Float, unless you set `double_as_bigdecimal` to true)
|
96
97
|
* Time, Time-like objects
|
97
98
|
* Base64
|
98
99
|
|
@@ -112,7 +113,7 @@ Base64 return values from normal String return values.
|
|
112
113
|
## Supported methods
|
113
114
|
You can use most methods via
|
114
115
|
|
115
|
-
proxy.methodname
|
116
|
+
proxy.methodname 'a', 1, [:a, :b], :a => :d
|
116
117
|
|
117
118
|
However methods starting with \_\_, or ending with a bang \! or a question mark ? are not supported. To call those methods you can always
|
118
119
|
use
|
@@ -127,11 +128,11 @@ note
|
|
127
128
|
|
128
129
|
client.call 'methodname', *args
|
129
130
|
|
130
|
-
will return a Rapuncel::Response object, use _call\_to\_ruby_ to get
|
131
|
+
will return a Rapuncel::Response object, use _call\_to\_ruby_ to get a parsed result
|
131
132
|
|
132
133
|
## Deserialization options
|
133
134
|
|
134
|
-
At the moment there are 2 options, to be set quite ugly as class attributes on Rapuncel::
|
135
|
+
At the moment there are 2 options, to be set quite ugly as class attributes on Rapuncel::XmlRpc::Deserializer,
|
135
136
|
which will definitely change.
|
136
137
|
|
137
138
|
1. **double\_as\_bigdecimal**
|
@@ -139,6 +140,17 @@ Deserialize all <double> tags as BigDecimal.
|
|
139
140
|
2. **hash\_keys\_as\_string**
|
140
141
|
Don't use Symbols as keys for deserialized <struct>, but Strings.
|
141
142
|
|
143
|
+
## Custom Serialization
|
144
|
+
|
145
|
+
module MySpecialSerialization
|
146
|
+
class Serializer < Rapuncel::XmlRpc::Serializer
|
147
|
+
end
|
148
|
+
class Deserializer < Rapuncel::XmlRpc::Deserializer
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
client = Rapuncel::Client.new :serialization => MySpecialSerialization
|
153
|
+
# or :serialization => 'MySpecialSerialization'
|
142
154
|
|
143
155
|
## Todo ?
|
144
156
|
|
@@ -155,11 +167,21 @@ See Usage -> configuration -> raise\_on switch
|
|
155
167
|
### Malformed XML/XMLRPC
|
156
168
|
Rapuncel will most likely fail hard.
|
157
169
|
|
170
|
+
## Changelog
|
171
|
+
|
172
|
+
* **0.0.5**
|
173
|
+
* Refactored serialization, preparation for pluggable extensions
|
174
|
+
* Deserialization option "double\_as\_bigdecimal"
|
175
|
+
* Deserialization option "hash\_keys\_as\_string"
|
176
|
+
* base64 support
|
177
|
+
* Object#to\_xmlrpc now should expect a XmlRpc::Serializer instance,
|
178
|
+
not a Builder (you can access the Builder directly via XmlRpc::Serializer#builder)
|
179
|
+
|
158
180
|
## Open Source
|
159
181
|
|
160
182
|
### License
|
161
183
|
|
162
|
-
Copyright (c)
|
184
|
+
Copyright (c) 2011 ['Marian Theisen', 'Michael Eickenberg']
|
163
185
|
|
164
186
|
Permission is hereby granted, free of charge, to any person obtaining
|
165
187
|
a copy of this software and associated documentation files (the
|
@@ -10,22 +10,27 @@ end
|
|
10
10
|
|
11
11
|
module Rapuncel
|
12
12
|
class Base64String < String
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
if RUBY_VERSION =~ /^1\.9/
|
15
|
+
|
16
|
+
def base64_encoded
|
15
17
|
[self].pack 'm'
|
16
|
-
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.decode_base64 string
|
21
|
+
new string.unpack('m')[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
else
|
25
|
+
|
26
|
+
def base64_encoded
|
17
27
|
Base64.encode64 self
|
18
28
|
end
|
19
|
-
end
|
20
29
|
|
21
|
-
|
22
|
-
|
23
|
-
if RUBY_VERSION =~ /^1\.9/
|
24
|
-
new string.unpack('m')[0]
|
25
|
-
else
|
26
|
-
new Base64.decode64 string
|
27
|
-
end
|
30
|
+
def self.decode_base64 string
|
31
|
+
new Base64.decode64 string
|
28
32
|
end
|
33
|
+
|
29
34
|
end
|
30
35
|
end
|
31
|
-
end
|
36
|
+
end
|
data/lib/rapuncel/client.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'rapuncel/adapters/net_http_adapter'
|
2
2
|
require 'rapuncel/connection'
|
3
|
+
require 'rapuncel/xml_rpc/serializer'
|
4
|
+
require 'rapuncel/xml_rpc/deserializer'
|
3
5
|
require 'active_support/core_ext/hash/except'
|
6
|
+
require 'active_support/inflector/methods'
|
4
7
|
|
5
8
|
module Rapuncel
|
6
9
|
class Client
|
@@ -17,8 +20,9 @@ module Rapuncel
|
|
17
20
|
end
|
18
21
|
|
19
22
|
def initialize configuration = {}
|
20
|
-
@connection = Connection.new configuration.except(:raise_on)
|
21
|
-
|
23
|
+
@connection = Connection.new configuration.except(:raise_on, :serialization)
|
24
|
+
@serialization = configuration[:serialization]
|
25
|
+
|
22
26
|
@raise_on_fault, @raise_on_error = case configuration[:raise_on]
|
23
27
|
when :fault
|
24
28
|
[true, false]
|
@@ -31,10 +35,12 @@ module Rapuncel
|
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
38
|
+
# Dispatch a method call and return the response as Rapuncel::Response object.
|
34
39
|
def call name, *args
|
35
40
|
execute Request.new(name, *args)
|
36
41
|
end
|
37
42
|
|
43
|
+
# Dispatch a method call and return the response as parsed ruby.
|
38
44
|
def call_to_ruby name, *args
|
39
45
|
response = call name, *args
|
40
46
|
|
@@ -46,9 +52,28 @@ module Rapuncel
|
|
46
52
|
|
47
53
|
protected
|
48
54
|
def execute request
|
49
|
-
xml =
|
55
|
+
xml = serializer[request]
|
50
56
|
|
51
|
-
Response.new send_method_call(xml)
|
57
|
+
Response.new send_method_call(xml), deserializer
|
58
|
+
end
|
59
|
+
|
60
|
+
def serialization
|
61
|
+
case @serialization
|
62
|
+
when Module
|
63
|
+
@serialization
|
64
|
+
when String, Symbol
|
65
|
+
ActiveSupport::Inflector.constantize(@serialization.to_s)
|
66
|
+
else
|
67
|
+
XmlRpc
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def serializer
|
72
|
+
@serializer ||= serialization.const_get 'Serializer'
|
73
|
+
end
|
74
|
+
|
75
|
+
def deserializer
|
76
|
+
@deserializer ||= serialization.const_get 'Deserializer'
|
52
77
|
end
|
53
78
|
|
54
79
|
private
|
data/lib/rapuncel/connection.rb
CHANGED
@@ -42,6 +42,7 @@ module Rapuncel
|
|
42
42
|
def protocol
|
43
43
|
ssl? ? 'https' : 'http'
|
44
44
|
end
|
45
|
+
alias_method :scheme, :protocol
|
45
46
|
|
46
47
|
def auth?
|
47
48
|
!!user && !!password
|
@@ -57,7 +58,7 @@ module Rapuncel
|
|
57
58
|
self.path = configuration[:path] || '/'
|
58
59
|
self.headers = configuration[:headers] || {}
|
59
60
|
self.user = configuration[:user]
|
60
|
-
self.password
|
61
|
+
self.password = configuration[:password]
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
data/lib/rapuncel/proxy.rb
CHANGED
@@ -3,13 +3,13 @@ require 'rapuncel/request'
|
|
3
3
|
module Rapuncel
|
4
4
|
class Proxy
|
5
5
|
PROXY_METHODS = %w(tap inspect clone freeze dup class initialize to_s).freeze
|
6
|
-
LOCKED_METHODS = %w(method_missing).freeze
|
6
|
+
LOCKED_METHODS = %w(method_missing object_id).freeze
|
7
7
|
LOCKED_PATTERN = /(\A__|\?\Z|!\Z)/.freeze
|
8
8
|
|
9
9
|
class << self
|
10
10
|
# Initialize a new Proxy object for a specific Client. Alternatively
|
11
11
|
# you can pass a Hash containing configuration for a new Client, which
|
12
|
-
# will be created on-the-fly, but not accessible. The second parameter
|
12
|
+
# will be created on-the-fly, but is directly not accessible. The second parameter
|
13
13
|
# specifies a specific interface/namespace for the remote calls,
|
14
14
|
# i.e. if your RPC method is
|
15
15
|
#
|
@@ -35,6 +35,13 @@ module Rapuncel
|
|
35
35
|
end
|
36
36
|
RUBY
|
37
37
|
end
|
38
|
+
|
39
|
+
# Predefine some proxy methods.
|
40
|
+
def define_proxy_methods *names
|
41
|
+
names.each do |name|
|
42
|
+
define_proxy_method name
|
43
|
+
end
|
44
|
+
end
|
38
45
|
end
|
39
46
|
|
40
47
|
PROXY_METHODS.each do |name|
|
@@ -44,7 +51,7 @@ module Rapuncel
|
|
44
51
|
alias_method "__inspect__", "__to_s__"
|
45
52
|
|
46
53
|
instance_methods.each do |name|
|
47
|
-
unless LOCKED_METHODS.include?(name) || LOCKED_PATTERN.match(name)
|
54
|
+
unless LOCKED_METHODS.include?(name.to_s) || LOCKED_PATTERN.match(name.to_s)
|
48
55
|
define_proxy_method name
|
49
56
|
end
|
50
57
|
end
|
data/lib/rapuncel/request.rb
CHANGED
data/lib/rapuncel/response.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'active_support/core_ext/module/delegation'
|
2
|
-
require 'rapuncel/xml_rpc_deserializer'
|
3
2
|
|
4
3
|
module Rapuncel
|
5
4
|
class Response
|
@@ -7,14 +6,14 @@ module Rapuncel
|
|
7
6
|
class Fault < Exception ; end
|
8
7
|
class Error < Exception ; end
|
9
8
|
|
10
|
-
attr_accessor :http_response, :status, :status_code, :response
|
9
|
+
attr_accessor :http_response, :status, :status_code, :response, :deserializer
|
11
10
|
|
12
11
|
delegate :body,
|
13
12
|
:to => :http_response
|
14
13
|
|
15
|
-
def initialize http_response
|
16
|
-
@http_response = http_response
|
17
|
-
|
14
|
+
def initialize http_response, deserializer
|
15
|
+
@http_response, @deserializer = http_response, deserializer
|
16
|
+
|
18
17
|
evaluate_status
|
19
18
|
end
|
20
19
|
|
@@ -64,7 +63,7 @@ module Rapuncel
|
|
64
63
|
|
65
64
|
protected
|
66
65
|
def deserialize_response
|
67
|
-
@response =
|
66
|
+
@response = deserializer[body]
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Rapuncel
|
2
|
+
module XmlRpc
|
3
|
+
class Deserializer
|
4
|
+
XML_ENCODING = 'UTF-8'
|
5
|
+
|
6
|
+
def initialize xml
|
7
|
+
@xml_doc = Nokogiri::XML.parse xml
|
8
|
+
end
|
9
|
+
|
10
|
+
def deserialize xml_node = root_node
|
11
|
+
send "deserialize_#{xml_node.name}", xml_node
|
12
|
+
end
|
13
|
+
|
14
|
+
def deserialize_base64 xml_node
|
15
|
+
Base64String.decode_base64 xml_node.text.strip
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize_string xml_node
|
19
|
+
xml_node.text.gsub(/(\r\n|\r)/, "\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
def deserialize_array xml_node
|
23
|
+
values = xml_node.first_element_child.element_children
|
24
|
+
|
25
|
+
values.map do |value|
|
26
|
+
deserialize value.first_element_child
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def deserialize_boolean xml_node
|
31
|
+
xml_node.text == "1"
|
32
|
+
end
|
33
|
+
|
34
|
+
def deserialize_double xml_node
|
35
|
+
text = xml_node.text.strip
|
36
|
+
|
37
|
+
if double_as_bigdecimal?
|
38
|
+
BigDecimal.new text
|
39
|
+
else
|
40
|
+
text.to_f
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def deserialize_struct xml_node
|
45
|
+
keys_and_values = xml_node.element_children
|
46
|
+
|
47
|
+
{}.tap do |hash|
|
48
|
+
keys_and_values.each do |key_value|
|
49
|
+
key = key_value.first_element_child.text.strip
|
50
|
+
key = key.to_sym unless hash_keys_as_string?
|
51
|
+
|
52
|
+
value = deserialize key_value.last_element_child.first_element_child
|
53
|
+
|
54
|
+
hash[key] = value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def deserialize_int xml_node
|
60
|
+
xml_node.text.strip.to_i
|
61
|
+
end
|
62
|
+
alias_method :deserialize_i4, :deserialize_int
|
63
|
+
|
64
|
+
define_method "deserialize_dateTime.iso8601" do |xml_node|
|
65
|
+
Time.parse xml_node.text.strip
|
66
|
+
end
|
67
|
+
|
68
|
+
def deserialize_methodResponse xml_node
|
69
|
+
response = xml_node.first_element_child
|
70
|
+
|
71
|
+
if response.name == 'fault'
|
72
|
+
deserialize_methodResponse_fault response
|
73
|
+
else
|
74
|
+
deserialize_methodResponse_params response
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def deserialize_methodResponse_params xml_node
|
79
|
+
deserialize xml_node.first_element_child.first_element_child.first_element_child
|
80
|
+
end
|
81
|
+
|
82
|
+
def deserialize_methodResponse_fault xml_node
|
83
|
+
deserialize xml_node.first_element_child.first_element_child
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_ruby
|
87
|
+
deserialize
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
def root_node
|
92
|
+
@xml_doc.root
|
93
|
+
end
|
94
|
+
|
95
|
+
def double_as_bigdecimal?
|
96
|
+
!!self.class.double_as_bigdecimal
|
97
|
+
end
|
98
|
+
|
99
|
+
def hash_keys_as_string?
|
100
|
+
!!self.class.hash_keys_as_string
|
101
|
+
end
|
102
|
+
|
103
|
+
class << self
|
104
|
+
attr_accessor :double_as_bigdecimal, :hash_keys_as_string
|
105
|
+
|
106
|
+
def deserialize xml
|
107
|
+
new(xml).to_ruby
|
108
|
+
end
|
109
|
+
alias_method :[], :deserialize
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|