actionwebservice 0.5.0 → 0.6.0

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