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.
@@ -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 _:fault_, _:error_ or _:both_,
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 *args
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 standard ruby objects
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::XmlRpcDeserializer,
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) 2010 ['Marian Theisen', 'Michael Eickenberg']
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
- def base64_encoded
14
- if RUBY_VERSION =~ /^1\.9/
13
+
14
+ if RUBY_VERSION =~ /^1\.9/
15
+
16
+ def base64_encoded
15
17
  [self].pack 'm'
16
- else
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
- class << self
22
- def decode_base64 string
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
@@ -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 = XmlRpcSerializer.new(request).to_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
@@ -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 = configuration[:password]
61
+ self.password = configuration[:password]
61
62
  end
62
63
  end
63
64
  end
@@ -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
@@ -1,5 +1,3 @@
1
- require 'rapuncel/xml_rpc_serializer'
2
-
3
1
  module Rapuncel
4
2
  class Request
5
3
  attr_accessor :method_name, :arguments
@@ -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 = XmlRpcDeserializer.new(body).to_ruby
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