actionwebservice 0.5.0 → 0.6.0

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 (66) hide show
  1. data/{ChangeLog → CHANGELOG} +20 -0
  2. data/README +45 -1
  3. data/Rakefile +12 -10
  4. data/TODO +8 -9
  5. data/lib/action_web_service.rb +10 -6
  6. data/lib/action_web_service/api.rb +1 -2
  7. data/lib/action_web_service/api/{abstract.rb → base.rb} +14 -71
  8. data/lib/action_web_service/base.rb +0 -3
  9. data/lib/action_web_service/client/base.rb +1 -12
  10. data/lib/action_web_service/client/soap_client.rb +49 -17
  11. data/lib/action_web_service/client/xmlrpc_client.rb +20 -15
  12. data/lib/action_web_service/container.rb +3 -85
  13. data/lib/action_web_service/{api/action_controller.rb → container/action_controller_container.rb} +2 -2
  14. data/lib/action_web_service/container/delegated_container.rb +87 -0
  15. data/lib/action_web_service/container/direct_container.rb +70 -0
  16. data/lib/action_web_service/dispatcher/abstract.rb +100 -102
  17. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +199 -137
  18. data/lib/action_web_service/protocol.rb +1 -1
  19. data/lib/action_web_service/protocol/abstract.rb +14 -112
  20. data/lib/action_web_service/protocol/discovery.rb +37 -0
  21. data/lib/action_web_service/protocol/soap_protocol.rb +32 -458
  22. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +29 -149
  23. data/lib/action_web_service/struct.rb +2 -5
  24. data/lib/action_web_service/test_invoke.rb +130 -0
  25. data/lib/action_web_service/vendor/ws.rb +4 -0
  26. data/lib/action_web_service/vendor/ws/common.rb +8 -0
  27. data/lib/action_web_service/vendor/ws/encoding.rb +3 -0
  28. data/lib/action_web_service/vendor/ws/encoding/abstract.rb +26 -0
  29. data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +90 -0
  30. data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +53 -0
  31. data/lib/action_web_service/vendor/ws/marshaling.rb +3 -0
  32. data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +17 -0
  33. data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +277 -0
  34. data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +116 -0
  35. data/lib/action_web_service/vendor/ws/types.rb +162 -0
  36. data/test/abstract_client.rb +8 -11
  37. data/test/abstract_dispatcher.rb +370 -0
  38. data/test/abstract_unit.rb +1 -0
  39. data/test/api_test.rb +18 -1
  40. data/test/apis/auto_load_api.rb +3 -0
  41. data/test/apis/broken_auto_load_api.rb +2 -0
  42. data/test/client_soap_test.rb +16 -3
  43. data/test/client_xmlrpc_test.rb +16 -4
  44. data/test/container_test.rb +28 -8
  45. data/test/dispatcher_action_controller_soap_test.rb +106 -0
  46. data/test/dispatcher_action_controller_xmlrpc_test.rb +44 -0
  47. data/test/gencov +1 -1
  48. data/test/invocation_test.rb +39 -3
  49. data/test/run +4 -4
  50. data/test/test_invoke_test.rb +77 -0
  51. data/test/ws/abstract_encoding.rb +68 -0
  52. data/test/ws/abstract_unit.rb +13 -0
  53. data/test/ws/gencov +3 -0
  54. data/test/ws/run +5 -0
  55. data/test/ws/soap_marshaling_test.rb +91 -0
  56. data/test/ws/soap_rpc_encoding_test.rb +47 -0
  57. data/test/ws/types_test.rb +41 -0
  58. data/test/ws/xmlrpc_encoding_test.rb +34 -0
  59. metadata +48 -19
  60. data/lib/action_web_service/protocol/registry.rb +0 -55
  61. data/lib/action_web_service/support/signature.rb +0 -100
  62. data/test/abstract_soap.rb +0 -58
  63. data/test/dispatcher_action_controller_test.rb +0 -186
  64. data/test/protocol_registry_test.rb +0 -53
  65. data/test/protocol_soap_test.rb +0 -252
  66. data/test/protocol_xmlrpc_test.rb +0 -147
@@ -0,0 +1,162 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ module WS
5
+ module BaseTypes
6
+ class << self
7
+ def type_name_to_class(name)
8
+ case canonical_type_name(name)
9
+ when :int
10
+ Integer
11
+ when :string
12
+ String
13
+ when :bool
14
+ TrueClass
15
+ when :float
16
+ Float
17
+ when :time
18
+ Time
19
+ when :date
20
+ Date
21
+ end
22
+ end
23
+
24
+ def class_to_type_name(klass)
25
+ if WS.derived_from?(Integer, klass) || WS.derived_from?(Fixnum, klass) || WS.derived_from?(Bignum, klass)
26
+ :int
27
+ elsif klass == String
28
+ :string
29
+ elsif klass == TrueClass || klass == FalseClass
30
+ :bool
31
+ elsif WS.derived_from?(Float, klass) || WS.derived_from?(Precision, klass) || WS.derived_from?(Numeric, klass)
32
+ :float
33
+ elsif klass == Time || klass == DateTime
34
+ :time
35
+ elsif klass == Date
36
+ :date
37
+ else
38
+ raise(TypeError, "#{klass} is not a valid base type")
39
+ end
40
+ end
41
+
42
+ def base_type?(klass)
43
+ !(canonical_type_class(klass) rescue nil).nil?
44
+ end
45
+
46
+ def canonical_type_class(klass)
47
+ type_name_to_class(class_to_type_name(klass))
48
+ end
49
+
50
+ def canonical_param_type_class(spec)
51
+ klass = spec.is_a?(Hash) ? spec.values[0] : spec
52
+ array_element_class = klass.is_a?(Array) ? klass[0] : nil
53
+ klass = array_element_class ? array_element_class : klass
54
+ klass = type_name_to_class(klass) if klass.is_a?(Symbol) || klass.is_a?(String)
55
+ base_class = canonical_type_class(klass) rescue nil
56
+ klass = base_class unless base_class.nil?
57
+ array_element_class ? [klass] : klass
58
+ end
59
+
60
+ def canonical_param_type_spec(spec)
61
+ klass = canonical_param_type_class(spec)
62
+ spec.is_a?(Hash) ? {spec.keys[0]=>klass} : klass
63
+ end
64
+
65
+ def canonical_type_name(name)
66
+ name = name.to_sym
67
+ case name
68
+ when :int, :integer, :fixnum, :bignum
69
+ :int
70
+ when :string, :base64
71
+ :string
72
+ when :bool, :boolean
73
+ :bool
74
+ when :float, :double
75
+ :float
76
+ when :time, :datetime, :timestamp
77
+ :time
78
+ when :date
79
+ :date
80
+ else
81
+ raise(TypeError, "#{name} is not a valid base type")
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ class Param
88
+ attr_accessor :value
89
+ attr_accessor :info
90
+
91
+ def initialize(value, info)
92
+ @value = value
93
+ @info = info
94
+ end
95
+ end
96
+
97
+ class ParamInfo
98
+ attr_accessor :name
99
+ attr_accessor :type
100
+ attr_accessor :data
101
+
102
+ def initialize(name, type, data=nil)
103
+ @name = name
104
+ @type = type
105
+ @data = data
106
+ end
107
+
108
+ def self.create(spec, data, index=nil)
109
+ name = spec.is_a?(Hash) ? spec.keys[0].to_s : (index ? "param#{index}" : nil)
110
+ type = BaseTypes.canonical_param_type_class(spec)
111
+ ParamInfo.new(name, type, data)
112
+ end
113
+ end
114
+
115
+ class BaseTypeCaster
116
+ def initialize
117
+ @handlers = {}
118
+ install_handlers
119
+ end
120
+
121
+ def cast(value, klass)
122
+ type_class = BaseTypes.canonical_type_class(klass)
123
+ return value unless type_class
124
+ @handlers[type_class].call(value, type_class)
125
+ end
126
+
127
+ protected
128
+ def install_handlers
129
+ handler = method(:cast_base_type)
130
+ [:int, :string, :bool, :float, :time, :date].each do |name|
131
+ type = BaseTypes.type_name_to_class(name)
132
+ @handlers[type] = handler
133
+ end
134
+ @handlers[Fixnum] = handler
135
+ end
136
+
137
+ def cast_base_type(value, type_class)
138
+ desired_class = BaseTypes.canonical_type_class(type_class)
139
+ value_class = BaseTypes.canonical_type_class(value.class)
140
+ return value if desired_class == value_class
141
+ desired_name = BaseTypes.class_to_type_name(desired_class)
142
+ case desired_name
143
+ when :int
144
+ Integer(value)
145
+ when :string
146
+ value.to_s
147
+ when :bool
148
+ return false if value.nil?
149
+ value = value.to_s
150
+ return true if value == 'true'
151
+ return false if value == 'false'
152
+ raise(TypeError, "can't convert #{value} to boolean")
153
+ when :float
154
+ Float(value)
155
+ when :time
156
+ Time.parse(value.to_s)
157
+ when :date
158
+ Date.parse(value.to_s)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -20,6 +20,7 @@ module ClientTest
20
20
  api_method :struct_pass, :expects => [[Person]], :returns => [:bool]
21
21
  api_method :client_container, :returns => [:int]
22
22
  api_method :named_parameters, :expects => [{:key=>:string}, {:id=>:int}]
23
+ api_method :thrower
23
24
  end
24
25
 
25
26
  class NullLogOut
@@ -29,11 +30,11 @@ module ClientTest
29
30
  class Container < ActionController::Base
30
31
  web_service_api API
31
32
 
32
- attr :value_void
33
- attr :value_normal
34
- attr :value_array_return
35
- attr :value_struct_pass
36
- attr :value_named_parameters
33
+ attr_accessor :value_void
34
+ attr_accessor :value_normal
35
+ attr_accessor :value_array_return
36
+ attr_accessor :value_struct_pass
37
+ attr_accessor :value_named_parameters
37
38
 
38
39
  def initialize
39
40
  @session = @assigns = {}
@@ -73,12 +74,8 @@ module ClientTest
73
74
  @value_named_parameters = @method_params
74
75
  end
75
76
 
76
- def protocol_request(request)
77
- probe_request_protocol(request)
78
- end
79
-
80
- def dispatch_request(protocol_request)
81
- dispatch_protocol_request(protocol_request)
77
+ def thrower
78
+ raise "Hi"
82
79
  end
83
80
  end
84
81
 
@@ -0,0 +1,370 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+
3
+ module DispatcherTest
4
+ class Node < ActiveRecord::Base
5
+ def initialize(*args)
6
+ super(*args)
7
+ @new_record = false
8
+ end
9
+
10
+ class << self
11
+ def name
12
+ "DispatcherTest::Node"
13
+ end
14
+
15
+ def columns(*args)
16
+ [
17
+ ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
18
+ ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
19
+ ActiveRecord::ConnectionAdapters::Column.new('description', nil, 'string'),
20
+ ]
21
+ end
22
+
23
+ def connection
24
+ self
25
+ end
26
+ end
27
+ end
28
+
29
+ class Person < ActionWebService::Struct
30
+ member :id, :int
31
+ member :name, :string
32
+ end
33
+
34
+ class API < ActionWebService::API::Base
35
+ api_method :add, :expects => [:int, :int], :returns => [:int]
36
+ api_method :interceptee
37
+ api_method :struct_return, :returns => [[Node]]
38
+ api_method :void
39
+ end
40
+
41
+ class DirectAPI < ActionWebService::API::Base
42
+ api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
43
+ api_method :before_filtered
44
+ api_method :after_filtered, :returns => [[:int]]
45
+ api_method :struct_return, :returns => [[Node]]
46
+ api_method :base_struct_return, :returns => [[Person]]
47
+ api_method :thrower
48
+ api_method :void
49
+ end
50
+
51
+ class VirtualAPI < ActionWebService::API::Base
52
+ default_api_method :fallback
53
+ end
54
+
55
+ class Service < ActionWebService::Base
56
+ web_service_api API
57
+
58
+ before_invocation :do_intercept, :only => [:interceptee]
59
+
60
+ attr :added
61
+ attr :intercepted
62
+ attr :void_called
63
+
64
+ def initialize
65
+ @void_called = false
66
+ end
67
+
68
+ def add(a, b)
69
+ @added = a + b
70
+ end
71
+
72
+ def interceptee
73
+ @intercepted = false
74
+ end
75
+
76
+ def struct_return
77
+ n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
78
+ n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
79
+ [n1, n2]
80
+ end
81
+
82
+ def void(*args)
83
+ @void_called = args
84
+ end
85
+
86
+ def do_intercept(name, args)
87
+ [false, "permission denied"]
88
+ end
89
+ end
90
+
91
+ class MTAPI < ActionWebService::API::Base
92
+ inflect_names false
93
+ api_method :getCategories, :returns => [[:string]]
94
+ end
95
+
96
+ class BloggerAPI < ActionWebService::API::Base
97
+ inflect_names false
98
+ api_method :getCategories, :returns => [[:string]]
99
+ end
100
+
101
+ class MTService < ActionWebService::Base
102
+ web_service_api MTAPI
103
+
104
+ def getCategories
105
+ ["mtCat1", "mtCat2"]
106
+ end
107
+ end
108
+
109
+ class BloggerService < ActionWebService::Base
110
+ web_service_api BloggerAPI
111
+
112
+ def getCategories
113
+ ["bloggerCat1", "bloggerCat2"]
114
+ end
115
+ end
116
+
117
+ class AbstractController < ActionController::Base
118
+ def generate_wsdl
119
+ to_wsdl
120
+ end
121
+ end
122
+
123
+ class DelegatedController < AbstractController
124
+ web_service_dispatching_mode :delegated
125
+
126
+ web_service(:test_service) { @service ||= Service.new; @service }
127
+ end
128
+
129
+ class LayeredController < AbstractController
130
+ web_service_dispatching_mode :layered
131
+
132
+ web_service(:mt) { @mt_service ||= MTService.new; @mt_service }
133
+ web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service }
134
+ end
135
+
136
+ class DirectController < AbstractController
137
+ web_service_api DirectAPI
138
+ web_service_dispatching_mode :direct
139
+
140
+ before_filter :alwaysfail, :only => [:before_filtered]
141
+ after_filter :alwaysok, :only => [:after_filtered]
142
+
143
+ attr :added
144
+ attr :before_filter_called
145
+ attr :before_filter_target_called
146
+ attr :after_filter_called
147
+ attr :after_filter_target_called
148
+ attr :void_called
149
+
150
+ def initialize
151
+ @before_filter_called = false
152
+ @before_filter_target_called = false
153
+ @after_filter_called = false
154
+ @after_filter_target_called = false
155
+ @void_called = false
156
+ end
157
+
158
+ def add
159
+ @added = @params['a'] + @params['b']
160
+ end
161
+
162
+ def before_filtered
163
+ @before_filter_target_called = true
164
+ end
165
+
166
+ def after_filtered
167
+ @after_filter_target_called = true
168
+ [5, 6, 7]
169
+ end
170
+
171
+ def thrower
172
+ raise "Hi, I'm an exception"
173
+ end
174
+
175
+ def struct_return
176
+ n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
177
+ n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
178
+ [n1, n2]
179
+ end
180
+
181
+ def base_struct_return
182
+ p1 = Person.new('id' => 1, 'name' => 'person1')
183
+ p2 = Person.new('id' => 2, 'name' => 'person2')
184
+ [p1, p2]
185
+ end
186
+
187
+ def void
188
+ @void_called = @method_params
189
+ end
190
+
191
+ protected
192
+ def alwaysfail
193
+ @before_filter_called = true
194
+ false
195
+ end
196
+
197
+ def alwaysok
198
+ @after_filter_called = true
199
+ end
200
+ end
201
+
202
+ class VirtualController < AbstractController
203
+ web_service_api VirtualAPI
204
+
205
+ def fallback
206
+ "fallback!"
207
+ end
208
+ end
209
+ end
210
+
211
+ module DispatcherCommonTests
212
+ def test_direct_dispatching
213
+ assert_equal(70, do_method_call(@direct_controller, 'Add', 20, 50))
214
+ assert_equal(70, @direct_controller.added)
215
+ assert(@direct_controller.void_called == false)
216
+ case @encoder
217
+ when WS::Encoding::SoapRpcEncoding
218
+ assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
219
+ when WS::Encoding::XmlRpcEncoding
220
+ assert(do_method_call(@direct_controller, 'Void', 3, 4, 5) == true)
221
+ end
222
+ assert(@direct_controller.void_called == [])
223
+ result = do_method_call(@direct_controller, 'BaseStructReturn')
224
+ case @encoder
225
+ when WS::Encoding::SoapRpcEncoding
226
+ assert(result[0].is_a?(DispatcherTest::Person))
227
+ assert(result[1].is_a?(DispatcherTest::Person))
228
+ when WS::Encoding::XmlRpcEncoding
229
+ assert(result[0].is_a?(Hash))
230
+ assert(result[1].is_a?(Hash))
231
+ end
232
+ end
233
+
234
+ def test_direct_entrypoint
235
+ assert(@direct_controller.respond_to?(:api))
236
+ end
237
+
238
+ def test_virtual_dispatching
239
+ assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualOne'))
240
+ assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualTwo'))
241
+ end
242
+
243
+ def test_direct_filtering
244
+ assert_equal(false, @direct_controller.before_filter_called)
245
+ assert_equal(false, @direct_controller.before_filter_target_called)
246
+ do_method_call(@direct_controller, 'BeforeFiltered')
247
+ assert_equal(true, @direct_controller.before_filter_called)
248
+ assert_equal(false, @direct_controller.before_filter_target_called)
249
+ assert_equal(false, @direct_controller.after_filter_called)
250
+ assert_equal(false, @direct_controller.after_filter_target_called)
251
+ assert_equal([5, 6, 7], do_method_call(@direct_controller, 'AfterFiltered'))
252
+ assert_equal(true, @direct_controller.after_filter_called)
253
+ assert_equal(true, @direct_controller.after_filter_target_called)
254
+ end
255
+
256
+ def test_delegated_dispatching
257
+ assert_equal(130, do_method_call(@delegated_controller, 'Add', 50, 80))
258
+ service = @delegated_controller.web_service_object(:test_service)
259
+ assert_equal(130, service.added)
260
+ @delegated_controller.web_service_exception_reporting = true
261
+ assert(service.intercepted.nil?)
262
+ result = do_method_call(@delegated_controller, 'Interceptee')
263
+ assert(service.intercepted.nil?)
264
+ assert(is_exception?(result))
265
+ assert_match(/permission denied/, exception_message(result))
266
+ result = do_method_call(@delegated_controller, 'NonExistentMethod')
267
+ assert(is_exception?(result))
268
+ assert_match(/NonExistentMethod/, exception_message(result))
269
+ assert(service.void_called == false)
270
+ case @encoder
271
+ when WS::Encoding::SoapRpcEncoding
272
+ assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
273
+ when WS::Encoding::XmlRpcEncoding
274
+ assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5) == true)
275
+ end
276
+ assert(service.void_called == [])
277
+ end
278
+
279
+ def test_garbage_request
280
+ [@direct_controller, @delegated_controller].each do |controller|
281
+ controller.class.web_service_exception_reporting = true
282
+ send_garbage_request = lambda do
283
+ request = create_ap_request(controller, 'invalid request body', 'xxx')
284
+ response = ActionController::TestResponse.new
285
+ controller.process(request, response)
286
+ # puts response.body
287
+ assert(response.headers['Status'] =~ /^500/)
288
+ end
289
+ send_garbage_request.call
290
+ controller.class.web_service_exception_reporting = false
291
+ send_garbage_request.call
292
+ end
293
+ end
294
+
295
+ def test_exception_marshaling
296
+ @direct_controller.web_service_exception_reporting = true
297
+ result = do_method_call(@direct_controller, 'Thrower')
298
+ assert(is_exception?(result))
299
+ assert_equal("Hi, I'm an exception", exception_message(result))
300
+ @direct_controller.web_service_exception_reporting = false
301
+ result = do_method_call(@direct_controller, 'Thrower')
302
+ assert(exception_message(result) != "Hi, I'm an exception")
303
+ end
304
+
305
+ def test_ar_struct_return
306
+ [@direct_controller, @delegated_controller].each do |controller|
307
+ result = do_method_call(controller, 'StructReturn')
308
+ case @encoder
309
+ when WS::Encoding::SoapRpcEncoding
310
+ assert(result[0].is_a?(DispatcherTest::Node))
311
+ assert(result[1].is_a?(DispatcherTest::Node))
312
+ assert_equal('node1', result[0].name)
313
+ assert_equal('node2', result[1].name)
314
+ when WS::Encoding::XmlRpcEncoding
315
+ assert(result[0].is_a?(Hash))
316
+ assert(result[1].is_a?(Hash))
317
+ assert_equal('node1', result[0]['name'])
318
+ assert_equal('node2', result[1]['name'])
319
+ end
320
+ end
321
+ end
322
+
323
+ protected
324
+ def service_name(container)
325
+ raise NotImplementedError
326
+ end
327
+
328
+ def exception_message(obj)
329
+ raise NotImplementedError
330
+ end
331
+
332
+ def is_exception?(obj)
333
+ raise NotImplementedError
334
+ end
335
+
336
+ def do_method_call(container, public_method_name, *params)
337
+ mode = container.web_service_dispatching_mode
338
+ case mode
339
+ when :direct
340
+ api = container.class.web_service_api
341
+ when :delegated
342
+ api = container.web_service_object(service_name(container)).class.web_service_api
343
+ when :layered
344
+ service_name = nil
345
+ if public_method_name =~ /^([^\.]+)\.(.*)$/
346
+ service_name = $1
347
+ end
348
+ api = container.web_service_object(service_name.to_sym).class.web_service_api
349
+ end
350
+ info = api.api_methods[method_name] || {}
351
+ params = params.dup
352
+ ((info[:expects] || []) + (info[:returns] || [])).each do |spec|
353
+ @marshaler.register_type(spec)
354
+ end
355
+ expects = info[:expects]
356
+ (0..(params.length-1)).each do |i|
357
+ type_binding = @marshaler.register_type(expects ? expects[i] : params[i].class)
358
+ info = WS::ParamInfo.create(expects ? expects[i] : params[i].class, type_binding, i)
359
+ params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
360
+ end
361
+ body = @encoder.encode_rpc_call(public_method_name, params)
362
+ # puts body
363
+ ap_request = create_ap_request(container, body, public_method_name, *params)
364
+ ap_response = ActionController::TestResponse.new
365
+ container.process(ap_request, ap_response)
366
+ # puts ap_response.body
367
+ public_method_name, return_value = @encoder.decode_rpc_response(ap_response.body)
368
+ @marshaler.unmarshal(return_value).value
369
+ end
370
+ end