nogara-wash_out 0.5.2

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 (59) hide show
  1. data/.gitignore +9 -0
  2. data/.travis.yml +3 -0
  3. data/Appraisals +7 -0
  4. data/CHANGELOG.md +79 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +118 -0
  7. data/LICENSE +22 -0
  8. data/README.md +181 -0
  9. data/Rakefile +14 -0
  10. data/app/helpers/wash_out_helper.rb +64 -0
  11. data/app/views/wash_with_soap/error.builder +10 -0
  12. data/app/views/wash_with_soap/response.builder +13 -0
  13. data/app/views/wash_with_soap/wsdl.builder +68 -0
  14. data/gemfiles/rails-3.0.11.gemfile +8 -0
  15. data/gemfiles/rails-3.0.11.gemfile.lock +130 -0
  16. data/gemfiles/rails-3.1.3.gemfile +8 -0
  17. data/gemfiles/rails-3.1.3.gemfile.lock +141 -0
  18. data/init.rb +1 -0
  19. data/lib/wash_out.rb +35 -0
  20. data/lib/wash_out/dispatcher.rb +206 -0
  21. data/lib/wash_out/engine.rb +30 -0
  22. data/lib/wash_out/model.rb +25 -0
  23. data/lib/wash_out/param.rb +176 -0
  24. data/lib/wash_out/router.rb +36 -0
  25. data/lib/wash_out/soap.rb +40 -0
  26. data/lib/wash_out/type.rb +28 -0
  27. data/lib/wash_out/version.rb +3 -0
  28. data/lib/wash_out/wsse.rb +79 -0
  29. data/spec/dummy/Rakefile +7 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  31. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  32. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/spec/dummy/config.ru +4 -0
  34. data/spec/dummy/config/application.rb +42 -0
  35. data/spec/dummy/config/boot.rb +10 -0
  36. data/spec/dummy/config/environment.rb +5 -0
  37. data/spec/dummy/config/environments/development.rb +23 -0
  38. data/spec/dummy/config/environments/test.rb +30 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/inflections.rb +10 -0
  41. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  42. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  43. data/spec/dummy/config/initializers/session_store.rb +8 -0
  44. data/spec/dummy/config/locales/en.yml +5 -0
  45. data/spec/dummy/config/routes.rb +58 -0
  46. data/spec/dummy/public/404.html +26 -0
  47. data/spec/dummy/public/422.html +26 -0
  48. data/spec/dummy/public/500.html +26 -0
  49. data/spec/dummy/public/favicon.ico +0 -0
  50. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  51. data/spec/dummy/script/rails +6 -0
  52. data/spec/spec_helper.rb +51 -0
  53. data/spec/support/httpi-rack.rb +46 -0
  54. data/spec/wash_out/dispatcher_spec.rb +65 -0
  55. data/spec/wash_out/param_spec.rb +26 -0
  56. data/spec/wash_out/type_spec.rb +23 -0
  57. data/spec/wash_out_spec.rb +686 -0
  58. data/wash_out.gemspec +21 -0
  59. metadata +183 -0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'wash_out'
@@ -0,0 +1,35 @@
1
+ require 'wash_out/engine'
2
+ require 'wash_out/param'
3
+ require 'wash_out/dispatcher'
4
+ require 'wash_out/soap'
5
+ require 'wash_out/router'
6
+ require 'wash_out/type'
7
+ require 'wash_out/model'
8
+ require 'wash_out/wsse'
9
+
10
+ module ActionDispatch::Routing
11
+ class Mapper
12
+ # Adds the routes for a SOAP endpoint at +controller+.
13
+ def wash_out(controller_name, options={})
14
+ options.reverse_merge!(@scope) if @scope
15
+ controller_class_name = [options[:module], controller_name].compact.join("/")
16
+
17
+ match "#{controller_name}/wsdl" => "#{controller_name}#_generate_wsdl", :via => :get, :format => false
18
+ match "#{controller_name}/action" => WashOut::Router.new(controller_class_name), :defaults => { :controller => controller_class_name, :action => '_action' }, :format => false
19
+ end
20
+ end
21
+ end
22
+
23
+ Mime::Type.register "application/soap+xml", :soap
24
+ ActiveRecord::Base.send :extend, WashOut::Model if defined?(ActiveRecord)
25
+
26
+ ActionController::Renderers.add :soap do |what, options|
27
+ _render_soap(what, options)
28
+ end
29
+
30
+ module ActionView
31
+ class Base
32
+ cattr_accessor :washout_namespace
33
+ @@washout_namespace = false
34
+ end
35
+ end
@@ -0,0 +1,206 @@
1
+ require 'nori'
2
+
3
+ module WashOut
4
+ # The WashOut::Dispatcher module should be included in a controller acting
5
+ # as a SOAP endpoint. It includes actions for generating WSDL and handling
6
+ # SOAP requests.
7
+ module Dispatcher
8
+ # A SOAPError exception can be raised to return a correct SOAP error
9
+ # response.
10
+ class SOAPError < Exception; end
11
+ class ProgrammerError < Exception; end
12
+
13
+ # This filter parses the SOAP request and puts it into +params+ array.
14
+ def _parse_soap_parameters
15
+ # Do not interfere with project-space Nori setup
16
+ strip = Nori.strip_namespaces?
17
+ convert = Nori.convert_tags?
18
+ typecast = Nori.advanced_typecasting?
19
+
20
+ Nori.strip_namespaces = true
21
+ Nori.advanced_typecasting = false
22
+
23
+ if WashOut::Engine.snakecase_input
24
+ Nori.convert_tags_to { |tag| tag.snakecase.to_sym }
25
+ else
26
+ Nori.convert_tags_to { |tag| tag.to_sym }
27
+ end
28
+
29
+ request_body = request.body.read
30
+ @_params = Nori.parse(request_body)
31
+
32
+ references = WashOut::Dispatcher.deep_select(@_params){|k,v| v.is_a?(Hash) && v.has_key?(:@id)}
33
+
34
+ unless references.blank?
35
+ replaces = {}; references.each{|r| replaces['#'+r[:@id]] = r}
36
+ @_params = WashOut::Dispatcher.deep_replace_href(@_params, replaces)
37
+ end
38
+
39
+ # Reset Nori setup to project-space
40
+ Nori.strip_namespaces = strip
41
+ Nori.advanced_typecasting = typecast
42
+ Nori.convert_tags_to convert
43
+ end
44
+
45
+ def _authenticate_wsse
46
+ begin
47
+ xml_security = @_params.values_at(:envelope, :Envelope).compact.first
48
+ xml_security = xml_security.values_at(:header, :Header).compact.first
49
+ xml_security = xml_security.values_at(:security, :Security).compact.first
50
+ username_token = xml_security.values_at(:username_token, :UsernameToken).compact.first
51
+ rescue
52
+ username_token = nil
53
+ end
54
+
55
+ WashOut::Wsse.authenticate username_token
56
+
57
+ request.env['WSSE_TOKEN'] = username_token.with_indifferent_access unless username_token.blank?
58
+ end
59
+
60
+ def _map_soap_parameters
61
+ soap_action = request.env['wash_out.soap_action']
62
+ action_spec = self.class.soap_actions[soap_action]
63
+
64
+ xml_data = @_params.values_at(:envelope, :Envelope).compact.first
65
+ xml_data = xml_data.values_at(:body, :Body).compact.first
66
+ xml_data = xml_data.values_at(soap_action.underscore.to_sym,
67
+ soap_action.to_sym).compact.first || {}
68
+
69
+ strip_empty_nodes = lambda{|hash|
70
+ hash.each do |key, value|
71
+ if value.is_a? Hash
72
+ value = value.delete_if{|key, value| key.to_s[0] == '@'}
73
+
74
+ if value.length > 0
75
+ hash[key] = strip_empty_nodes.call(value)
76
+ else
77
+ hash[key] = nil
78
+ end
79
+ end
80
+ end
81
+
82
+ hash
83
+ }
84
+ xml_data = strip_empty_nodes.call(xml_data)
85
+ @_params = HashWithIndifferentAccess.new
86
+
87
+ action_spec[:in].each do |param|
88
+ key = param.raw_name.to_sym
89
+
90
+ if xml_data.has_key? key
91
+ @_params[param.raw_name] = param.load(xml_data, key)
92
+ end
93
+ end
94
+ end
95
+
96
+ # This action generates the WSDL for defined SOAP methods.
97
+ def _generate_wsdl
98
+ @map = self.class.soap_actions
99
+ @namespace = WashOut::Engine.namespace
100
+ @name = controller_path.gsub('/', '_')
101
+
102
+ render :template => 'wash_with_soap/wsdl', :layout => false
103
+ end
104
+
105
+ # Render a SOAP response.
106
+ def _render_soap(result, options)
107
+ @namespace = WashOut::Engine.namespace
108
+ @operation = soap_action = request.env['wash_out.soap_action']
109
+ action_spec = self.class.soap_actions[soap_action][:out]
110
+
111
+ result = { 'value' => result } unless result.is_a? Hash
112
+ result = HashWithIndifferentAccess.new(result)
113
+
114
+ inject = lambda {|data, map|
115
+ result_spec = []
116
+
117
+ map.each_with_index do |param, i|
118
+ result_spec[i] = param.flat_copy
119
+
120
+ # Inline complex structure
121
+ if param.struct? && !param.multiplied
122
+ result_spec[i].map = inject.call(data[param.raw_name], param.map)
123
+
124
+ # Inline array of complex structures
125
+ elsif param.struct? && param.multiplied
126
+ if data.nil?
127
+ data = {} # when no data is given
128
+ elsif data.is_a?(Array)
129
+ raise ProgrammerError,
130
+ "SOAP response used #{data.inspect} (which is an Array), " +
131
+ "in the context where a Hash with key of '#{param.raw_name}' " +
132
+ "was expected."
133
+ end
134
+ data[param.raw_name] = [] unless data[param.raw_name].is_a?(Array)
135
+ result_spec[i].map = data[param.raw_name].map{|e| inject.call(e, param.map)}
136
+
137
+ else
138
+ val = data[param.raw_name]
139
+ if param.multiplied and val and not val.is_a?(Array)
140
+ raise ProgrammerError,
141
+ "SOAP response tried to use '#{val.inspect}' " +
142
+ "(which is of type #{val.class}), as the value for " +
143
+ "'#{param.raw_name}' (which expects an Array)."
144
+ end
145
+ result_spec[i].value = val
146
+ end
147
+ end
148
+
149
+ return result_spec
150
+ }
151
+
152
+ render :template => 'wash_with_soap/response',
153
+ :layout => false,
154
+ :locals => { :result => inject.call(result, action_spec) },
155
+ :content_type => 'text/xml'
156
+ end
157
+
158
+ # This action is a fallback for all undefined SOAP actions.
159
+ def _invalid_action
160
+ render_soap_error("Cannot find SOAP action mapping for #{request.env['wash_out.soap_action']}")
161
+ end
162
+
163
+ def _render_soap_exception(error)
164
+ render_soap_error(error.message)
165
+ end
166
+
167
+ # Render a SOAP error response.
168
+ #
169
+ # Rails do not support sequental rescue_from handling, that is, rescuing an
170
+ # exception from a rescue_from handler. Hence this function is a public API.
171
+ def render_soap_error(message)
172
+ render :template => 'wash_with_soap/error', :status => 500,
173
+ :layout => false,
174
+ :locals => { :error_message => message },
175
+ :content_type => 'text/xml'
176
+ end
177
+
178
+ def self.included(controller)
179
+ controller.send :rescue_from, SOAPError, :with => :_render_soap_exception
180
+ controller.send :helper, :wash_out
181
+ controller.send :before_filter, :_parse_soap_parameters, :except => [ :_generate_wsdl, :_invalid_action ]
182
+ controller.send :before_filter, :_authenticate_wsse, :except => [ :_generate_wsdl, :_invalid_action ]
183
+ controller.send :before_filter, :_map_soap_parameters, :except => [ :_generate_wsdl, :_invalid_action ]
184
+ end
185
+
186
+ def self.deep_select(hash, result=[], &blk)
187
+ result += Hash[hash.select(&blk)].values
188
+
189
+ hash.each do |key, value|
190
+ result = deep_select(value, result, &blk) if value.is_a? Hash
191
+ end
192
+
193
+ result
194
+ end
195
+
196
+ def self.deep_replace_href(hash, replace)
197
+ return replace[hash[:@href]] if hash.has_key?(:@href)
198
+
199
+ hash.keys.each do |key, value|
200
+ hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
201
+ end
202
+
203
+ hash
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,30 @@
1
+ module WashOut
2
+ class Engine < ::Rails::Engine
3
+ class << self
4
+ attr_accessor :namespace
5
+ attr_accessor :snakecase, :snakecase_input, :camelize_wsdl
6
+ attr_accessor :wsse_username, :wsse_password
7
+ end
8
+
9
+ self.namespace = 'urn:WashOut'
10
+ self.snakecase = nil
11
+
12
+ self.snakecase_input = false
13
+ self.camelize_wsdl = false
14
+
15
+ self.wsse_username = nil
16
+ self.wsse_password = nil
17
+
18
+ config.wash_out = ActiveSupport::OrderedOptions.new
19
+
20
+ initializer "wash_out.configuration" do |app|
21
+ app.config.wash_out.each do |key, value|
22
+ self.class.send "#{key}=", value
23
+ end
24
+
25
+ unless self.class.snakecase.nil?
26
+ raise "Usage of wash_out.snakecase is deprecated. You should use wash_out.snakecase_input and wash_out.camelize_wsdl"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ module WashOut
2
+ module Model
3
+ def wash_out_param_map
4
+ types = {
5
+ :text => :string,
6
+ :float => :double,
7
+ :decimal => :double,
8
+ :timestamp => :string
9
+ }
10
+ map = {}
11
+
12
+ columns_hash.each do |key, column|
13
+ type = column.type
14
+ type = types[type] if types.has_key?(type)
15
+ map[key] = type
16
+ end
17
+
18
+ map
19
+ end
20
+
21
+ def wash_out_param_name
22
+ return name.underscore
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,176 @@
1
+ module WashOut
2
+ class Param
3
+ attr_accessor :raw_name
4
+ attr_accessor :name
5
+ attr_accessor :map
6
+ attr_accessor :type
7
+ attr_accessor :multiplied
8
+ attr_accessor :value
9
+ attr_accessor :source_class
10
+
11
+ # Defines a WSDL parameter with name +name+ and type specifier +type+.
12
+ # The type specifier format is described in #parse_def.
13
+ def initialize(name, type, multiplied = false)
14
+ type ||= {}
15
+
16
+ @name = name.to_s
17
+ @raw_name = name.to_s
18
+ @map = {}
19
+ @multiplied = multiplied
20
+
21
+ if WashOut::Engine.camelize_wsdl.to_s == 'lower'
22
+ @name = @name.camelize(:lower)
23
+ elsif WashOut::Engine.camelize_wsdl
24
+ @name = @name.camelize
25
+ end
26
+
27
+ if type.is_a?(Symbol)
28
+ @type = type.to_s
29
+ elsif type.is_a?(Class)
30
+ @type = 'struct'
31
+ @map = self.class.parse_def(type.wash_out_param_map)
32
+ @source_class = type
33
+ else
34
+ @type = 'struct'
35
+ @map = self.class.parse_def(type)
36
+ end
37
+ end
38
+
39
+ # Converts a generic externally derived Ruby value, such as String or
40
+ # Hash, to a native Ruby object according to the definition of this type.
41
+ def load(data, key)
42
+ if !data.has_key? key
43
+ raise WashOut::Dispatcher::SOAPError, "Required SOAP parameter '#{key}' is missing"
44
+ end
45
+
46
+ data = data[key]
47
+ data = [data] if @multiplied && !data.is_a?(Array)
48
+
49
+ if struct?
50
+ if @multiplied
51
+ data.map do |x|
52
+ map_struct x do |param, data, elem|
53
+ param.load(data, elem)
54
+ end
55
+ end
56
+ else
57
+ map_struct data do |param, data, elem|
58
+ param.load(data, elem)
59
+ end
60
+ end
61
+ else
62
+ operation = case type
63
+ when 'string'; :to_s
64
+ when 'integer'; :to_i
65
+ when 'double'; :to_f
66
+ when 'boolean'; lambda { |v| v == 'true' }
67
+ when 'date'; :to_date
68
+ when 'datetime'; :to_datetime
69
+ when 'time'; :to_time
70
+ else raise RuntimeError, "Invalid WashOut simple type: #{type}"
71
+ end
72
+
73
+ if operation.nil? || data.nil?
74
+ data
75
+ elsif @multiplied
76
+ data.map{|x| x.send(operation)}
77
+ elsif operation.is_a? Symbol
78
+ data.send(operation)
79
+ else
80
+ operation.call(data)
81
+ end
82
+ end
83
+ end
84
+
85
+ # Checks if this Param defines a complex type.
86
+ def struct?
87
+ type == 'struct'
88
+ end
89
+
90
+ def classified?
91
+ !source_class.nil?
92
+ end
93
+
94
+ def basic_type
95
+ return name unless classified?
96
+ return source_class.wash_out_param_name
97
+ end
98
+
99
+ def xsd_type
100
+ return 'int' if type.to_s == 'integer'
101
+ return 'dateTime' if type.to_s == 'datetime'
102
+ return type
103
+ end
104
+
105
+ # Returns a WSDL namespaced identifier for this type.
106
+ def namespaced_type
107
+ struct? ? "tns:#{basic_type}" : "xsd:#{xsd_type}"
108
+ end
109
+
110
+ # Parses a +definition+. The format of the definition is best described
111
+ # by the following BNF-like grammar.
112
+ #
113
+ # simple_type := :string | :integer | :double | :boolean
114
+ # nested_type := type_hash | simple_type | WashOut::Param instance
115
+ # type_hash := { :parameter_name => nested_type, ... }
116
+ # definition := [ WashOut::Param, ... ] |
117
+ # type_hash |
118
+ # simple_type
119
+ #
120
+ # If a simple type is passed as the +definition+, a single Param is returned
121
+ # with the +name+ set to "value".
122
+ # If a WashOut::Param instance is passed as a +nested_type+, the corresponding
123
+ # +:parameter_name+ is ignored.
124
+ #
125
+ # This function returns an array of WashOut::Param objects.
126
+ def self.parse_def(definition)
127
+ raise RuntimeError, "[] should not be used in your params. Use nil if you want to mark empty set." if definition == []
128
+ return [] if definition == nil
129
+
130
+ if [Array, Symbol, Class].include?(definition.class)
131
+ definition = { :value => definition }
132
+ end
133
+
134
+ if definition.is_a? Hash
135
+ definition.map do |name, opt|
136
+ if opt.is_a? WashOut::Param
137
+ opt
138
+ elsif opt.is_a? Array
139
+ WashOut::Param.new(name, opt[0], true)
140
+ else
141
+ WashOut::Param.new(name, opt)
142
+ end
143
+ end
144
+ else
145
+ raise RuntimeError, "Wrong definition: #{definition.inspect}"
146
+ end
147
+ end
148
+
149
+ def flat_copy
150
+ copy = self.class.new(@name, @type.to_sym, @multiplied)
151
+ copy.raw_name = raw_name
152
+ copy
153
+ end
154
+
155
+ private
156
+
157
+ # Used to load an entire structure.
158
+ def map_struct(data)
159
+ unless data.is_a?(Hash)
160
+ raise WashOut::Dispatcher::SOAPError, "SOAP message structure is broken"
161
+ end
162
+
163
+ data = data.with_indifferent_access
164
+ struct = {}.with_indifferent_access
165
+
166
+ # RUBY18 Enumerable#each_with_object is better, but 1.9 only.
167
+ @map.map do |param|
168
+ if data.has_key? param.raw_name
169
+ struct[param.raw_name] = yield param, data, param.raw_name
170
+ end
171
+ end
172
+
173
+ struct
174
+ end
175
+ end
176
+ end