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.
- data/MIT-LICENSE +20 -0
- data/README.md +171 -0
- data/Rakefile +24 -0
- data/lib/rapuncel.rb +25 -0
- data/lib/rapuncel/adapters/net_http_adapter.rb +34 -0
- data/lib/rapuncel/base.rb +7 -0
- data/lib/rapuncel/client.rb +54 -0
- data/lib/rapuncel/connection.rb +75 -0
- data/lib/rapuncel/core_ext/array.rb +23 -0
- data/lib/rapuncel/core_ext/big_decimal.rb +7 -0
- data/lib/rapuncel/core_ext/boolean.rb +29 -0
- data/lib/rapuncel/core_ext/float.rb +11 -0
- data/lib/rapuncel/core_ext/hash.rb +32 -0
- data/lib/rapuncel/core_ext/integer.rb +11 -0
- data/lib/rapuncel/core_ext/nil.rb +7 -0
- data/lib/rapuncel/core_ext/object.rb +49 -0
- data/lib/rapuncel/core_ext/string.rb +12 -0
- data/lib/rapuncel/core_ext/symbol.rb +7 -0
- data/lib/rapuncel/core_ext/time.rb +14 -0
- data/lib/rapuncel/proxy.rb +82 -0
- data/lib/rapuncel/request.rb +49 -0
- data/lib/rapuncel/response.rb +92 -0
- data/rapuncel-0.0.1.preview.gem +0 -0
- data/rapuncel.gemspec +22 -0
- data/test/functional/client_test.rb +54 -0
- data/test/functional_test_helper.rb +13 -0
- data/test/test_helper.rb +38 -0
- data/test/test_server.rb +28 -0
- data/test/unit/array_test.rb +96 -0
- data/test/unit/boolean_test.rb +34 -0
- data/test/unit/connection_test.rb +17 -0
- data/test/unit/float_test.rb +23 -0
- data/test/unit/hash_test.rb +54 -0
- data/test/unit/int_test.rb +27 -0
- data/test/unit/nil_test.rb +16 -0
- data/test/unit/object_test.rb +83 -0
- data/test/unit/proxy_test.rb +52 -0
- data/test/unit/request_test.rb +34 -0
- data/test/unit/response_test.rb +100 -0
- data/test/unit/string_test.rb +40 -0
- data/test/unit/time_test.rb +23 -0
- metadata +154 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
class TrueClass
|
2
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
3
|
+
b.boolean "1"
|
4
|
+
|
5
|
+
b.to_xml
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class FalseClass
|
10
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
11
|
+
b.boolean "0"
|
12
|
+
|
13
|
+
b.to_xml
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# this is to catch the from_xml_rpc call from Object
|
18
|
+
class Rapuncel::Boolean
|
19
|
+
def self.from_xml_rpc xml_node
|
20
|
+
|
21
|
+
# DISCUSS: need convention here:
|
22
|
+
case xml_node.text.downcase
|
23
|
+
when '1'
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Hash
|
2
|
+
# Hash will be translated into an XML-RPC "struct" object
|
3
|
+
|
4
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
5
|
+
b.struct do |b|
|
6
|
+
self.each_pair do |key, value|
|
7
|
+
b.member do |b|
|
8
|
+
b.name key.to_s
|
9
|
+
|
10
|
+
b.value do |b|
|
11
|
+
value.to_xml_rpc b
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
b.to_xml
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_xml_rpc xml_node
|
21
|
+
keys_and_values = xml_node.element_children
|
22
|
+
|
23
|
+
new.tap do |hash|
|
24
|
+
keys_and_values.each do |kv|
|
25
|
+
key = kv.first_element_child.text.strip.to_sym
|
26
|
+
value = Object.from_xml_rpc kv.last_element_child.first_element_child
|
27
|
+
|
28
|
+
hash[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Object
|
2
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
3
|
+
if respond_to?(:acts_like?) && acts_like?(:time)
|
4
|
+
to_time.to_xml_rpc b
|
5
|
+
else
|
6
|
+
_collect_ivars_in_hash.to_xml_rpc b
|
7
|
+
end
|
8
|
+
|
9
|
+
b.to_xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_xml_rpc xml_node
|
13
|
+
if xml_node.is_a? String
|
14
|
+
xml_node = Nokogiri::XML.parse(xml_node).root
|
15
|
+
end
|
16
|
+
|
17
|
+
return nil if xml_node.nil?
|
18
|
+
|
19
|
+
case xml_node.name
|
20
|
+
when 'i4', 'int'
|
21
|
+
Integer.from_xml_rpc xml_node
|
22
|
+
when 'array'
|
23
|
+
Array.from_xml_rpc xml_node
|
24
|
+
when 'struct'
|
25
|
+
Hash.from_xml_rpc xml_node
|
26
|
+
when 'double'
|
27
|
+
Float.from_xml_rpc xml_node
|
28
|
+
when 'boolean'
|
29
|
+
Rapuncel::Boolean.from_xml_rpc xml_node
|
30
|
+
when 'string'
|
31
|
+
String.from_xml_rpc xml_node
|
32
|
+
when 'dateTime.iso8601'
|
33
|
+
Time.from_xml_rpc xml_node
|
34
|
+
when 'base64'
|
35
|
+
raise 'Now its time to implement Base64'
|
36
|
+
else
|
37
|
+
raise "What is this? I didn't know #{xml_node.name} was part of the XMLRPC specification? Anyway, the value was: #{xml_node.text.strip}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def _collect_ivars_in_hash
|
43
|
+
{}.tap do |hsh|
|
44
|
+
instance_variables.each do |ivar|
|
45
|
+
hsh[ivar[1..-1]] = instance_variable_get ivar
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class String
|
2
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
3
|
+
b.string self
|
4
|
+
|
5
|
+
b.to_xml
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.from_xml_rpc xml_node
|
9
|
+
xml_node.text.gsub(/(\r\n|\r)/, "\n") #just give back the string between the 'string' tags
|
10
|
+
# DISCUSS: to strip or not to strip
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
class Time
|
4
|
+
|
5
|
+
def to_xml_rpc b=Rapuncel.get_builder
|
6
|
+
b.send "dateTime.iso8601", self.iso8601
|
7
|
+
|
8
|
+
b.to_xml
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_xml_rpc xml_node
|
12
|
+
Time.parse xml_node.text.strip #make a Time object of this string which is hopefully in the right format
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rapuncel/request'
|
2
|
+
|
3
|
+
module Rapuncel
|
4
|
+
class Proxy
|
5
|
+
PROXY_METHODS = %w(tap inspect clone freeze dup class initialize).freeze
|
6
|
+
LOCKED_METHODS = %w(method_missing).freeze
|
7
|
+
LOCKED_PATTERN = /(\A__|\?\Z|!\Z)/.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Initialize a new Proxy object for a specific Client. Alternatively
|
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
|
13
|
+
# specifies a specific interface/namespace for the remote calls,
|
14
|
+
# i.e. if your RPC method is
|
15
|
+
#
|
16
|
+
# int numbers.add(int a, int b)
|
17
|
+
#
|
18
|
+
# You can create a specific proxy for +numbers+, and use +add+ directly
|
19
|
+
#
|
20
|
+
# proxy = Proxy.new client, 'numbers'
|
21
|
+
# proxy.add(40, 2) -> 42
|
22
|
+
#
|
23
|
+
def new client_or_configuration, interface = nil
|
24
|
+
client_or_configuration = Client.new client_or_configuration if client_or_configuration.is_a?(Hash)
|
25
|
+
|
26
|
+
allocate.__tap__ do |new_proxy|
|
27
|
+
new_proxy.__initialize__ client_or_configuration, interface
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_proxy_method name
|
32
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
33
|
+
def #{name} *args, &block
|
34
|
+
call! '#{name}', *args, &block
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
PROXY_METHODS.each do |name|
|
41
|
+
alias_method "__#{name}__", name
|
42
|
+
end
|
43
|
+
|
44
|
+
instance_methods.each do |name|
|
45
|
+
unless LOCKED_METHODS.include?(name) || LOCKED_PATTERN.match(name)
|
46
|
+
define_proxy_method name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def call! name, *args
|
51
|
+
name = "#{@interface}.#{name}" if @interface
|
52
|
+
|
53
|
+
@client.call_to_ruby(name, *args).tap do |response|
|
54
|
+
|
55
|
+
if block_given?
|
56
|
+
yield response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def __initialize__ client, interface #:nodoc:
|
62
|
+
@interface = interface
|
63
|
+
@client = client
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to? name #:nodoc:
|
67
|
+
LOCKED_PATTERN.match(name.to_s) ? super : true
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
def method_missing name, *args, &block #:nodoc:
|
72
|
+
name = name.to_s
|
73
|
+
|
74
|
+
if LOCKED_PATTERN.match name
|
75
|
+
super name.to_sym, *args, &block
|
76
|
+
else
|
77
|
+
self.__class__.define_proxy_method name
|
78
|
+
call! name, *args, &block
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Rapuncel
|
5
|
+
class Request
|
6
|
+
attr_accessor :method_name, :arguments
|
7
|
+
|
8
|
+
|
9
|
+
# Create a new XML-RPC request
|
10
|
+
def initialize method_name, *args
|
11
|
+
@method_name, @arguments = method_name, args
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_xml_rpc builder = Rapuncel.get_builder
|
15
|
+
method_call! builder
|
16
|
+
|
17
|
+
builder.to_xml :encoding => 'UTF-8'
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def method_call! builder
|
22
|
+
|
23
|
+
builder.methodCall do |builder|
|
24
|
+
method_name! builder
|
25
|
+
params! builder
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_name! builder
|
30
|
+
builder.methodName method_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def params! builder
|
34
|
+
builder.params do |builder|
|
35
|
+
arguments.each do |value|
|
36
|
+
param! builder, value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def param! builder, value
|
42
|
+
builder.param do |builder|
|
43
|
+
builder.value do |builder|
|
44
|
+
value.to_xml_rpc builder
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Rapuncel
|
4
|
+
class Response
|
5
|
+
class Exception < ::Exception
|
6
|
+
attr_accessor :code, :string
|
7
|
+
|
8
|
+
def initialize code, string
|
9
|
+
@code, @string = code, string
|
10
|
+
end
|
11
|
+
|
12
|
+
def exception
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class Fault < Exception ; end
|
17
|
+
class Error < Exception ; end
|
18
|
+
|
19
|
+
attr_accessor :http_response, :status, :status_code
|
20
|
+
|
21
|
+
delegate :body,
|
22
|
+
:to => :http_response
|
23
|
+
|
24
|
+
def initialize http_response
|
25
|
+
@http_response = http_response
|
26
|
+
|
27
|
+
evaluate_status
|
28
|
+
end
|
29
|
+
|
30
|
+
def evaluate_status
|
31
|
+
@status_code = http_response.code.to_i
|
32
|
+
|
33
|
+
@status = case
|
34
|
+
when !http_response.success? then 'error'
|
35
|
+
when parsed_body && method_response_success? then 'success'
|
36
|
+
else 'fault'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def success?
|
41
|
+
status == 'success'
|
42
|
+
end
|
43
|
+
|
44
|
+
def fault?
|
45
|
+
status == 'fault'
|
46
|
+
end
|
47
|
+
|
48
|
+
def error?
|
49
|
+
status == 'error'
|
50
|
+
end
|
51
|
+
|
52
|
+
def fault
|
53
|
+
if fault?
|
54
|
+
@fault ||= begin
|
55
|
+
fault_node = parsed_body.xpath('/methodResponse/fault/value/struct').first
|
56
|
+
fault_hash = Hash.from_xml_rpc(fault_node)
|
57
|
+
|
58
|
+
Fault.new fault_hash[:faultCode], fault_hash[:faultString]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def error
|
64
|
+
if error?
|
65
|
+
@error ||= Error.new @status_code, body
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def result
|
70
|
+
if success?
|
71
|
+
@result ||= begin
|
72
|
+
return_values = parsed_body.xpath('/methodResponse/params/param/value/*')
|
73
|
+
|
74
|
+
Object.from_xml_rpc return_values.first
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_ruby
|
80
|
+
result || fault || error
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
def parsed_body
|
85
|
+
@xml_doc ||= Nokogiri::XML.parse body
|
86
|
+
end
|
87
|
+
|
88
|
+
def method_response_success?
|
89
|
+
parsed_body.xpath('/methodResponse/fault').empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
Binary file
|
data/rapuncel.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "rapuncel"
|
3
|
+
s.version = "0.0.1.alpha"
|
4
|
+
s.date = "2010-12-02"
|
5
|
+
s.authors = ["Michael Eickenberg", "Marian Theisen"]
|
6
|
+
s.email = 'marian@cice-online.net'
|
7
|
+
s.summary = "Simple XML-RPC Client"
|
8
|
+
s.homepage = "http://github.com/cice/rapuncel"
|
9
|
+
s.description = "Rapuncel is a simple XML-RPC Client based on Nokogiri, thus provides a fast and easy way to interact with XML-RPC services."
|
10
|
+
|
11
|
+
s.files = Dir["**/*"] -
|
12
|
+
Dir["coverage/**/*"] -
|
13
|
+
Dir["rdoc/**/*"] -
|
14
|
+
Dir["doc/**/*"] -
|
15
|
+
Dir["sdoc/**/*"] -
|
16
|
+
Dir["rcov/**/*"]
|
17
|
+
|
18
|
+
s.add_dependency 'nokogiri'
|
19
|
+
s.add_dependency 'activesupport', '>= 3.0.0'
|
20
|
+
|
21
|
+
s.add_development_dependency 'mocha'
|
22
|
+
end
|