actionwebservice 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +21 -0
  2. data/README +50 -6
  3. data/Rakefile +9 -9
  4. data/TODO +6 -6
  5. data/lib/action_web_service.rb +4 -3
  6. data/lib/action_web_service/api.rb +248 -1
  7. data/lib/action_web_service/casting.rb +111 -0
  8. data/lib/action_web_service/client/soap_client.rb +17 -33
  9. data/lib/action_web_service/client/xmlrpc_client.rb +10 -34
  10. data/lib/action_web_service/container/delegated_container.rb +1 -1
  11. data/lib/action_web_service/dispatcher/abstract.rb +52 -72
  12. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +71 -55
  13. data/lib/action_web_service/protocol/abstract.rb +82 -3
  14. data/lib/action_web_service/protocol/discovery.rb +2 -2
  15. data/lib/action_web_service/protocol/soap_protocol.rb +95 -22
  16. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +197 -0
  17. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +56 -24
  18. data/lib/action_web_service/scaffolding.rb +237 -0
  19. data/lib/action_web_service/struct.rb +17 -4
  20. data/lib/action_web_service/support/signature_types.rb +194 -0
  21. data/lib/action_web_service/templates/scaffolds/layout.rhtml +65 -0
  22. data/lib/action_web_service/templates/scaffolds/methods.rhtml +6 -0
  23. data/lib/action_web_service/templates/scaffolds/parameters.rhtml +28 -0
  24. data/lib/action_web_service/templates/scaffolds/result.rhtml +30 -0
  25. data/lib/action_web_service/test_invoke.rb +23 -42
  26. data/test/abstract_dispatcher.rb +102 -48
  27. data/test/abstract_unit.rb +1 -1
  28. data/test/api_test.rb +40 -7
  29. data/test/casting_test.rb +82 -0
  30. data/test/client_soap_test.rb +3 -0
  31. data/test/client_xmlrpc_test.rb +5 -1
  32. data/test/dispatcher_action_controller_soap_test.rb +9 -12
  33. data/test/dispatcher_action_controller_xmlrpc_test.rb +1 -11
  34. data/test/run +1 -0
  35. data/test/scaffolded_controller_test.rb +67 -0
  36. data/test/struct_test.rb +33 -21
  37. data/test/test_invoke_test.rb +1 -1
  38. metadata +18 -31
  39. data/lib/action_web_service/api/base.rb +0 -135
  40. data/lib/action_web_service/vendor/ws.rb +0 -4
  41. data/lib/action_web_service/vendor/ws/common.rb +0 -8
  42. data/lib/action_web_service/vendor/ws/encoding.rb +0 -3
  43. data/lib/action_web_service/vendor/ws/encoding/abstract.rb +0 -26
  44. data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +0 -90
  45. data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +0 -53
  46. data/lib/action_web_service/vendor/ws/marshaling.rb +0 -3
  47. data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +0 -17
  48. data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +0 -277
  49. data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +0 -116
  50. data/lib/action_web_service/vendor/ws/types.rb +0 -165
  51. data/test/ws/abstract_encoding.rb +0 -68
  52. data/test/ws/abstract_unit.rb +0 -13
  53. data/test/ws/gencov +0 -3
  54. data/test/ws/run +0 -5
  55. data/test/ws/soap_marshaling_test.rb +0 -91
  56. data/test/ws/soap_rpc_encoding_test.rb +0 -47
  57. data/test/ws/types_test.rb +0 -43
  58. data/test/ws/xmlrpc_encoding_test.rb +0 -34
data/CHANGELOG CHANGED
@@ -1,3 +1,24 @@
1
+ *0.7.0* (19th April, 2005)
2
+
3
+ * When casting structured types, don't try to send obj.name= unless obj responds to it, causes casting to be less likely to fail for XML-RPC
4
+
5
+ * Add scaffolding via ActionController::Base.web_service_scaffold for quick testing using a web browser
6
+
7
+ * ActionWebService::API::Base#api_methods now returns a hash containing ActionWebService::API::Method objects instead of hashes. However, ActionWebService::API::Method defines a #[]() backwards compatibility method so any existing code utilizing this will still work.
8
+
9
+ * The :layered dispatching mode can now be used with SOAP as well, allowing you to support SOAP and XML-RPC clients for APIs like the metaWeblog API
10
+
11
+ * Remove ActiveRecordSoapMarshallable workaround, see #912 for details
12
+
13
+ * Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
14
+
15
+ * Ensure return value is properly cast as well, fixes XML-RPC interoperability with Ecto and possibly other clients
16
+
17
+ * Include backtraces in 500 error responses for failed request parsing, and remove "rescue nil" statements obscuring real errors for XML-RPC
18
+
19
+ * Perform casting of struct members even if the structure is already of the correct type, so that the type we specify for the struct member is always the type of the value seen by the API implementation
20
+
21
+
1
22
  *0.6.2* (27th March, 2005)
2
23
 
3
24
  * Allow method declarations for direct dispatching to declare parameters as well. We treat an arity of < 0 or > 0 as an indication that we should send through parameters. Closes #939.
data/README CHANGED
@@ -162,11 +162,10 @@ This mode is similar to _delegated_ mode, in that multiple web service objects
162
162
  can be attached to one controller, however, all protocol requests are sent to a
163
163
  single endpoint.
164
164
 
165
- This mode is only usable by XML-RPC. In this mode, method names can contain
166
- _prefixes_, which will indicate which web service object implements the API
167
- identified by that prefix.
165
+ Use this mode when you want to share code between XML-RPC and SOAP clients,
166
+ for APIs where the XML-RPC method names have prefixes. An example of such
167
+ a method name would be <tt>blogger.newPost</tt>.
168
168
 
169
- The _prefix_ can be any word, followed by a period.
170
169
 
171
170
  ==== Layered dispatching example
172
171
 
@@ -192,11 +191,56 @@ The _prefix_ can be any word, followed by a period.
192
191
  end
193
192
 
194
193
 
195
- For this example, a remote call for a method with a name like
196
- <tt>mt.getCategories</tt> will be dispatched as the <tt>getCategories</tt>
194
+ For this example, an XML-RPC call for a method with a name like
195
+ <tt>mt.getCategories</tt> will be sent to the <tt>getCategories</tt>
197
196
  method on the <tt>:mt</tt> service.
198
197
 
199
198
 
199
+ == Testing your APIs
200
+
201
+
202
+ === Functional testing
203
+
204
+ You can perform testing of your APIs by creating a functional test for the
205
+ controller dispatching the API, and calling #invoke in the test case to
206
+ perform the invocation.
207
+
208
+ Example:
209
+
210
+ class PersonApiControllerTest < Test::Unit::TestCase
211
+ def setup
212
+ @controller = PersonController.new
213
+ @request = ActionController::TestRequest.new
214
+ @response = ActionController::TestResponse.new
215
+ end
216
+
217
+ def test_add
218
+ result = invoke :remove, 1
219
+ assert_equal true, result
220
+ end
221
+ end
222
+
223
+ This example invokes the API method <tt>test</tt>, defined on
224
+ the PersonController, and returns the result.
225
+
226
+
227
+ === Scaffolding
228
+
229
+ You can also test your APIs with a web browser by attaching scaffolding
230
+ to the controller.
231
+
232
+ Example:
233
+
234
+ class PersonController
235
+ web_service_scaffold :invocation
236
+ end
237
+
238
+ This creates an action named <tt>invocation</tt> on the PersonController.
239
+
240
+ Navigating to this action lets you select the method to invoke, supply the parameters,
241
+ and view the result of the invocation.
242
+
243
+
200
244
  == Using the client support
201
245
 
202
246
  Action Web Service includes client classes that can use the same API
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ require 'fileutils'
9
9
 
10
10
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
11
11
  PKG_NAME = 'actionwebservice'
12
- PKG_VERSION = '0.6.2' + PKG_BUILD
12
+ PKG_VERSION = '0.7.0' + PKG_BUILD
13
13
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
14
14
  PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}"
15
15
 
@@ -25,7 +25,7 @@ task :default => [ :test ]
25
25
  # Run the unit tests
26
26
  Rake::TestTask.new { |t|
27
27
  t.libs << "test"
28
- t.test_files = Dir['test/*_test.rb'] + Dir['test/ws/*_test.rb']
28
+ t.test_files = Dir['test/*_test.rb']
29
29
  t.verbose = true
30
30
  }
31
31
 
@@ -62,9 +62,9 @@ spec = Gem::Specification.new do |s|
62
62
  s.rubyforge_project = "aws"
63
63
  s.homepage = "http://www.rubyonrails.org"
64
64
 
65
- s.add_dependency('actionpack', '= 1.7.0' + PKG_BUILD)
66
- s.add_dependency('activerecord', '= 1.9.1' + PKG_BUILD)
67
- s.add_dependency('activesupport', '= 1.0.3' + PKG_BUILD)
65
+ s.add_dependency('actionpack', '= 1.8.0' + PKG_BUILD)
66
+ s.add_dependency('activerecord', '= 1.10.0' + PKG_BUILD)
67
+ s.add_dependency('activesupport', '= 1.0.4' + PKG_BUILD)
68
68
 
69
69
  s.has_rdoc = true
70
70
  s.requirements << 'none'
@@ -86,14 +86,14 @@ end
86
86
  # Publish beta gem
87
87
  desc "Publish the API documentation"
88
88
  task :pgem => [:package] do
89
- Rake::SshFilePublisher.new("davidhh@comox.textdrive.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
90
- `ssh davidhh@comox.textdrive.com './gemupdate.sh'`
89
+ Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
90
+ `ssh davidhh@wrath.rubyonrails.com './gemupdate.sh'`
91
91
  end
92
92
 
93
93
  # Publish documentation
94
94
  desc "Publish the API documentation"
95
95
  task :pdoc => [:rdoc] do
96
- Rake::SshDirPublisher.new("davidhh@comox.textdrive.com", "public_html/aws", "doc").upload
96
+ Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.com", "public_html/aws", "doc").upload
97
97
  end
98
98
 
99
99
 
@@ -264,4 +264,4 @@ task :release => [:package] do
264
264
  first_file = false
265
265
  end
266
266
  end
267
- end
267
+ end
data/TODO CHANGED
@@ -1,11 +1,11 @@
1
- = 0.7.0
2
- - WS Dynamic Scaffolding
3
- - WS Scaffolding Generators
4
-
5
1
  = 0.8.0
6
- - Consumption of WSDL services
2
+ - Allow locking down a controller to only accept messages for a particular protocol.
3
+ This will allow us to generate fully conformant error messages in cases where we
4
+ currently fudge it if we don't know the protocol.
5
+
6
+ - Allow AWS user to participate in typecasting, so they can centralize workarounds
7
+ for buggy input in one place
7
8
 
8
9
  = Refactoring
9
- - Port dispatcher tests to use test_invoke
10
10
  - Don't have clean way to go from SOAP Class object to the xsd:NAME type
11
11
  string -- NaHi possibly looking at remedying this situation
@@ -35,17 +35,17 @@ end
35
35
  $:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
36
36
 
37
37
  require 'action_web_service/support/class_inheritable_options'
38
- require 'action_web_service/vendor/ws'
39
-
38
+ require 'action_web_service/support/signature_types'
40
39
  require 'action_web_service/base'
41
40
  require 'action_web_service/client'
42
41
  require 'action_web_service/invocation'
43
42
  require 'action_web_service/api'
43
+ require 'action_web_service/casting'
44
44
  require 'action_web_service/struct'
45
45
  require 'action_web_service/container'
46
46
  require 'action_web_service/protocol'
47
- require 'action_web_service/struct'
48
47
  require 'action_web_service/dispatcher'
48
+ require 'action_web_service/scaffolding'
49
49
 
50
50
  ActionWebService::Base.class_eval do
51
51
  include ActionWebService::Container::Direct
@@ -61,4 +61,5 @@ ActionController::Base.class_eval do
61
61
  include ActionWebService::Container::ActionController
62
62
  include ActionWebService::Dispatcher
63
63
  include ActionWebService::Dispatcher::ActionController
64
+ include ActionWebService::Scaffolding
64
65
  end
@@ -1 +1,248 @@
1
- require 'action_web_service/api/base'
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
+ # ClassMethods#web_service_api.
12
+ class Base
13
+ # Whether to transform the public API method names into camel-cased names
14
+ class_inheritable_option :inflect_names, true
15
+
16
+ # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
17
+ # The default is +false+, you should be aware of the security implications
18
+ # of allowing this, and ensure that you don't allow remote callers to
19
+ # easily overwrite data they should not have access to.
20
+ class_inheritable_option :allow_active_record_expects, false
21
+
22
+ # If present, the name of a method to call when the remote caller
23
+ # tried to call a nonexistent method. Semantically equivalent to
24
+ # +method_missing+.
25
+ class_inheritable_option :default_api_method
26
+
27
+ # Disallow instantiation
28
+ private_class_method :new, :allocate
29
+
30
+ class << self
31
+ include ActionWebService::SignatureTypes
32
+
33
+ # API methods have a +name+, which must be the Ruby method name to use when
34
+ # performing the invocation on the web service object.
35
+ #
36
+ # The signatures for the method input parameters and return value can
37
+ # by specified in +options+.
38
+ #
39
+ # A signature is an array of one or more parameter specifiers.
40
+ # A parameter specifier can be one of the following:
41
+ #
42
+ # * A symbol or string of representing one of the Action Web Service base types.
43
+ # See ActionWebService::Signature for a canonical list of the base types.
44
+ # * The Class object of the parameter type
45
+ # * A single-element Array containing one of the two preceding items. This
46
+ # will cause Action Web Service to treat the parameter at that position
47
+ # as an array containing only values of the given type.
48
+ # * A Hash containing as key the name of the parameter, and as value
49
+ # one of the three preceding items
50
+ #
51
+ # If no method input parameter or method return value signatures are given,
52
+ # the method is assumed to take no parameters and/or return no values of
53
+ # interest, and any values that are received by the server will be
54
+ # discarded and ignored.
55
+ #
56
+ # Valid options:
57
+ # [<tt>:expects</tt>] Signature for the method input parameters
58
+ # [<tt>:returns</tt>] Signature for the method return value
59
+ # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
60
+ def api_method(name, options={})
61
+ unless options.is_a?(Hash)
62
+ raise(ActionWebServiceError, "Expected a Hash for options")
63
+ end
64
+ validate_options([:expects, :returns, :expects_and_returns], options.keys)
65
+ if options[:expects_and_returns]
66
+ expects = options[:expects_and_returns]
67
+ returns = options[:expects_and_returns]
68
+ else
69
+ expects = options[:expects]
70
+ returns = options[:returns]
71
+ end
72
+ expects = canonical_signature(expects)
73
+ returns = canonical_signature(returns)
74
+ if expects
75
+ expects.each do |type|
76
+ type = type.element_type if type.is_a?(ArrayType)
77
+ if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
78
+ raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
79
+ end
80
+ end
81
+ end
82
+ name = name.to_sym
83
+ public_name = public_api_method_name(name)
84
+ method = Method.new(name, public_name, expects, returns)
85
+ write_inheritable_hash("api_methods", name => method)
86
+ write_inheritable_hash("api_public_method_names", public_name => name)
87
+ end
88
+
89
+ # Whether the given method name is a service method on this API
90
+ def has_api_method?(name)
91
+ api_methods.has_key?(name)
92
+ end
93
+
94
+ # Whether the given public method name has a corresponding service method
95
+ # on this API
96
+ def has_public_api_method?(public_name)
97
+ api_public_method_names.has_key?(public_name)
98
+ end
99
+
100
+ # The corresponding public method name for the given service method name
101
+ def public_api_method_name(name)
102
+ if inflect_names
103
+ name.to_s.camelize
104
+ else
105
+ name.to_s
106
+ end
107
+ end
108
+
109
+ # The corresponding service method name for the given public method name
110
+ def api_method_name(public_name)
111
+ api_public_method_names[public_name]
112
+ end
113
+
114
+ # A Hash containing all service methods on this API, and their
115
+ # associated metadata.
116
+ def api_methods
117
+ read_inheritable_attribute("api_methods") || {}
118
+ end
119
+
120
+ # The Method instance for the given public API method name, if any
121
+ def public_api_method_instance(public_method_name)
122
+ api_method_instance(api_method_name(public_method_name))
123
+ end
124
+
125
+ # The Method instance for the given API method name, if any
126
+ def api_method_instance(method_name)
127
+ api_methods[method_name]
128
+ end
129
+
130
+ # The Method instance for the default API method, if any
131
+ def default_api_method_instance
132
+ return nil unless name = default_api_method
133
+ instance = read_inheritable_attribute("default_api_method_instance")
134
+ if instance && instance.name == name
135
+ return instance
136
+ end
137
+ instance = Method.new(name, public_api_method_name(name), nil, nil)
138
+ write_inheritable_attribute("default_api_method_instance", instance)
139
+ instance
140
+ end
141
+
142
+ private
143
+ def api_public_method_names
144
+ read_inheritable_attribute("api_public_method_names") || {}
145
+ end
146
+
147
+ def validate_options(valid_option_keys, supplied_option_keys)
148
+ unknown_option_keys = supplied_option_keys - valid_option_keys
149
+ unless unknown_option_keys.empty?
150
+ raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Represents an API method and its associated metadata, and provides functionality
157
+ # to assist in commonly performed API method tasks.
158
+ class Method
159
+ attr :name
160
+ attr :public_name
161
+ attr :expects
162
+ attr :returns
163
+
164
+ def initialize(name, public_name, expects, returns)
165
+ @name = name
166
+ @public_name = public_name
167
+ @expects = expects
168
+ @returns = returns
169
+ @caster = ActionWebService::Casting::BaseCaster.new(self)
170
+ end
171
+
172
+ # The list of parameter names for this method
173
+ def param_names
174
+ return [] unless @expects
175
+ @expects.map{ |type| type.name }
176
+ end
177
+
178
+ # Casts a set of Ruby values into the expected Ruby values
179
+ def cast_expects(params)
180
+ @caster.cast_expects(params)
181
+ end
182
+
183
+ # Cast a Ruby return value into the expected Ruby value
184
+ def cast_returns(return_value)
185
+ @caster.cast_returns(return_value)
186
+ end
187
+
188
+ # Returns the index of the first expected parameter
189
+ # with the given name
190
+ def expects_index_of(param_name)
191
+ return -1 if @expects.nil?
192
+ (0..(@expects.length-1)).each do |i|
193
+ return i if @expects[i].name.to_s == param_name.to_s
194
+ end
195
+ -1
196
+ end
197
+
198
+ # Returns a hash keyed by parameter name for the given
199
+ # parameter list
200
+ def expects_to_hash(params)
201
+ return {} if @expects.nil?
202
+ h = {}
203
+ @expects.zip(params){ |type, param| h[type.name] = param }
204
+ h
205
+ end
206
+
207
+ # Backwards compatibility with previous API
208
+ def [](sig_type)
209
+ case sig_type
210
+ when :expects
211
+ @expects.map{|x| compat_signature_entry(x)}
212
+ when :returns
213
+ @returns.map{|x| compat_signature_entry(x)}
214
+ end
215
+ end
216
+
217
+ # String representation of this method
218
+ def to_s
219
+ fqn = ""
220
+ fqn << (@returns ? (friendly_param(@returns[0], false) + " ") : "void ")
221
+ fqn << "#{@public_name}("
222
+ fqn << @expects.map{ |p| friendly_param(p) }.join(", ") if @expects
223
+ fqn << ")"
224
+ fqn
225
+ end
226
+
227
+ private
228
+ def compat_signature_entry(entry)
229
+ if entry.array?
230
+ [compat_signature_entry(entry.element_type)]
231
+ else
232
+ if entry.spec.is_a?(Hash)
233
+ {entry.spec.keys.first => entry.type_class}
234
+ else
235
+ entry.type_class
236
+ end
237
+ end
238
+ end
239
+
240
+ def friendly_param(type, show_name=true)
241
+ name = type.name.to_s
242
+ type_type = type.array?? type.element_type.type.to_s : type.type.to_s
243
+ str = type.array?? (type_type + '[]') : type_type
244
+ show_name ? (str + " " + name) : str
245
+ end
246
+ end
247
+ end
248
+ end