bing_ads_ruby_sdk 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.rspec +3 -1
  4. data/Gemfile +0 -3
  5. data/LICENSE.txt +1 -1
  6. data/README.md +70 -9
  7. data/Rakefile +2 -0
  8. data/bin/setup +1 -3
  9. data/bing_ads_ruby_sdk.gemspec +17 -22
  10. data/changelog.md +19 -0
  11. data/lib/bing_ads_ruby_sdk.rb +32 -3
  12. data/lib/bing_ads_ruby_sdk/api.rb +81 -0
  13. data/lib/bing_ads_ruby_sdk/augmented_parser.rb +79 -0
  14. data/lib/bing_ads_ruby_sdk/configuration.rb +26 -0
  15. data/lib/bing_ads_ruby_sdk/errors/error_handler.rb +65 -0
  16. data/lib/bing_ads_ruby_sdk/errors/errors.rb +192 -0
  17. data/lib/bing_ads_ruby_sdk/header.rb +44 -0
  18. data/lib/bing_ads_ruby_sdk/http_client.rb +65 -0
  19. data/lib/bing_ads_ruby_sdk/log_message.rb +57 -0
  20. data/lib/bing_ads_ruby_sdk/oauth2/authorization_handler.rb +94 -0
  21. data/lib/bing_ads_ruby_sdk/oauth2/fs_store.rb +34 -0
  22. data/lib/bing_ads_ruby_sdk/postprocessors/cast_long_arrays.rb +35 -0
  23. data/lib/bing_ads_ruby_sdk/postprocessors/snakize.rb +35 -0
  24. data/lib/bing_ads_ruby_sdk/preprocessors/camelize.rb +45 -0
  25. data/lib/bing_ads_ruby_sdk/preprocessors/order.rb +58 -0
  26. data/lib/bing_ads_ruby_sdk/services/ad_insight.rb +10 -0
  27. data/lib/bing_ads_ruby_sdk/services/base.rb +83 -0
  28. data/lib/bing_ads_ruby_sdk/services/bulk.rb +26 -0
  29. data/lib/bing_ads_ruby_sdk/services/campaign_management.rb +116 -0
  30. data/lib/bing_ads_ruby_sdk/services/customer_billing.rb +10 -0
  31. data/lib/bing_ads_ruby_sdk/services/customer_management.rb +26 -0
  32. data/lib/bing_ads_ruby_sdk/services/reporting.rb +10 -0
  33. data/lib/bing_ads_ruby_sdk/soap_client.rb +143 -0
  34. data/lib/bing_ads_ruby_sdk/string_utils.rb +23 -0
  35. data/lib/bing_ads_ruby_sdk/version.rb +4 -1
  36. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/ad_insight.xml +1 -0
  37. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/bulk.xml +1 -0
  38. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/campaign_management.xml +1 -0
  39. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/customer_billing.xml +1 -0
  40. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/customer_management.xml +1 -0
  41. data/lib/bing_ads_ruby_sdk/wsdl/v12/production/reporting.xml +1 -0
  42. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/ad_insight.xml +1 -0
  43. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/bulk.xml +1 -0
  44. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/campaign_management.xml +1 -0
  45. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/customer_billing.xml +1 -0
  46. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/customer_management.xml +1 -0
  47. data/lib/bing_ads_ruby_sdk/wsdl/v12/sandbox/reporting.xml +1 -0
  48. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/ad_insight.xml +3065 -0
  49. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/bulk.xml +1424 -0
  50. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/campaign_management.xml +9949 -0
  51. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/customer_billing.xml +899 -0
  52. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/customer_management.xml +3966 -0
  53. data/lib/bing_ads_ruby_sdk/wsdl/v12/test/reporting.xml +3742 -0
  54. data/lib/bing_ads_ruby_sdk/wsdl/wsdl_source.txt +25 -0
  55. data/lib/bing_ads_ruby_sdk/wsdl_operation_wrapper.rb +39 -0
  56. data/tasks/bing_ads_ruby_sdk.rake +28 -0
  57. metadata +137 -38
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ class Configuration
5
+ attr_accessor :pretty_print_xml, :filters, :log
6
+ attr_writer :logger
7
+
8
+ def initialize
9
+ @log = false
10
+ @pretty_print_xml = false
11
+ @filters = []
12
+ end
13
+
14
+ def logger
15
+ @logger ||= default_logger
16
+ end
17
+
18
+ private
19
+
20
+ def default_logger
21
+ Logger.new(File.join(BingAdsRubySdk::ROOT_PATH, "log", "bing-sdk.log")).tap do |l|
22
+ l.level = Logger::INFO
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Errors
5
+ # Parses the response from the API to raise errors if they are returned
6
+ class ErrorHandler
7
+ def initialize(response)
8
+ @response = response
9
+ end
10
+
11
+ def call
12
+ # Some operations don't return a response, for example:
13
+ # https://msdn.microsoft.com/en-us/library/bing-ads-customer-management-deleteaccount.aspx
14
+ return unless response.is_a? Hash
15
+ raise fault_class.new(response) if contains_error?
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :response
21
+
22
+ def contains_error?
23
+ partial_error_keys.any? || contains_fault?
24
+ end
25
+
26
+ def contains_fault?
27
+ (ERROR_KEYS & response.keys).any?
28
+ end
29
+
30
+ def fault_class
31
+ ERRORS_MAPPING.fetch(hash_with_error.keys.first, BASE_FAULT)
32
+ end
33
+
34
+ def hash_with_error
35
+ response[:detail] || partial_errors || {}
36
+ end
37
+
38
+ def partial_errors
39
+ response.select {|key| partial_error_keys.include?(key)}
40
+ end
41
+
42
+ # Gets populated partial error keys from the response
43
+ # @return [Array] array of symbols for keys in the response
44
+ # that are populated with errors
45
+ def partial_error_keys
46
+ @partial_error_keys ||= (PARTIAL_ERROR_KEYS & response.keys).reject do |key|
47
+ response[key].nil? || response[key].is_a?(String)
48
+ end
49
+ end
50
+
51
+ BASE_FAULT = BingAdsRubySdk::Errors::GeneralError
52
+ PARTIAL_ERROR_KEYS = %i[partial_errors nested_partial_errors].freeze
53
+ ERROR_KEYS = %i[faultcode error_code]
54
+ ERRORS_MAPPING = {
55
+ api_fault_detail: BingAdsRubySdk::Errors::ApiFaultDetail,
56
+ ad_api_fault_detail: BingAdsRubySdk::Errors::AdApiFaultDetail,
57
+ editorial_api_fault_detail: BingAdsRubySdk::Errors::EditorialApiFaultDetail,
58
+ api_batch_fault: BingAdsRubySdk::Errors::ApiBatchFault,
59
+ api_fault: BingAdsRubySdk::Errors::ApiFault,
60
+ nested_partial_errors: BingAdsRubySdk::Errors::NestedPartialError,
61
+ partial_errors: BingAdsRubySdk::Errors::PartialError
62
+ }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Errors
5
+ # Base exception class for reporting API errors
6
+ class GeneralError < ::StandardError
7
+ attr_accessor :raw_response, :message
8
+
9
+ def initialize(response)
10
+ @raw_response = response
11
+
12
+ code = response[:error_code] || 'Bing Ads API error'
13
+
14
+ message = response[:message] ||
15
+ response[:faultstring] ||
16
+ 'See exception details for more information.'
17
+
18
+ @message = format_message(code, message)
19
+ end
20
+
21
+ private
22
+
23
+ # Format the message separated by hyphen if
24
+ # there is a code and a message
25
+ def format_message(code, message)
26
+ [code, message].compact.join(' - ')
27
+ end
28
+ end
29
+
30
+ class ServerError < GeneralError
31
+ def initialize(server_error)
32
+ super "Server raised error #{server_error}"
33
+ end
34
+ end
35
+
36
+ # Base exception class for handling errors where the detail is supplied
37
+ class ApplicationFault < GeneralError
38
+ def initialize(response)
39
+ super
40
+
41
+ populate_error_lists
42
+ end
43
+
44
+ def message
45
+ error_list = all_errors
46
+ return @message if error_list.empty?
47
+
48
+ first_message = first_error_message(error_list)
49
+ if error_list.count > 1
50
+ "API raised #{ error_list.count } errors, including: #{first_message}"
51
+ else
52
+ first_message
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def populate_error_lists
59
+ self.class.error_lists.each do |key|
60
+ instance_variable_set("@#{key}", array_wrap(fault_hash[key]))
61
+ end
62
+ end
63
+
64
+ def all_errors
65
+ self.class.error_lists.flat_map do |list_name|
66
+ list = send(list_name)
67
+
68
+ # Call sometimes returns an empty string instead of
69
+ # nil for empty lists
70
+ list.nil? || list.empty? ? nil : list
71
+ end.compact
72
+ end
73
+
74
+ # The fault hash from the API response detail element
75
+ # @return [Hash] containing the fault information if provided
76
+ # @return [Hash] empty hash if no fault information
77
+ def fault_hash
78
+ raw_response[:detail][fault_key] || {}
79
+ end
80
+
81
+ # The fault key that corresponds to the inherited class
82
+ # @return [Symbol] the fault key
83
+ def fault_key
84
+ class_name = self.class.name.split('::').last
85
+ BingAdsRubySdk::StringUtils.snakize(class_name).to_sym
86
+ end
87
+
88
+ def first_error_message(error_list)
89
+ error = error_list.first.values.first
90
+ format_message(error[:error_code], error[:message])
91
+ end
92
+
93
+ def array_wrap(value)
94
+ case value
95
+ when Array then value
96
+ when nil, "" then []
97
+ else
98
+ [value]
99
+ end
100
+ end
101
+
102
+ class << self
103
+ def error_lists=(value)
104
+ @error_lists = value
105
+ end
106
+
107
+ def error_lists
108
+ @error_lists ||= []
109
+ end
110
+
111
+ def define_error_lists(*error_list_array)
112
+ self.error_lists += error_list_array
113
+
114
+ error_list_array.each { |attr| attr_accessor attr }
115
+ end
116
+ end
117
+ end
118
+
119
+ # Base class for handling partial errors
120
+ class PartialErrorBase < ApplicationFault
121
+
122
+ private
123
+
124
+ # The parent hash for this type of error is the root of the response
125
+ def fault_hash
126
+ raw_response[fault_key] || {}
127
+ end
128
+
129
+ # Gets the first error message in the list. This is
130
+ # overridden because partial errors are structured differently
131
+ # to application faults
132
+ # @return [Hash] containing the details of the error
133
+ def first_error_message(error_list)
134
+ error = error_list.first
135
+ format_message(error[:error_code], error[:message])
136
+ end
137
+ end
138
+
139
+ class PartialError < PartialErrorBase
140
+ define_error_lists :batch_error
141
+
142
+ private
143
+ def fault_key
144
+ :partial_errors
145
+ end
146
+ end
147
+
148
+ class NestedPartialError < PartialErrorBase
149
+ define_error_lists :batch_error_collection
150
+
151
+ private
152
+ def fault_key
153
+ :nested_partial_errors
154
+ end
155
+ end
156
+
157
+ # For handling API errors of the same name.
158
+ # Documentation:
159
+ # https://msdn.microsoft.com/en-gb/library/bing-ads-overview-adapifaultdetail.aspx
160
+ class AdApiFaultDetail < ApplicationFault
161
+ define_error_lists :errors
162
+ end
163
+
164
+ # For handling API errors of the same name.
165
+ # Documentation:
166
+ # https://msdn.microsoft.com/en-gb/library/bing-ads-overview-apifaultdetail.aspx
167
+ class ApiFaultDetail < ApplicationFault
168
+ define_error_lists :batch_errors, :operation_errors
169
+ end
170
+
171
+ # For handling API errors of the same name.
172
+ # Documentation:
173
+ # https://msdn.microsoft.com/en-gb/library/bing-ads-overview-editorialapifaultdetail.aspx
174
+ class EditorialApiFaultDetail < ApplicationFault
175
+ define_error_lists :batch_errors, :editorial_errors, :operation_errors
176
+ end
177
+
178
+ # For handling API errors of the same name.
179
+ # Documentation:
180
+ # https://msdn.microsoft.com/en-gb/library/bing-ads-apibatchfault-customer-billing.aspx
181
+ class ApiBatchFault < ApplicationFault
182
+ define_error_lists :batch_errors, :operation_errors
183
+ end
184
+
185
+ # For handling API errors of the same name.
186
+ # Documentation:
187
+ # https://msdn.microsoft.com/en-gb/library/bing-ads-apifault-customer-billing.aspx
188
+ class ApiFault < ApplicationFault
189
+ define_error_lists :operation_errors
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ # Contains the SOAP Request header informations
5
+ class Header
6
+ # @param developer_token
7
+ # @param client_id
8
+ # @param store instance of a store
9
+ def initialize(developer_token:, client_id:, store:)
10
+ @developer_token = developer_token
11
+ @client_id = client_id
12
+ @oauth_store = store
13
+ @customer = {}
14
+ end
15
+
16
+ # @return [Hash] Authorization and identification data that will be added to the SOAP header
17
+ def content
18
+ {
19
+ "AuthenticationToken" => auth_handler.fetch_or_refresh,
20
+ "DeveloperToken" => developer_token,
21
+ "CustomerId" => customer[:customer_id],
22
+ "CustomerAccountId" => customer[:account_id]
23
+ }
24
+ end
25
+
26
+ def set_customer(account_id:, customer_id:)
27
+ customer[:account_id] = account_id
28
+ customer[:customer_id] = customer_id
29
+ self
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :oauth_store, :developer_token, :client_id, :customer
35
+
36
+ def auth_handler
37
+ @auth_handler ||= ::BingAdsRubySdk::OAuth2::AuthorizationHandler.new(
38
+ developer_token: developer_token,
39
+ client_id: client_id,
40
+ store: oauth_store
41
+ )
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "excon"
5
+
6
+ module BingAdsRubySdk
7
+ class HttpClient
8
+ @http_connections = {}
9
+ HTTP_OPEN_TIMEOUT = 10
10
+ HTTP_READ_TIMEOUT = 20
11
+ HTTP_RETRY_COUNT_ON_TIMEOUT = 2
12
+ HTTP_INTERVAL_RETRY_COUNT_ON_TIMEOUT = 1
13
+ HTTP_ERRORS = [ Net::HTTPServerError, Net::HTTPClientError ]
14
+
15
+ class << self
16
+ def post(request)
17
+ uri = URI(request.url)
18
+ conn = self.connection(request.url)
19
+ raw_response = conn.post(
20
+ path: uri.path,
21
+ body: request.content,
22
+ headers: request.headers,
23
+ )
24
+
25
+ if contains_error?(raw_response)
26
+ BingAdsRubySdk.log(:warn) { BingAdsRubySdk::LogMessage.new(raw_response.body).to_s }
27
+ raise BingAdsRubySdk::Errors::ServerError, raw_response.body
28
+ else
29
+ BingAdsRubySdk.log(:debug) { BingAdsRubySdk::LogMessage.new(raw_response.body).to_s }
30
+ end
31
+
32
+ raw_response.body
33
+ end
34
+
35
+ def close_http_connections
36
+ self.http_connections.values.each do |connection|
37
+ connection.reset
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ attr_reader :http_connections
44
+
45
+ def contains_error?(response)
46
+ HTTP_ERRORS.any? { |http_error_class| response.class <= http_error_class }
47
+ end
48
+
49
+ def connection(host)
50
+ self.http_connections[host] ||= Excon.new(
51
+ host,
52
+ persistent: true,
53
+ tcp_nodelay: true,
54
+ retry_limit: HTTP_RETRY_COUNT_ON_TIMEOUT,
55
+ idempotent: true,
56
+ retry_interval: HTTP_INTERVAL_RETRY_COUNT_ON_TIMEOUT,
57
+ connect_timeout: HTTP_OPEN_TIMEOUT,
58
+ read_timeout: HTTP_READ_TIMEOUT,
59
+ ssl_version: :TLSv1_2,
60
+ ciphers: "TLSv1.2:!aNULL:!eNULL",
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ class LogMessage
5
+
6
+ def initialize(message)
7
+ @message = message
8
+ end
9
+
10
+ def to_s
11
+ return message unless message_is_xml
12
+ return message unless filters.any? || pretty_print
13
+
14
+ document = Nokogiri::XML(message)
15
+ document = apply_filter(document) if filters.any?
16
+ document.to_xml(nokogiri_options)
17
+ end
18
+
19
+ FILTERED = "***FILTERED***"
20
+
21
+ private
22
+
23
+ attr_reader :message
24
+
25
+ def message_is_xml
26
+ message =~ /^</
27
+ end
28
+
29
+ def apply_filter(document)
30
+ return document unless document.errors.empty?
31
+
32
+ filters.each do |filter|
33
+ apply_filter! document, filter
34
+ end
35
+
36
+ document
37
+ end
38
+
39
+ def apply_filter!(document, filter)
40
+ document.xpath("//*[local-name()='#{filter}']").each do |node|
41
+ node.content = FILTERED
42
+ end
43
+ end
44
+
45
+ def nokogiri_options
46
+ pretty_print ? { indent: 2 } : { save_with: Nokogiri::XML::Node::SaveOptions::AS_XML }
47
+ end
48
+
49
+ def pretty_print
50
+ BingAdsRubySdk.config.pretty_print_xml
51
+ end
52
+
53
+ def filters
54
+ BingAdsRubySdk.config.filters
55
+ end
56
+ end
57
+ end