socialcast-rapuncel 0.0.7.RC1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ require 'rapuncel/request'
2
+
3
+ module Rapuncel
4
+ class Proxy
5
+ PROXY_METHODS = %w(tap inspect clone freeze dup class initialize to_s).freeze
6
+ LOCKED_METHODS = %w(method_missing object_id).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 is directly 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
+
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
45
+ end
46
+
47
+ PROXY_METHODS.each do |name|
48
+ alias_method "__#{name}__", name
49
+ end
50
+
51
+ alias_method "__inspect__", "__to_s__"
52
+
53
+ instance_methods.each do |name|
54
+ unless LOCKED_METHODS.include?(name.to_s) || LOCKED_PATTERN.match(name.to_s)
55
+ define_proxy_method name
56
+ end
57
+ end
58
+
59
+ def call! name, *args
60
+ name = "#{@interface}.#{name}" if @interface
61
+
62
+ @client.call_to_ruby(name, *args).tap do |response|
63
+
64
+ if block_given?
65
+ yield response
66
+ end
67
+ end
68
+ end
69
+
70
+ def __initialize__ client, interface #:nodoc:
71
+ @interface = interface
72
+ @client = client
73
+ end
74
+
75
+ def respond_to? name #:nodoc:
76
+ LOCKED_PATTERN.match(name.to_s) ? super : true
77
+ end
78
+
79
+ protected
80
+ def method_missing name, *args, &block #:nodoc:
81
+ name = name.to_s
82
+
83
+ if LOCKED_PATTERN.match name
84
+ super name.to_sym, *args, &block
85
+ else
86
+ self.__class__.define_proxy_method name
87
+ call! name, *args, &block
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,10 @@
1
+ module Rapuncel
2
+ class Request
3
+ attr_accessor :method_name, :arguments
4
+
5
+ # Create a new XML-RPC request
6
+ def initialize method_name, *args
7
+ @method_name, @arguments = method_name, args
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,69 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Rapuncel
4
+ class Response
5
+ class Exception < ::Exception ; end
6
+ class Fault < Exception ; end
7
+ class Error < Exception ; end
8
+
9
+ attr_accessor :http_response, :status, :status_code, :response, :deserializer
10
+
11
+ delegate :body,
12
+ :to => :http_response
13
+
14
+ def initialize http_response, deserializer
15
+ @http_response, @deserializer = http_response, deserializer
16
+
17
+ evaluate_status
18
+ end
19
+
20
+ def evaluate_status
21
+ @status_code = http_response.code.to_i
22
+
23
+ @status = unless http_response.success?
24
+ 'error'
25
+ else
26
+ deserialize_response
27
+
28
+ if Hash === response && response[:faultCode]
29
+ 'fault'
30
+ else
31
+ 'success'
32
+ end
33
+ end
34
+ end
35
+
36
+ def success?
37
+ status == 'success'
38
+ end
39
+
40
+ def fault?
41
+ status == 'fault'
42
+ end
43
+
44
+ def error?
45
+ status == 'error'
46
+ end
47
+
48
+ def fault
49
+ fault? && @response
50
+ end
51
+
52
+ def error
53
+ error? && { :http_code => @status_code, :http_body => body }
54
+ end
55
+
56
+ def result
57
+ success? && @response
58
+ end
59
+
60
+ def to_ruby
61
+ result || fault || error
62
+ end
63
+
64
+ protected
65
+ def deserialize_response
66
+ @response = deserializer[body]
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,116 @@
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
+ return unless xml_node
12
+ if self.respond_to?("deserialize_#{xml_node.name}")
13
+ send "deserialize_#{xml_node.name}", xml_node
14
+ end
15
+ end
16
+
17
+ def deserialize_base64 xml_node
18
+ Base64String.decode_base64 xml_node.text.strip
19
+ end
20
+
21
+ def deserialize_string xml_node
22
+ xml_node.text.gsub(/(\r\n|\r)/, "\n")
23
+ end
24
+
25
+ def deserialize_array xml_node
26
+ values = xml_node.first_element_child.element_children
27
+
28
+ values.map do |value|
29
+ deserialize value.first_element_child
30
+ end
31
+ end
32
+
33
+ def deserialize_boolean xml_node
34
+ xml_node.text == "1"
35
+ end
36
+
37
+ def deserialize_double xml_node
38
+ text = xml_node.text.strip
39
+
40
+ if double_as_bigdecimal?
41
+ BigDecimal.new text
42
+ else
43
+ text.to_f
44
+ end
45
+ end
46
+
47
+ def deserialize_struct xml_node
48
+ keys_and_values = xml_node.element_children
49
+
50
+ {}.tap do |hash|
51
+ keys_and_values.each do |key_value|
52
+ key = key_value.first_element_child.text.strip
53
+ key = key.to_sym unless hash_keys_as_string?
54
+
55
+ value = deserialize key_value.last_element_child.first_element_child
56
+
57
+ hash[key] = value
58
+ end
59
+ end
60
+ end
61
+
62
+ def deserialize_int xml_node
63
+ xml_node.text.strip.to_i
64
+ end
65
+ alias_method :deserialize_i4, :deserialize_int
66
+
67
+ define_method "deserialize_dateTime.iso8601" do |xml_node|
68
+ Time.parse xml_node.text.strip
69
+ end
70
+
71
+ def deserialize_methodResponse xml_node
72
+ response = xml_node.first_element_child
73
+
74
+ if response.name == 'fault'
75
+ deserialize_methodResponse_fault response
76
+ else
77
+ deserialize_methodResponse_params response
78
+ end
79
+ end
80
+
81
+ def deserialize_methodResponse_params xml_node
82
+ deserialize xml_node.first_element_child.first_element_child.first_element_child
83
+ end
84
+
85
+ def deserialize_methodResponse_fault xml_node
86
+ deserialize xml_node.first_element_child.first_element_child
87
+ end
88
+
89
+ def to_ruby
90
+ deserialize
91
+ end
92
+
93
+ protected
94
+ def root_node
95
+ @xml_doc.root
96
+ end
97
+
98
+ def double_as_bigdecimal?
99
+ !!self.class.double_as_bigdecimal
100
+ end
101
+
102
+ def hash_keys_as_string?
103
+ !!self.class.hash_keys_as_string
104
+ end
105
+
106
+ class << self
107
+ attr_accessor :double_as_bigdecimal, :hash_keys_as_string
108
+
109
+ def deserialize xml
110
+ new(xml).to_ruby
111
+ end
112
+ alias_method :[], :deserialize
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,153 @@
1
+ require 'bigdecimal'
2
+ require 'time'
3
+
4
+ module Rapuncel
5
+ module XmlRpc
6
+ class Serializer
7
+ XML_ENCODING = 'UTF-8'
8
+
9
+ attr_reader :builder
10
+
11
+ def initialize object
12
+ @builder = Nokogiri::XML::Builder.new :encoding => XML_ENCODING
13
+
14
+ serialize object
15
+ end
16
+
17
+ def serialize object
18
+ case object
19
+ when Base64String
20
+ serialize_base64 object
21
+ when Array
22
+ serialize_array object
23
+ when String, Symbol
24
+ serialize_string object.to_s
25
+ when TrueClass
26
+ serialize_true
27
+ when FalseClass
28
+ serialize_false
29
+ when Float
30
+ serialize_float object
31
+ when BigDecimal
32
+ serialize_big_decimal object
33
+ when Hash
34
+ serialize_hash object
35
+ when Integer
36
+ serialize_integer object
37
+ when NilClass
38
+ serialize_nil
39
+ when Time
40
+ serialize_time object
41
+ when Request
42
+ serialize_request object
43
+ else
44
+ if object.respond_to?(:acts_like?) && object.acts_like?(:time)
45
+ serialize_time object
46
+ elsif object.respond_to?(:to_xmlrpc)
47
+ object.to_xmlrpc self
48
+ else
49
+ serialize_hash instance_variable_hash(object)
50
+ end
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ def serialize_base64 string
57
+ builder.base64 string.base64_encoded
58
+ end
59
+
60
+ def serialize_array array
61
+ builder.array do |builder|
62
+ builder.data do |builder|
63
+ array.each do |element|
64
+ builder.value do |_|
65
+ serialize element
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def serialize_string string
73
+ builder.string string
74
+ end
75
+
76
+ def serialize_true
77
+ builder.boolean "1"
78
+ end
79
+
80
+ def serialize_false
81
+ builder.boolean "0"
82
+ end
83
+ alias_method :serialize_nil, :serialize_false
84
+
85
+ def serialize_float float
86
+ builder.double float.to_s
87
+ end
88
+
89
+ def serialize_big_decimal big_decimal
90
+ builder.double big_decimal.to_s("F")
91
+ end
92
+
93
+ def serialize_hash hash
94
+ builder.struct do |builder|
95
+ hash.each_pair do |key, value|
96
+ builder.member do |builder|
97
+ # Get a better string representation of BigDecimals
98
+ key = key.to_s("F") if BigDecimal === key
99
+ builder.name key.to_s
100
+
101
+ builder.value do |builder|
102
+ serialize value
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def serialize_integer int
110
+ builder.int int.to_s
111
+ end
112
+
113
+ def serialize_time time
114
+ builder.send "dateTime.iso8601", time.iso8601
115
+ end
116
+
117
+ def serialize_request request
118
+ builder.methodCall do |builder|
119
+ builder.methodName request.method_name
120
+ builder.params do |builder|
121
+ request.arguments.each do |argument|
122
+ builder.param do |builder|
123
+ builder.value do |builder|
124
+ serialize argument
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def to_xml
133
+ @builder.to_xml
134
+ end
135
+
136
+ protected
137
+ def instance_variable_hash object
138
+ {}.tap do |hash|
139
+ object.instance_variables.each do |ivar|
140
+ hash[ivar[1..-1]] = object.instance_variable_get ivar
141
+ end
142
+ end
143
+ end
144
+
145
+ class << self
146
+ def serialize object
147
+ new(object).to_xml
148
+ end
149
+ alias_method :[], :serialize
150
+ end
151
+ end
152
+ end
153
+ end
data/lib/rapuncel.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require 'nokogiri'
3
+
4
+ module Rapuncel
5
+ BUILDER_OPTIONS = {:encoding => 'UTF-8'}
6
+
7
+ def self.get_builder options = {}
8
+ Nokogiri::XML::Builder.new options
9
+ end
10
+ end
11
+
12
+ require 'rapuncel/request'
13
+ require 'rapuncel/response'
14
+ require 'rapuncel/client'
15
+ require 'rapuncel/proxy'
16
+
17
+ require 'rapuncel/base_64_string'
Binary file
data/rapuncel.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "socialcast-rapuncel"
3
+ s.version = "0.0.7.RC1"
4
+ s.date = Time.now.strftime("%Y-%m-%d")
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
+ s.add_dependency 'cookiejar', '~> 0.3.0'
21
+
22
+ s.add_development_dependency 'rspec', '>= 2.6.0'
23
+ s.add_development_dependency 'rake'
24
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rapuncel::Client do
4
+ it 'simple XMLRPC' do
5
+ client = Rapuncel::Client.new :port => 9485
6
+ proxy = client.proxy_for 'num'
7
+
8
+ proxy.add(40, 2).should == 42
9
+ end
10
+
11
+ it 'fault XMLRPC without raising' do
12
+ client = Rapuncel::Client.new :port => 9485
13
+ proxy = client.proxy_for 'num'
14
+
15
+ lambda do
16
+ proxy.add 20, 20, 2
17
+ end.should_not raise_error Rapuncel::Response::Exception
18
+
19
+
20
+ proxy.add(20, 20, 2).should be_a Hash
21
+ end
22
+
23
+ it 'fault XMLRPC with raising' do
24
+ client = Rapuncel::Client.new :port => 9485, :raise_on => :both
25
+ proxy = client.proxy_for 'num'
26
+
27
+ lambda do
28
+ proxy.add 20, 20, 2
29
+ end.should raise_error Rapuncel::Response::Fault
30
+ end
31
+
32
+ it 'error in connection without raising' do
33
+ client = Rapuncel::Client.new :host => 'www.google.de', :port => 80, :path => '/somefoobarurl'
34
+ proxy = client.proxy
35
+
36
+ lambda do
37
+ proxy.foo :bar, :baz
38
+ end.should_not raise_error Rapuncel::Response::Error
39
+
40
+ err = proxy.foo(:bar, :baz)
41
+ err.should be_a Hash
42
+ err[:http_code].should == 404
43
+ end
44
+
45
+ it 'error in connection with raising' do
46
+ client = Rapuncel::Client.new :host => 'www.google.de', :port => 80, :path => '/somefoobarurl', :raise_on => :both
47
+ proxy = client.proxy
48
+
49
+ lambda do
50
+ proxy.foo :bar
51
+ end.should raise_error Rapuncel::Response::Error
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'nokogiri'
5
+ require 'rapuncel'
6
+ require 'test_server'
7
+
8
+ module SpecHelper
9
+ def anythings count
10
+ [anything] * count
11
+ end
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ config.before :all do
16
+ @server = TestServer.new
17
+ @server.start
18
+ end
19
+
20
+ config.after :all do
21
+ @server.stop
22
+ end
23
+ end
24
+
25
+ String.class_eval do
26
+ def has_xpath? xpath, options = {}
27
+ eq = options[:content]
28
+ count = options[:count] || 1
29
+ doc = Nokogiri::XML.parse self, nil, nil, Nokogiri::XML::ParseOptions::STRICT
30
+ res = doc.xpath xpath
31
+
32
+ unless eq
33
+ res.size.should == count
34
+ else
35
+ eq = eq.strip
36
+
37
+ if count
38
+ res.to_a.select{ |node|
39
+ node.text.strip == eq
40
+ }.size.should == count
41
+ else
42
+ res.to_a.all?{ |node|
43
+ node.text.strip == eq
44
+ }.should == true
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ require "xmlrpc/server"
2
+ require 'rubygems'
3
+
4
+ class Num
5
+ INTERFACE = XMLRPC::interface("num") {
6
+ meth 'int add(int, int)', 'Add two numbers', 'add'
7
+ meth 'int div(int, int)', 'Divide two numbers'
8
+ }
9
+
10
+ def add(a, b) a + b end
11
+ def div(a, b) a / b end
12
+ end
13
+
14
+ class TestServer
15
+ attr_accessor :server
16
+
17
+ def initialize
18
+ @server = XMLRPC::Server.new(9485, '127.0.0.1', 4, File.open('/dev/null','w')).tap do |s|
19
+ s.add_handler(Num::INTERFACE, Num.new)
20
+ end
21
+ end
22
+
23
+ def start
24
+ Thread.new { @server.serve }
25
+ end
26
+
27
+ def stop
28
+ @server.shutdown
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ describe "Serialization" do
5
+ before do
6
+ @array = (1..10).to_a.map &:to_s
7
+
8
+ @xml = Rapuncel::XmlRpc::Serializer[@array]
9
+ end
10
+
11
+ it "yields a value tag for each array element" do
12
+ @xml.should have_xpath('/array/data/value', :count => 10)
13
+ end
14
+
15
+ it "preserves the element order" do
16
+ @xml.should have_xpath('/array/data/value[4]/string', :content => "4")
17
+ @xml.should have_xpath('/array/data/value[5]/string', :content => "5")
18
+ end
19
+ end
20
+
21
+ describe "Deserialization" do
22
+ before do
23
+ @xml = <<-XML
24
+ <array><data>
25
+ <value><string>1</string></value>
26
+ <value><string>2</string></value>
27
+ <value><string>3</string></value>
28
+ </data></array>
29
+ XML
30
+ @array = Rapuncel::XmlRpc::Deserializer[@xml]
31
+ end
32
+
33
+ it 'has correct number of elements' do
34
+ @array.count.should == 3
35
+ end
36
+
37
+ it 'preserves element order' do
38
+ @array.should == ["1", "2", "3"]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Base64" do
4
+ def to_base64 string
5
+ if RUBY_VERSION =~ /^1\.9/
6
+ [string].pack('m')
7
+ else
8
+ require 'base64'
9
+ Base64.encode64(string)
10
+ end
11
+ end
12
+
13
+ it 'should encode Base64 marked strings as Base64' do
14
+ string = "abcdefghABCDEFGH1234567890".as_base64
15
+ string.should be_a Rapuncel::Base64String
16
+
17
+ xml = Rapuncel::XmlRpc::Serializer[string]
18
+ xml.should have_xpath('/base64', :content => to_base64(string))
19
+ end
20
+
21
+ it 'should decode base64 as Base64Strings' do
22
+ xml = <<-XML
23
+ <base64>YWJjZGVmZ2hBQkNERUZHSDEyMzQ1Njc4OTA=</base64>
24
+ XML
25
+
26
+ string = Rapuncel::XmlRpc::Deserializer[xml]
27
+ string.should be_a Rapuncel::Base64String
28
+ string.should == "abcdefghABCDEFGH1234567890"
29
+ end
30
+ end