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