google-ads-common 0.4.0 → 0.5.0

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 (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)