rapuncel 0.0.1.alpha

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