rapuncel 0.0.1.alpha

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.
Files changed (42) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +171 -0
  3. data/Rakefile +24 -0
  4. data/lib/rapuncel.rb +25 -0
  5. data/lib/rapuncel/adapters/net_http_adapter.rb +34 -0
  6. data/lib/rapuncel/base.rb +7 -0
  7. data/lib/rapuncel/client.rb +54 -0
  8. data/lib/rapuncel/connection.rb +75 -0
  9. data/lib/rapuncel/core_ext/array.rb +23 -0
  10. data/lib/rapuncel/core_ext/big_decimal.rb +7 -0
  11. data/lib/rapuncel/core_ext/boolean.rb +29 -0
  12. data/lib/rapuncel/core_ext/float.rb +11 -0
  13. data/lib/rapuncel/core_ext/hash.rb +32 -0
  14. data/lib/rapuncel/core_ext/integer.rb +11 -0
  15. data/lib/rapuncel/core_ext/nil.rb +7 -0
  16. data/lib/rapuncel/core_ext/object.rb +49 -0
  17. data/lib/rapuncel/core_ext/string.rb +12 -0
  18. data/lib/rapuncel/core_ext/symbol.rb +7 -0
  19. data/lib/rapuncel/core_ext/time.rb +14 -0
  20. data/lib/rapuncel/proxy.rb +82 -0
  21. data/lib/rapuncel/request.rb +49 -0
  22. data/lib/rapuncel/response.rb +92 -0
  23. data/rapuncel-0.0.1.preview.gem +0 -0
  24. data/rapuncel.gemspec +22 -0
  25. data/test/functional/client_test.rb +54 -0
  26. data/test/functional_test_helper.rb +13 -0
  27. data/test/test_helper.rb +38 -0
  28. data/test/test_server.rb +28 -0
  29. data/test/unit/array_test.rb +96 -0
  30. data/test/unit/boolean_test.rb +34 -0
  31. data/test/unit/connection_test.rb +17 -0
  32. data/test/unit/float_test.rb +23 -0
  33. data/test/unit/hash_test.rb +54 -0
  34. data/test/unit/int_test.rb +27 -0
  35. data/test/unit/nil_test.rb +16 -0
  36. data/test/unit/object_test.rb +83 -0
  37. data/test/unit/proxy_test.rb +52 -0
  38. data/test/unit/request_test.rb +34 -0
  39. data/test/unit/response_test.rb +100 -0
  40. data/test/unit/string_test.rb +40 -0
  41. data/test/unit/time_test.rb +23 -0
  42. metadata +154 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 ['Marian Theisen', 'Michael Eickenberg']
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # Rapuncel - Simple XML-RPC Client
2
+
3
+ Rapuncel ([wikipedia](http://en.wikipedia.org/wiki/Rapunzel)) is a simple and lightweight, but fast XML-RPC client library for ruby.
4
+ It's based on Nokogiri for XML parsing and thus provides a major performance improvement for large XML responses.
5
+
6
+ ## Installation
7
+
8
+ ### Rails
9
+ Add this to your Gemfile:
10
+
11
+ gem 'rapuncel'
12
+
13
+ Run
14
+
15
+ bundle install
16
+
17
+ and you're good to go.
18
+
19
+ ### Other Ruby / IRB
20
+ Install it as gem:
21
+
22
+ gem install rapuncel
23
+
24
+ Require **rubygems** and **rapuncel**
25
+
26
+ require 'rubygems'
27
+ require 'rapuncel'
28
+
29
+ ## Usage
30
+
31
+ ### Initialize client
32
+ Usage is pretty straightforward, Rapuncel provides a method proxy to send calls to your XMLRPC service like you would to a normal ruby
33
+ object.
34
+ First you have to create a client with the connection details, e.g.
35
+
36
+ client = Rapuncel::Client.new :host => 'localhost', :port => 8080, :path => '/xmlrpc'
37
+
38
+ Available options are:
39
+
40
+ * **host**
41
+ hostname or ip-address,
42
+ _default_: localhost
43
+ * **port**
44
+ port where your XMLRPC service is listening,
45
+ _default_: 8080
46
+ * **path**
47
+ path to the service,
48
+ _default_: /
49
+ * **user**
50
+ Username for HTTP Authentication
51
+ _default_: _empty_
52
+ * **password**
53
+ Username for HTTP Authentication
54
+ _default_: _empty_
55
+ * **auth\_method**
56
+ HTTP Auth method
57
+ _default_: basic **IF** user or password is set
58
+ * **api\_key**
59
+ If set, sends all request with a X-ApiKey: _api\_key_ header
60
+ * **api\_key\_header**
61
+ Allows you to modify the header key for API-Key auth
62
+ _default_: X-ApiKey
63
+ * **raise_on**
64
+ Lets you define the behavior on errors or faults, if set to _:fault_, _:error_ or _:both_,
65
+ an Exception will be raised if something goes wrong
66
+
67
+ ### Get a proxy object and ...
68
+ A proxy object receives ruby method calls, redirects them to your XMLRPC service and returns the response as ruby objects!
69
+
70
+ proxy = client.proxy
71
+
72
+ # suppose your XMLRPC service has a method exposed 'concat_string(string1, string2)'
73
+ proxy.concat_string "foo", "bar"
74
+ -> "foobar"
75
+
76
+ # if you need to access specific interfaces on your service, e.g. 'string.concat(string1, string2)'
77
+ proxy = client.proxy_for 'string'
78
+ proxy.concat 'foo', 'bar'
79
+ -> 'foobar'
80
+
81
+ ## Supported objects
82
+ Rapuncel supports natively following object-types (and all their subclasses):
83
+
84
+ * Integer
85
+ * String
86
+ * Array
87
+ * Hash
88
+ * TrueClass, FalseClass
89
+ * Float
90
+ * BigDecimal (treated like Float)
91
+ * Time, Time-like objects
92
+
93
+ * Symbols are converted to Strings
94
+
95
+ * All other objects are transformed into a Hash ('struct' in XMLRPC-speak) containing their instance variables as key-value-pairs.
96
+
97
+ ### Can i customize this behavior for my objects?
98
+ Yes you can, and it's dead simple, just override _to\_xml\_rpc_ with following signature (this is taken from Symbol#to\_xml\_rpc):
99
+
100
+ def to_xml_rpc(builder = Rapuncel.get_builder)
101
+ self.to_s.to_xml_rpc(builder)
102
+ end
103
+
104
+ Of course you don't have to delegate to #to\_s, you just can use the Builder object directly
105
+
106
+ def to_xml_rpc(builder = Rapuncel.get_builder)
107
+ builder.string(self.to_s)
108
+ end
109
+
110
+ ## Supported methods
111
+ You can use most methods via
112
+
113
+ proxy.methodname *args
114
+
115
+ However methods starting with \_\_, or ending with a bang \! or a question mark ? are not supported. To call those methods you can always
116
+ use
117
+
118
+ proxy.call! 'methodname', *args
119
+
120
+ or via
121
+
122
+ client.call_to_ruby 'methodname', *args
123
+
124
+ note
125
+
126
+ client.call 'methodname', *args
127
+
128
+ will return a Rapuncel::Response object, use _call\_to\_ruby_ to get standard ruby objects
129
+
130
+ ## Todo ?
131
+
132
+ * RDoc
133
+ * Extensive functional tests
134
+ * Async requests
135
+ * Base64 support (or rather a consistent concept for Base64)
136
+ * XMLRPC Extensions (pluggable support)
137
+
138
+ ## What happens if something goes wrong?
139
+ ### HTTP Errors / XMLRPC Faults
140
+ See Usage -> configuration -> raise\_on switch
141
+ ### Malformed XML/XMLRPC
142
+ Rapuncel will most likely fail hard.
143
+
144
+ ## Open Source
145
+
146
+ ### License
147
+
148
+ Copyright (c) 2010 ['Marian Theisen', 'Michael Eickenberg']
149
+
150
+ Permission is hereby granted, free of charge, to any person obtaining
151
+ a copy of this software and associated documentation files (the
152
+ "Software"), to deal in the Software without restriction, including
153
+ without limitation the rights to use, copy, modify, merge, publish,
154
+ distribute, sublicense, and/or sell copies of the Software, and to
155
+ permit persons to whom the Software is furnished to do so, subject to
156
+ the following conditions:
157
+
158
+ The above copyright notice and this permission notice shall be
159
+ included in all copies or substantial portions of the Software.
160
+
161
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
162
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
163
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
164
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
165
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
166
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
167
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
168
+
169
+ ### Contribution
170
+
171
+ Pull requests are very welcome!
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Run Rapuncel Testsuite.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ #TODO: sdoc
17
+ desc 'Generate Rapuncel rdoc.'
18
+ Rake::RDocTask.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'Rapuncel'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README.md')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
data/lib/rapuncel.rb ADDED
@@ -0,0 +1,25 @@
1
+
2
+
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ require 'nokogiri'
6
+
7
+ require 'rapuncel/base'
8
+ require 'rapuncel/request'
9
+ require 'rapuncel/response'
10
+ require 'rapuncel/client'
11
+ require 'rapuncel/proxy'
12
+
13
+ # Ruby Core extensions:
14
+ require 'rapuncel/core_ext/object'
15
+ require 'rapuncel/core_ext/string'
16
+ require 'rapuncel/core_ext/symbol'
17
+ require 'rapuncel/core_ext/integer'
18
+ require 'rapuncel/core_ext/big_decimal'
19
+ require 'rapuncel/core_ext/float'
20
+ require 'rapuncel/core_ext/hash'
21
+ require 'rapuncel/core_ext/array'
22
+ require 'rapuncel/core_ext/boolean'
23
+ require 'rapuncel/core_ext/nil'
24
+
25
+ require 'rapuncel/core_ext/time'
@@ -0,0 +1,34 @@
1
+ require 'net/http'
2
+ require 'active_support/memoizable'
3
+
4
+ module Rapuncel
5
+ module Adapters
6
+ module NetHttpAdapter
7
+ extend ActiveSupport::Memoizable
8
+
9
+ class HttpResponse
10
+ def initialize response
11
+ @response = response
12
+ end
13
+
14
+ def success?
15
+ @response.is_a? Net::HTTPOK
16
+ end
17
+
18
+ def body
19
+ @response.body
20
+ end
21
+
22
+ def code
23
+ @response.code
24
+ end
25
+ end
26
+
27
+ def send_method_call str
28
+ req = Net::HTTP.new connection.host, connection.port
29
+
30
+ HttpResponse.new req.post(connection.path, str, connection.headers.stringify_keys)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module Rapuncel
2
+ BUILDER_OPTIONS = {:encoding => 'UTF-8'}
3
+
4
+ def self.get_builder options = {}
5
+ Nokogiri::XML::Builder.new options
6
+ end
7
+ end
@@ -0,0 +1,54 @@
1
+ require 'rapuncel/adapters/net_http_adapter'
2
+ require 'rapuncel/connection'
3
+ require 'active_support/core_ext/hash/except'
4
+
5
+ module Rapuncel
6
+ class Client
7
+ attr_accessor :connection, :raise_on_fault, :raise_on_error
8
+
9
+ include Adapters::NetHttpAdapter
10
+
11
+ def proxy_for interface
12
+ Proxy.new self, interface
13
+ end
14
+
15
+ def proxy
16
+ proxy_for nil
17
+ end
18
+
19
+ def initialize configuration = {}
20
+ @connection = Connection.build configuration.except(:raise_on)
21
+
22
+ @raise_on_fault, @raise_on_error = case configuration[:raise_on]
23
+ when :fault
24
+ [true, false]
25
+ when :error
26
+ [false, true]
27
+ when :both
28
+ [true, true]
29
+ else
30
+ [false, false]
31
+ end
32
+ end
33
+
34
+ def call name, *args
35
+ execute Request.new(name, *args)
36
+ end
37
+
38
+ def call_to_ruby name, *args
39
+ response = call name, *args
40
+
41
+ raise_on_fault && response.fault? && raise(response.fault)
42
+ raise_on_error && response.error? && raise(response.error)
43
+
44
+ response.to_ruby
45
+ end
46
+
47
+ protected
48
+ def execute request
49
+ xml = request.to_xml_rpc
50
+
51
+ Response.new send_method_call(xml)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,75 @@
1
+ module Rapuncel
2
+ class Connection
3
+ def self.build configuration = {}
4
+ configuration = configuration.symbolize_keys
5
+
6
+ case
7
+ when configuration[:user], configuration[:auth_method], configuration[:password]
8
+ AuthConnection.new configuration
9
+ when configuration[:api_key_header], configuration[:api_key]
10
+ ApiKeyAuthConnection.new configuration
11
+ else
12
+ new configuration
13
+ end
14
+ end
15
+
16
+ attr_accessor :host, :port, :path, :ssl, :headers
17
+ alias_method :ssl?, :ssl
18
+
19
+ def initialize configuration = {}
20
+
21
+ @host = configuration[:host] || 'localhost'
22
+ @port = configuration[:port] || '8080'
23
+ @path = configuration[:path] || '/'
24
+ @headers = configuration[:headers] || {}
25
+
26
+
27
+ if ssl = configuration[:ssl]
28
+ @ssl = true
29
+ #TODO
30
+ end
31
+ end
32
+
33
+ def url
34
+ "http://#{host}:#{port}#{path}"
35
+ end
36
+
37
+ def headers
38
+ @headers.merge :Accept => 'text/xml', :'content-type' => 'text/xml'
39
+ end
40
+
41
+ def http_auth? ; false ; end
42
+ def api_auth? ; false ; end
43
+ end
44
+
45
+ class AuthConnection < Connection
46
+ attr_accessor :auth_method, :user, :password
47
+
48
+ def initialize configuration = {}
49
+ super
50
+
51
+ @auth_method = auth_method || 'basic'
52
+ @user = user || ''
53
+ @password = configuration[:password] || ''
54
+ end
55
+
56
+ def http_auth? ; true ; end
57
+ end
58
+
59
+ class ApiKeyAuthConnection < Connection
60
+ attr_accessor :api_key_header, :api_key
61
+
62
+ def initialize configuration = {}
63
+ super
64
+
65
+ @api_key_header = configuration[:api_key_header] || "X-ApiKey"
66
+ @api_key = configuration[:api_key] || '' #DISCUSS: raise error ?
67
+ end
68
+
69
+ def headers
70
+ super.merge api_key_header => api_key
71
+ end
72
+
73
+ def api_auth? ; true ; end
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ class Array
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.array do |b|
4
+ b.data do |b|
5
+ each do |array_entry|
6
+ b.value do |b|
7
+ array_entry.to_xml_rpc b
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ b.to_xml
14
+ end
15
+
16
+ def self.from_xml_rpc xml_node
17
+ values = xml_node.first_element_child.element_children
18
+
19
+ values.map do |value|
20
+ Object.from_xml_rpc value.first_element_child
21
+ end
22
+ end
23
+ end