google-ads-common 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/ChangeLog +5 -0
  2. data/README +1 -4
  3. data/Rakefile +2 -2
  4. data/lib/ads_common/api.rb +106 -16
  5. data/lib/ads_common/api_config.rb +2 -3
  6. data/lib/ads_common/auth/base_handler.rb +22 -3
  7. data/lib/ads_common/auth/client_login_handler.rb +27 -32
  8. data/lib/ads_common/auth/oauth_handler.rb +260 -0
  9. data/lib/ads_common/build/savon_abstract_generator.rb +12 -11
  10. data/lib/ads_common/build/savon_generator.rb +31 -27
  11. data/lib/ads_common/build/savon_registry.rb +46 -23
  12. data/lib/ads_common/build/savon_registry_generator.rb +23 -10
  13. data/lib/ads_common/build/savon_service_generator.rb +17 -3
  14. data/lib/ads_common/config.rb +1 -1
  15. data/lib/ads_common/credential_handler.rb +3 -7
  16. data/lib/ads_common/errors.rb +18 -6
  17. data/lib/ads_common/savon_headers/base_header_handler.rb +80 -0
  18. data/lib/ads_common/{soap4r_logger.rb → savon_headers/httpi_request_proxy.rb} +27 -20
  19. data/lib/ads_common/savon_headers/oauth_header_handler.rb +92 -0
  20. data/lib/ads_common/savon_headers/simple_header_handler.rb +17 -49
  21. data/lib/ads_common/savon_service.rb +129 -41
  22. data/test/test_savon_service.rb +9 -4
  23. metadata +39 -43
  24. data/lib/ads_common/build/rake_common.rb +0 -343
  25. data/lib/ads_common/build/soap4r_generator.rb +0 -565
  26. data/lib/ads_common/savon_headers/client_login_header_handler.rb +0 -60
  27. data/lib/ads_common/soap4r_headers/nested_header_handler.rb +0 -50
  28. data/lib/ads_common/soap4r_headers/single_header_handler.rb +0 -44
  29. data/lib/ads_common/soap4r_patches.rb +0 -210
  30. data/lib/ads_common/soap4r_response_handler.rb +0 -80
@@ -30,14 +30,11 @@ module AdsCommon
30
30
  raise NoMethodError, "Tried to instantiate an abstract class"
31
31
  end
32
32
  @require_path = args[:require_path]
33
+ @api_name = args[:api_name]
34
+ @version = args[:version]
33
35
  @service_name = args[:service_name]
34
- @module_name = args[:module_name]
35
36
  @namespace = args[:namespace]
36
- @logger = args[:logger]
37
- @generator_stamp = "Code generated by AdsCommon library %s on %s." %
38
- [AdsCommon::ApiConfig::ADS_COMMON_VERSION,
39
- Time.now.strftime("%Y-%m-%d %H:%M:%S")]
40
- prepare_modules_string()
37
+ prepare_template_strings()
41
38
  end
42
39
 
43
40
  def generate_code()
@@ -49,11 +46,15 @@ module AdsCommon
49
46
  raise NotImplementedError
50
47
  end
51
48
 
52
- def prepare_modules_string()
53
- modules_count = @module_name.scan(/::/).length
54
- @modules_open_string = 'module ' + @module_name.gsub(/::/, '; module ')
55
- @modules_close_string = 'end; ' * modules_count
56
- @modules_close_string += 'end'
49
+ private
50
+
51
+ def prepare_template_strings()
52
+ @generator_stamp = "Code generated by AdsCommon library %s on %s." %
53
+ [AdsCommon::ApiConfig::ADS_COMMON_VERSION,
54
+ Time.now.strftime("%Y-%m-%d %H:%M:%S")]
55
+ @modules_open_string = 'module ' +
56
+ [@api_name, @version.to_s.upcase, @service_name].join('; module ')
57
+ @modules_close_string = 'end; end; end'
57
58
  end
58
59
 
59
60
  def remove_lines_with_blanks_only(text)
@@ -20,7 +20,6 @@
20
20
  # Generates the wrappers for API services. Only used during the
21
21
  # 'rake generate' step of library setup.
22
22
 
23
- gem 'savon', '~>0.9.1'
24
23
  require 'savon'
25
24
 
26
25
  require 'ads_common/build/savon_service_generator'
@@ -34,25 +33,29 @@ module AdsCommon
34
33
  # Contains the methods that handle wrapper code generation.
35
34
  class SavonGenerator
36
35
 
37
- # Create a new generator for given WSDL
36
+ # Create a new generator for given WSDL.
38
37
  #
39
38
  # Args:
40
- # wsdl_url local or remote URL to pull WSDL data
41
- # code_path local path to store generated files
42
- # service_name a service name to generate stubs from
43
- # module_name a fully-qualified module name
44
- def initialize(wsdl_url, code_path, service_name, module_name)
39
+ # - wsdl_url local or remote URL to pull WSDL data
40
+ # - code_path local path to store generated files
41
+ # - api_name an API name to generate for
42
+ # - version a version of the service
43
+ # - service_name a service name to generate stubs for
44
+ # - extensions an optional list of extensions to include
45
+ #
46
+ def initialize(wsdl_url, code_path, api_name, version, service_name,
47
+ extensions = [])
45
48
  @wsdl_url = wsdl_url
46
49
  @code_path = code_path
47
- @service_name = service_name
48
- @logger = Logger.new(STDOUT)
49
- @logger.level = Logger::INFO
50
+ @extensions = extensions
50
51
  @generator_args = {
52
+ :api_name => api_name,
53
+ :version => version,
51
54
  :service_name => service_name,
52
- :module_name => module_name,
53
- :require_path => @code_path.sub(/^lib\//, ''),
54
- :logger => @logger
55
+ :require_path => @code_path.sub(/^lib\//, '')
55
56
  }
57
+ @logger = Logger.new(STDOUT)
58
+ @logger.level = Logger::INFO
56
59
  Savon.configure do |config|
57
60
  config.logger = @logger
58
61
  config.log_level = :debug
@@ -61,13 +64,13 @@ module AdsCommon
61
64
  end
62
65
 
63
66
  #
64
- # Pull, parse and generate wrapper for WSDL on given URL
67
+ # Pull, parse and generate wrapper for WSDL on given URL.
65
68
  #
66
69
  # Args:
67
- # none, instance variables are used.
70
+ # - none, instance variables are used
68
71
  #
69
72
  # Returns:
70
- # none
73
+ # - none
71
74
  def process_wsdl()
72
75
  client = Savon::Client.new(@wsdl_url)
73
76
  begin
@@ -82,13 +85,12 @@ module AdsCommon
82
85
 
83
86
  private
84
87
 
85
- # Generate code for given Savon client
88
+ # Generate code for given Savon client.
86
89
  def do_process_wsdl_client(client)
87
- service_file_name = @service_name.to_s.snakecase
88
-
89
90
  wsdl = client.wsdl
90
91
  check_service(wsdl)
91
92
 
93
+ service_file_name = @generator_args[:service_name].to_s.snakecase
92
94
  wrapper_file_name = "%s/%s.rb" % [@code_path, service_file_name]
93
95
  write_wrapper(wsdl, wrapper_file_name)
94
96
 
@@ -104,36 +106,38 @@ module AdsCommon
104
106
  end
105
107
  end
106
108
 
107
- # Generates wrapper file
109
+ # Generates wrapper file.
108
110
  def write_wrapper(wsdl, file_name)
109
111
  wrapper_file = create_new_file(file_name)
110
112
  generator = SavonServiceGenerator.new(@generator_args)
111
113
  generator.add_actions(wsdl.soap_actions.dup)
114
+ generator.add_extensions(@extensions)
112
115
  wrapper_file.write(generator.generate_code())
113
116
  wrapper_file.close
114
117
  end
115
118
 
116
- # Generates registry file
119
+ # Generates registry file.
117
120
  def write_registry(wsdl, file_name)
118
121
  registry_file = create_new_file(file_name)
119
122
  generator = SavonRegistryGenerator.new(@generator_args)
120
- registry = SavonRegistry.new(wsdl)
121
- generator.add_exceptions(registry.soap_exceptions.dup)
122
- generator.add_methods(registry.soap_methods.dup)
123
- generator.add_types(registry.soap_types.dup)
123
+ registry = SavonRegistry.new(wsdl, @generator_args)
124
+ generator.add_exceptions(registry.soap_exceptions)
125
+ generator.add_methods(registry.soap_methods)
126
+ generator.add_namespaces(registry.soap_namespaces)
127
+ generator.add_types(registry.soap_types)
124
128
  registry_file.write(generator.generate_code())
125
129
  registry_file.close
126
130
  end
127
131
 
128
132
  # Creates a new file on specified path, overwriting existing one if it
129
- # exists
133
+ # exists.
130
134
  def create_new_file(file_name)
131
135
  @logger.info("Creating %s..." % [file_name])
132
136
  make_dir_for_path(file_name)
133
137
  new_file = File.new(file_name, File::WRONLY|File::TRUNC|File::CREAT)
134
138
  end
135
139
 
136
- # Creates a directory for the file path specified if not exists
140
+ # Creates a directory for the file path specified if not exists.
137
141
  def make_dir_for_path(path)
138
142
  dir_name = File.dirname(path)
139
143
  Dir.mkdir(dir_name) if !File.directory?(dir_name)
@@ -25,18 +25,22 @@ require 'rexml/document'
25
25
 
26
26
  module AdsCommon
27
27
  module Build
28
- STANDARD_TYPES = ['long', 'boolean', 'int', 'string', 'double']
29
-
30
28
  # Contains the methods that extracts WSDL data.
31
29
  class SavonRegistry
32
30
  attr_reader :soap_exceptions
33
31
  attr_reader :soap_methods
34
32
  attr_reader :soap_types
33
+ attr_reader :soap_namespaces
35
34
 
36
35
  # Initializes the instance.
36
+ #
37
37
  # Args:
38
- # wsdl - string containing wsdl to parse.
39
- def initialize(wsdl)
38
+ # - wsdl: string containing wsdl to parse
39
+ # - options: variuos generation options
40
+ #
41
+ def initialize(wsdl, options = {})
42
+ @options = options
43
+ @default_namespace = options[:namespace]
40
44
  do_process_wsdl(wsdl)
41
45
  end
42
46
 
@@ -47,8 +51,9 @@ module AdsCommon
47
51
  @soap_exceptions = []
48
52
  @soap_types = []
49
53
  @soap_methods = []
54
+ @soap_namespaces = []
50
55
 
51
- doc = REXML::Document.new(wsdl.to_xml)
56
+ doc = REXML::Document.new(wsdl.xml)
52
57
  process_types(doc)
53
58
  process_methods(doc)
54
59
  sort_exceptions()
@@ -56,18 +61,36 @@ module AdsCommon
56
61
 
57
62
  # Extracts different types from XML.
58
63
  def process_types(doc)
59
- get_complex_types(doc).each do |ctype|
60
- ctype_name = get_element_name(ctype)
61
- if ctype_name.match('.+Exception$')
62
- @soap_exceptions << extract_exception(ctype)
63
- elsif ctype_name.match('.+Error$')
64
- # We don't use it at the moment.
65
- else
66
- @soap_types << extract_type(ctype)
64
+ REXML::XPath.each(doc, '//schema') do |schema|
65
+ ns_index = process_namespace(schema)
66
+ get_complex_types(schema).each do |ctype|
67
+ ctype_name = get_element_name(ctype)
68
+ if ctype_name.match('.+Exception$')
69
+ @soap_exceptions << extract_exception(ctype)
70
+ elsif ctype_name.match('.+Error$')
71
+ # We don't use it at the moment.
72
+ else
73
+ @soap_types << extract_type(ctype, ns_index)
74
+ end
67
75
  end
68
76
  end
69
77
  end
70
78
 
79
+ # Returns index of namespace for given schema. Adds namespace to internal
80
+ # array if not yet present. Returns nil for service default namespace.
81
+ def process_namespace(schema)
82
+ namespace_url = schema.attribute('targetNamespace').value
83
+ unless namespace_url == @default_namespace
84
+ ns_index = @soap_namespaces.index(namespace_url)
85
+ if ns_index.nil?
86
+ ns_index = @soap_namespaces.length
87
+ @soap_namespaces << namespace_url
88
+ end
89
+ return ns_index
90
+ end
91
+ return nil
92
+ end
93
+
71
94
  # Extracts SOAP actions as methods.
72
95
  def process_methods(doc)
73
96
  iface = REXML::XPath.first(doc, 'descendant::wsdl:portType')
@@ -76,13 +99,9 @@ module AdsCommon
76
99
  end
77
100
  end
78
101
 
79
- # Extracts ComplexTypes from XML into an array.
80
- def get_complex_types(doc)
81
- complex_types = []
82
- REXML::XPath.each(doc, '//schema/complexType') do |ctype|
83
- complex_types << ctype
84
- end
85
- return complex_types
102
+ # Extracts ComplexTypes from node into an array.
103
+ def get_complex_types(node)
104
+ return REXML::XPath.each(node, 'complexType').to_a
86
105
  end
87
106
 
88
107
  # Extracts exception parameters from ComplexTypes element.
@@ -110,13 +129,14 @@ module AdsCommon
110
129
 
111
130
  # Extracts definition of all types. If a non standard undefined type is
112
131
  # found it process it recursively.
113
- def extract_type(type_element)
132
+ def extract_type(type_element, ns_index)
114
133
  type = {:name => get_element_name(type_element), :fields => []}
115
134
  if attribute_to_boolean(type_element.attribute('abstract'))
116
135
  type[:abstract] = true
117
136
  end
118
137
  base_type = get_element_base(type_element)
119
138
  type[:base] = base_type if base_type
139
+ type[:ns] = ns_index if ns_index
120
140
  REXML::XPath.each(type_element,
121
141
  'sequence | complexContent/extension/sequence') do |seq_node|
122
142
  type[:fields] += get_element_fields(seq_node)
@@ -177,17 +197,20 @@ module AdsCommon
177
197
  def get_element_fields(element)
178
198
  fields = []
179
199
  REXML::XPath.each(element, 'descendant::element') do |item|
180
- fields << {:name => get_element_name(item).snakecase.to_sym,
200
+ field = {:name => get_element_name(item).snakecase.to_sym,
181
201
  :type => item.attribute('type').to_s.gsub(/^.+:/, ''),
182
202
  :min_occurs => attribute_to_int(item.attribute('minOccurs')),
183
203
  :max_occurs => attribute_to_int(item.attribute('maxOccurs'))}
204
+ fields << field.reject {|k, v| v.nil?}
184
205
  end
185
206
  return fields
186
207
  end
187
208
 
188
209
  # Simple converter for int values.
189
210
  def attribute_to_int(attribute)
190
- return attribute.value.eql?('unbounded') ? nil : attribute.value.to_i
211
+ return nil if attribute.nil?
212
+ return attribute.value.eql?('unbounded') ?
213
+ :unbounded : attribute.value.to_i
191
214
  end
192
215
 
193
216
  # Simple converter for boolean values.
@@ -39,6 +39,7 @@ module AdsCommon
39
39
  class <%= @service_name %>Registry
40
40
  <%= @service_name.upcase %>_METHODS = <%= format_signature(@methods) %>
41
41
  <%= @service_name.upcase %>_TYPES = <%= format_signature(@types) %>
42
+ <%= @service_name.upcase %>_NAMESPACES = <%= format_array(@namespaces) %>
42
43
 
43
44
  def self.get_method_signature(method_name)
44
45
  return <%= @service_name.upcase %>_METHODS[method_name.to_sym]
@@ -47,6 +48,10 @@ module AdsCommon
47
48
  def self.get_type_signature(type_name)
48
49
  return <%= @service_name.upcase %>_TYPES[type_name.to_sym]
49
50
  end
51
+
52
+ def self.get_namespace(index)
53
+ return <%= @service_name.upcase %>_NAMESPACES[index]
54
+ end
50
55
  end
51
56
  <% @exceptions.each do |exception| %>
52
57
  <% array_fields = [] %>
@@ -59,11 +64,13 @@ module AdsCommon
59
64
  class <%= exception[:name] %> < <%= base_text %>
60
65
  <% exception[:fields].each do |field| %>
61
66
  attr_reader :<%= field[:name] %> # <%= field[:type] %>
62
- <% array_fields << field[:name] if field[:max_occurs].nil? || (field[:max_occurs] > 1) %>
67
+ <% is_array_field = (field[:max_occurs].nil?) ? false :
68
+ ((field[:max_occurs] == :unbounded) || (field[:max_occurs] > 1)) %>
69
+ <% array_fields << field[:name] if is_array_field %>
63
70
  <% end %>
64
71
  <% if !(array_fields.empty?) %>
65
72
  def initialize(exception_fault)
66
- @array_fields = [] if !defined?(@array_fields)
73
+ @array_fields ||= []
67
74
  <% array_fields.each do |field| %>
68
75
  @array_fields << '<%= field.to_s %>'
69
76
  <% end %>
@@ -81,8 +88,8 @@ module AdsCommon
81
88
  @exceptions = []
82
89
  @methods = []
83
90
  @types = []
84
- @default_exception_base = "%s::Errors::ApiException" %
85
- @module_name.split('::').first
91
+ @namespaces = []
92
+ @default_exception_base = "%s::Errors::ApiException" % @api_name
86
93
  end
87
94
 
88
95
  def get_code_template()
@@ -101,6 +108,10 @@ module AdsCommon
101
108
  @types += types
102
109
  end
103
110
 
111
+ def add_namespaces(namespaces)
112
+ @namespaces += namespaces
113
+ end
114
+
104
115
  private
105
116
 
106
117
  # Multi-line documentation formatter. Used to format text extracted from
@@ -120,17 +131,19 @@ module AdsCommon
120
131
  return PP.singleline_pp(objects_hash, '')
121
132
  end
122
133
 
134
+ # Prepares string representing a simple array.
135
+ def format_array(objects_array)
136
+ return (objects_array.nil?) ? '[]' : PP.singleline_pp(objects_array, '')
137
+ end
138
+
123
139
  # Converts an array of hashes to a hash based on ":name" fields:
124
140
  # [{:name => 'foo', :data => 'bar'}] => {:foo => {:data => 'bar'}}
125
141
  def get_hash_for_names_array(input)
126
- output = {}
127
- input.each do |e|
142
+ return input.inject({}) do |output, e|
128
143
  key = e[:name].to_sym
129
- value = e.dup
130
- value.delete(:name)
131
- output[key] = value
144
+ output[key] = e.reject {|k, v| k.equal?(:name)}
145
+ output
132
146
  end
133
- return output
134
147
  end
135
148
  end
136
149
  end
@@ -35,13 +35,16 @@ module AdsCommon
35
35
 
36
36
  require 'ads_common/savon_service'
37
37
  require '<%= @require_path %>/<%= @service_name.to_s.snakecase %>_registry'
38
+ <% unless @extensions.empty? %>
39
+ require '<%= @api_name.snakecase %>/extensions'
40
+ <% end %>
38
41
 
39
42
  <%= @modules_open_string %>
40
43
 
41
44
  class <%= @service_name %> < AdsCommon::SavonService
42
- def initialize(endpoint)
45
+ def initialize(api, endpoint)
43
46
  namespace = '<%= @namespace %>'
44
- super(endpoint, namespace)
47
+ super(api, endpoint, namespace, :<%= @version %>)
45
48
  end
46
49
  <% @actions.each do |action| %>
47
50
 
@@ -49,6 +52,12 @@ module AdsCommon
49
52
  return execute_action('<%= action %>', args)
50
53
  end
51
54
  <% end %>
55
+ <% @extensions.each do |extention| %>
56
+
57
+ def <%= extention %>(*args)
58
+ return <%= @api_name %>::Extensions.<%= extention %>(self, args)
59
+ end
60
+ <% end %>
52
61
 
53
62
  private
54
63
 
@@ -57,7 +66,7 @@ module AdsCommon
57
66
  end
58
67
 
59
68
  def get_module()
60
- return <%= @module_name %>
69
+ return <%= [@api_name, @version.to_s.upcase, @service_name].join('::') %>
61
70
  end
62
71
  end
63
72
  <%= @modules_close_string %>
@@ -67,12 +76,17 @@ module AdsCommon
67
76
  def initialize(args)
68
77
  super(args)
69
78
  @actions = []
79
+ @extensions = []
70
80
  end
71
81
 
72
82
  def add_actions(actions)
73
83
  @actions += actions
74
84
  end
75
85
 
86
+ def add_extensions(extensions)
87
+ @extensions += extensions
88
+ end
89
+
76
90
  def get_code_template()
77
91
  SERVICE_TEMPLATE
78
92
  end
@@ -29,7 +29,7 @@ module AdsCommon
29
29
  # Initialized the Config object with either the contents of a provided file
30
30
  # or a provided hash.
31
31
  def initialize(param = nil)
32
- @config = Hash.new
32
+ @config = {}
33
33
  case param
34
34
  when String then load(param)
35
35
  when Hash then set_all(param)