keeguon-actionwebservice 3.0.1

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