rapuncel 0.0.5.RC1 → 0.0.5.RC2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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