rubyjedi-actionwebservice 2.3.5.20100615120735

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 (85) hide show
  1. data/CHANGELOG +335 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +381 -0
  4. data/Rakefile +180 -0
  5. data/TODO +32 -0
  6. data/examples/googlesearch/README +143 -0
  7. data/examples/googlesearch/autoloading/google_search_api.rb +50 -0
  8. data/examples/googlesearch/autoloading/google_search_controller.rb +57 -0
  9. data/examples/googlesearch/delegated/google_search_service.rb +108 -0
  10. data/examples/googlesearch/delegated/search_controller.rb +7 -0
  11. data/examples/googlesearch/direct/google_search_api.rb +50 -0
  12. data/examples/googlesearch/direct/search_controller.rb +58 -0
  13. data/examples/metaWeblog/README +17 -0
  14. data/examples/metaWeblog/apis/blogger_api.rb +60 -0
  15. data/examples/metaWeblog/apis/blogger_service.rb +34 -0
  16. data/examples/metaWeblog/apis/meta_weblog_api.rb +67 -0
  17. data/examples/metaWeblog/apis/meta_weblog_service.rb +48 -0
  18. data/examples/metaWeblog/controllers/xmlrpc_controller.rb +16 -0
  19. data/generators/web_service/USAGE +28 -0
  20. data/generators/web_service/templates/api_definition.rb +5 -0
  21. data/generators/web_service/templates/controller.rb +8 -0
  22. data/generators/web_service/templates/functional_test.rb +19 -0
  23. data/generators/web_service/web_service_generator.rb +29 -0
  24. data/lib/action_web_service/acts_as_web_service.rb +24 -0
  25. data/lib/action_web_service/api.rb +297 -0
  26. data/lib/action_web_service/base.rb +38 -0
  27. data/lib/action_web_service/casting.rb +151 -0
  28. data/lib/action_web_service/client/base.rb +28 -0
  29. data/lib/action_web_service/client/soap_client.rb +113 -0
  30. data/lib/action_web_service/client/xmlrpc_client.rb +58 -0
  31. data/lib/action_web_service/client.rb +3 -0
  32. data/lib/action_web_service/container/action_controller_container.rb +93 -0
  33. data/lib/action_web_service/container/delegated_container.rb +86 -0
  34. data/lib/action_web_service/container/direct_container.rb +69 -0
  35. data/lib/action_web_service/container.rb +3 -0
  36. data/lib/action_web_service/dispatcher/abstract.rb +208 -0
  37. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +396 -0
  38. data/lib/action_web_service/dispatcher.rb +2 -0
  39. data/lib/action_web_service/invocation.rb +202 -0
  40. data/lib/action_web_service/protocol/abstract.rb +112 -0
  41. data/lib/action_web_service/protocol/discovery.rb +37 -0
  42. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +242 -0
  43. data/lib/action_web_service/protocol/soap_protocol.rb +176 -0
  44. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +123 -0
  45. data/lib/action_web_service/protocol.rb +4 -0
  46. data/lib/action_web_service/scaffolding.rb +281 -0
  47. data/lib/action_web_service/simple.rb +53 -0
  48. data/lib/action_web_service/string_to_datetime_for_soap.rb +16 -0
  49. data/lib/action_web_service/struct.rb +68 -0
  50. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  51. data/lib/action_web_service/support/signature_types.rb +261 -0
  52. data/lib/action_web_service/templates/scaffolds/layout.html.erb +65 -0
  53. data/lib/action_web_service/templates/scaffolds/methods.html.erb +6 -0
  54. data/lib/action_web_service/templates/scaffolds/parameters.html.erb +29 -0
  55. data/lib/action_web_service/templates/scaffolds/result.html.erb +30 -0
  56. data/lib/action_web_service/test_invoke.rb +110 -0
  57. data/lib/action_web_service/version.rb +9 -0
  58. data/lib/action_web_service.rb +60 -0
  59. data/lib/actionwebservice.rb +1 -0
  60. data/setup.rb +1379 -0
  61. data/test/abstract_client.rb +184 -0
  62. data/test/abstract_dispatcher.rb +549 -0
  63. data/test/abstract_unit.rb +43 -0
  64. data/test/actionwebservice_unittest.db +0 -0
  65. data/test/api_test.rb +102 -0
  66. data/test/apis/auto_load_api.rb +3 -0
  67. data/test/apis/broken_auto_load_api.rb +2 -0
  68. data/test/base_test.rb +42 -0
  69. data/test/casting_test.rb +95 -0
  70. data/test/client_soap_test.rb +156 -0
  71. data/test/client_xmlrpc_test.rb +154 -0
  72. data/test/container_test.rb +75 -0
  73. data/test/debug.log +12305 -0
  74. data/test/dispatcher_action_controller_soap_test.rb +139 -0
  75. data/test/dispatcher_action_controller_xmlrpc_test.rb +59 -0
  76. data/test/fixtures/db_definitions/mysql.sql +8 -0
  77. data/test/fixtures/db_definitions/sqlite3.sql +8 -0
  78. data/test/fixtures/users.yml +12 -0
  79. data/test/gencov +3 -0
  80. data/test/invocation_test.rb +186 -0
  81. data/test/run +6 -0
  82. data/test/scaffolded_controller_test.rb +147 -0
  83. data/test/struct_test.rb +84 -0
  84. data/test/test_invoke_test.rb +113 -0
  85. metadata +182 -0
@@ -0,0 +1,48 @@
1
+ require 'meta_weblog_api'
2
+
3
+ class MetaWeblogService < ActionWebService::Base
4
+ web_service_api MetaWeblogAPI
5
+
6
+ def initialize
7
+ @postid = 0
8
+ end
9
+
10
+ def newPost(id, user, pw, struct, publish)
11
+ $stderr.puts "id=#{id} user=#{user} pw=#{pw}, struct=#{struct.inspect} [#{publish}]"
12
+ (@postid += 1).to_s
13
+ end
14
+
15
+ def editPost(post_id, user, pw, struct, publish)
16
+ $stderr.puts "id=#{post_id} user=#{user} pw=#{pw} struct=#{struct.inspect} [#{publish}]"
17
+ true
18
+ end
19
+
20
+ def getPost(post_id, user, pw)
21
+ $stderr.puts "get post #{post_id}"
22
+ Blog::Post.new(:title => 'hello world', :description => 'first post!')
23
+ end
24
+
25
+ def getCategories(id, user, pw)
26
+ $stderr.puts "categories for #{user}"
27
+ cat = Blog::Category.new(
28
+ :description => 'Tech',
29
+ :htmlUrl => 'http://blog/tech',
30
+ :rssUrl => 'http://blog/tech.rss')
31
+ [cat]
32
+ end
33
+
34
+ def getRecentPosts(id, user, pw, num)
35
+ $stderr.puts "recent #{num} posts for #{user} on blog #{id}"
36
+ post1 = Blog::Post.new(
37
+ :title => 'first post!',
38
+ :link => 'http://blog.xeraph.org/testOne.html',
39
+ :description => 'this is the first post'
40
+ )
41
+ post2 = Blog::Post.new(
42
+ :title => 'second post!',
43
+ :link => 'http://blog.xeraph.org/testTwo.html',
44
+ :description => 'this is the second post'
45
+ )
46
+ [post1, post2]
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ #
2
+ # example controller implementing both blogger and metaWeblog APIs
3
+ # in a way that should be compatible with clients supporting both/either.
4
+ #
5
+ # test by pointing your client at http://URL/xmlrpc/api
6
+ #
7
+
8
+ require 'meta_weblog_service'
9
+ require 'blogger_service'
10
+
11
+ class XmlrpcController < ApplicationController
12
+ web_service_dispatching_mode :layered
13
+
14
+ web_service :metaWeblog, MetaWeblogService.new
15
+ web_service :blogger, BloggerService.new
16
+ end
@@ -0,0 +1,28 @@
1
+ Description:
2
+ The web service generator creates the controller and API definition for
3
+ a web service.
4
+
5
+ The generator takes a web service name and a list of API methods as arguments.
6
+ The web service name may be given in CamelCase or under_score and should
7
+ contain no extra suffixes. To create a web service within a
8
+ module, specify the web service name as 'module/webservice'.
9
+
10
+ The generator creates a controller class in app/controllers, an API definition
11
+ in app/apis, and a functional test suite in test/functional.
12
+
13
+ Example:
14
+ ./script/generate web_service User add edit list remove
15
+
16
+ User web service.
17
+ Controller: app/controllers/user_controller.rb
18
+ API: app/apis/user_api.rb
19
+ Test: test/functional/user_api_test.rb
20
+
21
+ Modules Example:
22
+ ./script/generate web_service 'api/registration' register renew
23
+
24
+ Registration web service.
25
+ Controller: app/controllers/api/registration_controller.rb
26
+ API: app/apis/api/registration_api.rb
27
+ Test: test/functional/api/registration_api_test.rb
28
+
@@ -0,0 +1,5 @@
1
+ class <%= class_name %>Api < ActionWebService::API::Base
2
+ <% for method_name in args -%>
3
+ api_method :<%= method_name %>
4
+ <% end -%>
5
+ end
@@ -0,0 +1,8 @@
1
+ class <%= class_name %>Controller < ApplicationController
2
+ wsdl_service_name '<%= class_name %>'
3
+ <% for method_name in args -%>
4
+
5
+ def <%= method_name %>
6
+ end
7
+ <% end -%>
8
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
+ require '<%= file_path %>_controller'
3
+
4
+ class <%= class_name %>Controller; def rescue_action(e) raise e end; end
5
+
6
+ class <%= class_name %>ControllerApiTest < Test::Unit::TestCase
7
+ def setup
8
+ @controller = <%= class_name %>Controller.new
9
+ @request = ActionController::TestRequest.new
10
+ @response = ActionController::TestResponse.new
11
+ end
12
+ <% for method_name in args -%>
13
+
14
+ def test_<%= method_name %>
15
+ result = invoke :<%= method_name %>
16
+ assert_equal nil, result
17
+ end
18
+ <% end -%>
19
+ end
@@ -0,0 +1,29 @@
1
+ class WebServiceGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ # Check for class naming collisions.
5
+ m.class_collisions class_path, "#{class_name}Api", "#{class_name}Controller", "#{class_name}ApiTest"
6
+
7
+ # API and test directories.
8
+ m.directory File.join('app/services', class_path)
9
+ m.directory File.join('app/controllers', class_path)
10
+ m.directory File.join('test/functional', class_path)
11
+
12
+ # API definition, controller, and functional test.
13
+ m.template 'api_definition.rb',
14
+ File.join('app/services',
15
+ class_path,
16
+ "#{file_name}_api.rb")
17
+
18
+ m.template 'controller.rb',
19
+ File.join('app/controllers',
20
+ class_path,
21
+ "#{file_name}_controller.rb")
22
+
23
+ m.template 'functional_test.rb',
24
+ File.join('test/functional',
25
+ class_path,
26
+ "#{file_name}_api_test.rb")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module ActionWebService
2
+ module ActsAsWebService
3
+
4
+ def self.included(base) # :nodoc:
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # Add this to your controller to include all ActionWebservice
10
+ def acts_as_web_service
11
+ include ActionWebService::Protocol::Discovery
12
+ include ActionWebService::Protocol::Soap
13
+ include ActionWebService::Protocol::XmlRpc
14
+ include ActionWebService::Container::Direct
15
+ include ActionWebService::Container::Delegated
16
+ include ActionWebService::Container::ActionController
17
+ include ActionWebService::Invocation
18
+ include ActionWebService::Dispatcher
19
+ include ActionWebService::Dispatcher::ActionController
20
+ include ActionWebService::Scaffolding
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,297 @@
1
+ module ActionWebService # :nodoc:
2
+ module API # :nodoc:
3
+ # A web service API class specifies the methods that will be available for
4
+ # invocation for an API. It also contains metadata such as the method type
5
+ # signature hints.
6
+ #
7
+ # It is not intended to be instantiated.
8
+ #
9
+ # It is attached to web service implementation classes like
10
+ # ActionWebService::Base and ActionController::Base derivatives by using
11
+ # <tt>container.web_service_api</tt>, where <tt>container</tt> is an
12
+ # ActionController::Base or a ActionWebService::Base.
13
+ #
14
+ # See ActionWebService::Container::Direct::ClassMethods for an example
15
+ # of use.
16
+ class Base
17
+ # Whether to transform the public API method names into camel-cased names
18
+ class_inheritable_option :inflect_names, true
19
+
20
+ # By default only HTTP POST requests are processed
21
+ class_inheritable_option :allowed_http_methods, [ :post ]
22
+
23
+ # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
24
+ # The default is +false+; you should be aware of the security implications
25
+ # of allowing this, and ensure that you don't allow remote callers to
26
+ # easily overwrite data they should not have access to.
27
+ class_inheritable_option :allow_active_record_expects, false
28
+
29
+ # If present, the name of a method to call when the remote caller
30
+ # tried to call a nonexistent method. Semantically equivalent to
31
+ # +method_missing+.
32
+ class_inheritable_option :default_api_method
33
+
34
+ # Disallow instantiation
35
+ private_class_method :new, :allocate
36
+
37
+ class << self
38
+ include ActionWebService::SignatureTypes
39
+
40
+ # API methods have a +name+, which must be the Ruby method name to use when
41
+ # performing the invocation on the web service object.
42
+ #
43
+ # The signatures for the method input parameters and return value can
44
+ # by specified in +options+.
45
+ #
46
+ # A signature is an array of one or more parameter specifiers.
47
+ # A parameter specifier can be one of the following:
48
+ #
49
+ # * A symbol or string representing one of the Action Web Service base types.
50
+ # See ActionWebService::SignatureTypes for a canonical list of the base types.
51
+ # * The Class object of the parameter type
52
+ # * A single-element Array containing one of the two preceding items. This
53
+ # will cause Action Web Service to treat the parameter at that position
54
+ # as an array containing only values of the given type.
55
+ # * A Hash containing as key the name of the parameter, and as value
56
+ # one of the three preceding items
57
+ #
58
+ # If no method input parameter or method return value signatures are given,
59
+ # the method is assumed to take no parameters and/or return no values of
60
+ # interest, and any values that are received by the server will be
61
+ # discarded and ignored.
62
+ #
63
+ # Valid options:
64
+ # [<tt>:expects</tt>] Signature for the method input parameters
65
+ # [<tt>:returns</tt>] Signature for the method return value
66
+ # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
67
+ def api_method(name, options={})
68
+ unless options.is_a?(Hash)
69
+ raise(ActionWebServiceError, "Expected a Hash for options")
70
+ end
71
+ validate_options([:expects, :returns, :expects_and_returns], options.keys)
72
+ if options[:expects_and_returns]
73
+ expects = options[:expects_and_returns]
74
+ returns = options[:expects_and_returns]
75
+ else
76
+ expects = options[:expects]
77
+ returns = options[:returns]
78
+ end
79
+ expects = canonical_signature(expects)
80
+ returns = canonical_signature(returns)
81
+ if expects
82
+ expects.each do |type|
83
+ type = type.element_type if type.is_a?(ArrayType)
84
+ if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
85
+ raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
86
+ end
87
+ end
88
+ end
89
+ name = name.to_sym
90
+ public_name = public_api_method_name(name)
91
+ method = Method.new(name, public_name, expects, returns)
92
+ write_inheritable_hash("api_methods", name => method)
93
+ write_inheritable_hash("api_public_method_names", public_name => name)
94
+ end
95
+
96
+ # Whether the given method name is a service method on this API
97
+ #
98
+ # class ProjectsApi < ActionWebService::API::Base
99
+ # api_method :getCount, :returns => [:int]
100
+ # end
101
+ #
102
+ # ProjectsApi.has_api_method?('GetCount') #=> false
103
+ # ProjectsApi.has_api_method?(:getCount) #=> true
104
+ def has_api_method?(name)
105
+ api_methods.has_key?(name)
106
+ end
107
+
108
+ # Whether the given public method name has a corresponding service method
109
+ # on this API
110
+ #
111
+ # class ProjectsApi < ActionWebService::API::Base
112
+ # api_method :getCount, :returns => [:int]
113
+ # end
114
+ #
115
+ # ProjectsApi.has_api_method?(:getCount) #=> false
116
+ # ProjectsApi.has_api_method?('GetCount') #=> true
117
+ def has_public_api_method?(public_name)
118
+ api_public_method_names.has_key?(public_name)
119
+ end
120
+
121
+ # The corresponding public method name for the given service method name
122
+ #
123
+ # ProjectsApi.public_api_method_name('GetCount') #=> "GetCount"
124
+ # ProjectsApi.public_api_method_name(:getCount) #=> "GetCount"
125
+ def public_api_method_name(name)
126
+ if inflect_names
127
+ name.to_s.camelize
128
+ else
129
+ name.to_s
130
+ end
131
+ end
132
+
133
+ # The corresponding service method name for the given public method name
134
+ #
135
+ # class ProjectsApi < ActionWebService::API::Base
136
+ # api_method :getCount, :returns => [:int]
137
+ # end
138
+ #
139
+ # ProjectsApi.api_method_name('GetCount') #=> :getCount
140
+ def api_method_name(public_name)
141
+ api_public_method_names[public_name]
142
+ end
143
+
144
+ # A Hash containing all service methods on this API, and their
145
+ # associated metadata.
146
+ #
147
+ # class ProjectsApi < ActionWebService::API::Base
148
+ # api_method :getCount, :returns => [:int]
149
+ # api_method :getCompletedCount, :returns => [:int]
150
+ # end
151
+ #
152
+ # ProjectsApi.api_methods #=>
153
+ # {:getCount=>#<ActionWebService::API::Method:0x24379d8 ...>,
154
+ # :getCompletedCount=>#<ActionWebService::API::Method:0x2437794 ...>}
155
+ # ProjectsApi.api_methods[:getCount].public_name #=> "GetCount"
156
+ def api_methods
157
+ read_inheritable_attribute("api_methods") || {}
158
+ end
159
+
160
+ # The Method instance for the given public API method name, if any
161
+ #
162
+ # class ProjectsApi < ActionWebService::API::Base
163
+ # api_method :getCount, :returns => [:int]
164
+ # api_method :getCompletedCount, :returns => [:int]
165
+ # end
166
+ #
167
+ # ProjectsApi.public_api_method_instance('GetCount') #=> <#<ActionWebService::API::Method:0x24379d8 ...>
168
+ # ProjectsApi.public_api_method_instance(:getCount) #=> nil
169
+ def public_api_method_instance(public_method_name)
170
+ api_method_instance(api_method_name(public_method_name))
171
+ end
172
+
173
+ # The Method instance for the given API method name, if any
174
+ #
175
+ # class ProjectsApi < ActionWebService::API::Base
176
+ # api_method :getCount, :returns => [:int]
177
+ # api_method :getCompletedCount, :returns => [:int]
178
+ # end
179
+ #
180
+ # ProjectsApi.api_method_instance(:getCount) #=> <ActionWebService::API::Method:0x24379d8 ...>
181
+ # ProjectsApi.api_method_instance('GetCount') #=> <ActionWebService::API::Method:0x24379d8 ...>
182
+ def api_method_instance(method_name)
183
+ api_methods[method_name]
184
+ end
185
+
186
+ # The Method instance for the default API method, if any
187
+ def default_api_method_instance
188
+ return nil unless name = default_api_method
189
+ instance = read_inheritable_attribute("default_api_method_instance")
190
+ if instance && instance.name == name
191
+ return instance
192
+ end
193
+ instance = Method.new(name, public_api_method_name(name), nil, nil)
194
+ write_inheritable_attribute("default_api_method_instance", instance)
195
+ instance
196
+ end
197
+
198
+ private
199
+ def api_public_method_names
200
+ read_inheritable_attribute("api_public_method_names") || {}
201
+ end
202
+
203
+ def validate_options(valid_option_keys, supplied_option_keys)
204
+ unknown_option_keys = supplied_option_keys - valid_option_keys
205
+ unless unknown_option_keys.empty?
206
+ raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ # Represents an API method and its associated metadata, and provides functionality
213
+ # to assist in commonly performed API method tasks.
214
+ class Method
215
+ attr :name
216
+ attr :public_name
217
+ attr :expects
218
+ attr :returns
219
+
220
+ def initialize(name, public_name, expects, returns)
221
+ @name = name
222
+ @public_name = public_name
223
+ @expects = expects
224
+ @returns = returns
225
+ @caster = ActionWebService::Casting::BaseCaster.new(self)
226
+ end
227
+
228
+ # The list of parameter names for this method
229
+ def param_names
230
+ return [] unless @expects
231
+ @expects.map{ |type| type.name }
232
+ end
233
+
234
+ # Casts a set of Ruby values into the expected Ruby values
235
+ def cast_expects(params)
236
+ @caster.cast_expects(params)
237
+ end
238
+
239
+ # Cast a Ruby return value into the expected Ruby value
240
+ def cast_returns(return_value)
241
+ @caster.cast_returns(return_value)
242
+ end
243
+
244
+ # Returns the index of the first expected parameter
245
+ # with the given name
246
+ def expects_index_of(param_name)
247
+ return -1 if @expects.nil?
248
+ (0..(@expects.length-1)).each do |i|
249
+ return i if @expects[i].name.to_s == param_name.to_s
250
+ end
251
+ -1
252
+ end
253
+
254
+ # Returns a hash keyed by parameter name for the given
255
+ # parameter list
256
+ def expects_to_hash(params)
257
+ return {} if @expects.nil?
258
+ h = {}
259
+ @expects.zip(params){ |type, param| h[type.name] = param }
260
+ h
261
+ end
262
+
263
+ # Backwards compatibility with previous API
264
+ def [](sig_type)
265
+ case sig_type
266
+ when :expects
267
+ @expects.map{|x| compat_signature_entry(x)}
268
+ when :returns
269
+ @returns.map{|x| compat_signature_entry(x)}
270
+ end
271
+ end
272
+
273
+ # String representation of this method
274
+ def to_s
275
+ fqn = ""
276
+ fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
277
+ fqn << "#{@public_name}("
278
+ fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
279
+ fqn << ")"
280
+ fqn
281
+ end
282
+
283
+ private
284
+ def compat_signature_entry(entry)
285
+ if entry.array?
286
+ [compat_signature_entry(entry.element_type)]
287
+ else
288
+ if entry.spec.is_a?(Hash)
289
+ {entry.spec.keys.first => entry.type_class}
290
+ else
291
+ entry.type_class
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,38 @@
1
+ module ActionWebService # :nodoc:
2
+ class ActionWebServiceError < StandardError # :nodoc:
3
+ end
4
+
5
+ # An Action Web Service object implements a specified API.
6
+ #
7
+ # Used by controllers operating in _Delegated_ dispatching mode.
8
+ #
9
+ # ==== Example
10
+ #
11
+ # class PersonService < ActionWebService::Base
12
+ # web_service_api PersonAPI
13
+ #
14
+ # def find_person(criteria)
15
+ # Person.find(:all) [...]
16
+ # end
17
+ #
18
+ # def delete_person(id)
19
+ # Person.find_by_id(id).destroy
20
+ # end
21
+ # end
22
+ #
23
+ # class PersonAPI < ActionWebService::API::Base
24
+ # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]]
25
+ # api_method :delete_person, :expects => [:int]
26
+ # end
27
+ #
28
+ # class SearchCriteria < ActionWebService::Struct
29
+ # member :firstname, :string
30
+ # member :lastname, :string
31
+ # member :email, :string
32
+ # end
33
+ class Base
34
+ # Whether to report exceptions back to the caller in the protocol's exception
35
+ # format
36
+ class_inheritable_option :web_service_exception_reporting, true
37
+ end
38
+ end
@@ -0,0 +1,151 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'xmlrpc/datetime'
4
+
5
+ module ActionWebService # :nodoc:
6
+ module Casting # :nodoc:
7
+ class CastingError < ActionWebServiceError # :nodoc:
8
+ end
9
+
10
+ # Performs casting of arbitrary values into the correct types for the signature
11
+ class BaseCaster # :nodoc:
12
+ def initialize(api_method)
13
+ @api_method = api_method
14
+ end
15
+
16
+ # Coerces the parameters in +params+ (an Enumerable) into the types
17
+ # this method expects
18
+ def cast_expects(params)
19
+ self.class.cast_expects(@api_method, params)
20
+ end
21
+
22
+ # Coerces the given +return_value+ into the type returned by this
23
+ # method
24
+ def cast_returns(return_value)
25
+ self.class.cast_returns(@api_method, return_value)
26
+ end
27
+
28
+ class << self
29
+ include ActionWebService::SignatureTypes
30
+
31
+ def cast_expects(api_method, params) # :nodoc:
32
+ return [] if api_method.expects.nil?
33
+ api_method.expects.zip(params).map{ |type, param| cast(param, type) }
34
+ end
35
+
36
+ def cast_returns(api_method, return_value) # :nodoc:
37
+ return nil if api_method.returns.nil?
38
+ cast(return_value, api_method.returns[0])
39
+ end
40
+
41
+ def cast(value, signature_type) # :nodoc:
42
+ return value if signature_type.nil? # signature.length != params.length
43
+ return nil if value.nil?
44
+ # XMLRPC protocol doesn't support nil values. It uses false instead.
45
+ # It should never happen for SOAP.
46
+ if signature_type.structured? && value.equal?(false)
47
+ return nil
48
+ end
49
+ unless signature_type.array? || signature_type.structured?
50
+ return value if canonical_type(value.class) == signature_type.type
51
+ end
52
+ if signature_type.array?
53
+ unless value.respond_to?(:entries) && !value.is_a?(String)
54
+ raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
55
+ end
56
+ value.entries.map do |entry|
57
+ cast(entry, signature_type.element_type)
58
+ end
59
+ elsif signature_type.simple?
60
+ return value
61
+ elsif signature_type.structured?
62
+ cast_to_structured_type(value, signature_type)
63
+ elsif !signature_type.custom?
64
+ cast_base_type(value, signature_type)
65
+ end
66
+ end
67
+
68
+ def cast_base_type(value, signature_type) # :nodoc:
69
+ # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type
70
+ # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time,
71
+ # with the caveat that we won't be able to handle pre-1970 dates that are sent to us.
72
+ #
73
+ # See http://dev.rubyonrails.com/ticket/2516
74
+ value = value.to_time if value.is_a?(XMLRPC::DateTime)
75
+
76
+ case signature_type.type
77
+ when :int
78
+ Integer(value)
79
+ when :string
80
+ value.to_s
81
+ when :base64
82
+ if value.is_a?(ActionWebService::Base64)
83
+ value
84
+ else
85
+ ActionWebService::Base64.new(value.to_s)
86
+ end
87
+ when :bool
88
+ return false if value.nil?
89
+ return value if value == true || value == false
90
+ case value.to_s.downcase
91
+ when '1', 'true', 'y', 'yes'
92
+ true
93
+ when '0', 'false', 'n', 'no'
94
+ false
95
+ else
96
+ raise CastingError, "Don't know how to cast #{value.class} into Boolean"
97
+ end
98
+ when :float
99
+ Float(value)
100
+ when :decimal
101
+ BigDecimal(value.to_s)
102
+ when :time
103
+ value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash)
104
+ if value.kind_of?(Time)
105
+ value
106
+ elsif value.kind_of?(DateTime)
107
+ value.to_time
108
+ else
109
+ Time.parse(value.to_s)
110
+ end
111
+ when :date
112
+ value = "%s/%s/%s" % value.values_at(*%w[2 3 1]) if value.kind_of?(Hash)
113
+ value.kind_of?(Date) ? value : Date.parse(value.to_s)
114
+ when :datetime
115
+ value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash)
116
+ value.kind_of?(DateTime) ? value : DateTime.parse(value.to_s)
117
+ end
118
+ end
119
+
120
+ def cast_to_structured_type(value, signature_type) # :nodoc:
121
+ obj = nil
122
+ # if the canonical classes are the same or if the given value is of
123
+ # a type that is derived from the signature_type do not attempt to
124
+ # "cast" the value into the signature_type as it's already good to go
125
+ obj = (
126
+ canonical_type(value.class) == canonical_type(signature_type.type) or
127
+ derived_from?(signature_type.type, value.class)
128
+ ) ? value : signature_type.type_class.new
129
+ if value.respond_to?(:each_pair)
130
+ klass = signature_type.type_class
131
+ value.each_pair do |name, val|
132
+ type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
133
+ val = cast(val, type) if type
134
+ # See http://dev.rubyonrails.com/ticket/3567
135
+ val = val.to_time if val.is_a?(XMLRPC::DateTime)
136
+ obj.__send__("#{name}=", val) if obj.respond_to?(name)
137
+ end
138
+ elsif value.respond_to?(:attributes)
139
+ signature_type.each_member do |name, type|
140
+ val = value.__send__(name)
141
+ obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name)
142
+ end
143
+ else
144
+ raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
145
+ end
146
+ obj
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end