bing_ads_ruby_sdk 0.0.0 → 1.0.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 (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,94 @@
1
+ require 'signet/oauth_2/client'
2
+ require 'bing_ads_ruby_sdk/oauth2/fs_store'
3
+
4
+ module BingAdsRubySdk
5
+ module OAuth2
6
+ # Adds some useful methods to Signet::OAuth2::Client
7
+ class AuthorizationHandler
8
+
9
+ # @param developer_token
10
+ # @param client_id
11
+ # @param store [Store]
12
+ def initialize(developer_token:, client_id:, store:)
13
+ @client = build_client(developer_token, client_id)
14
+ @store = store
15
+ refresh_from_store
16
+ end
17
+
18
+ # @return [String] unless client.client_id url is nil interpolated url.
19
+ # @return [nil] if client.client_id is nil.
20
+ def code_url
21
+ return nil if client.client_id.nil?
22
+ "https://login.live.com/oauth20_authorize.srf?client_id=#{client.client_id}&scope=bingads.manage&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf"
23
+ end
24
+
25
+ # Once you have completed the oauth process in your browser using the code_url
26
+ # copy the url your browser has been redirected to and use it as argument here
27
+ def fetch_from_url(url)
28
+ codes = extract_codes(url)
29
+
30
+ return false if codes.none?
31
+ fetch_from_code(codes.last)
32
+ rescue Signet::AuthorizationError, URI::InvalidURIError
33
+ false
34
+ end
35
+
36
+ # Get or fetch an access token.
37
+ # @return [String] The access token.
38
+ def fetch_or_refresh
39
+ if client.expired?
40
+ client.refresh!
41
+ store.write(token_data)
42
+ end
43
+ client.access_token
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :client, :store
49
+
50
+ # Refresh existing authorization token
51
+ # @return [Signet::OAuth2::Client] if everything went well.
52
+ # @return [nil] if the token can't be read from the store.
53
+ def refresh_from_store
54
+ ext_token = store.read
55
+ client.update_token!(ext_token) if ext_token
56
+ end
57
+
58
+ # Request the Api to exchange the code for the access token.
59
+ # Save the access token through the store.
60
+ # @param [String] code authorization code from bing's ads.
61
+ # @return [#store.write] store's write output.
62
+ def fetch_from_code(code)
63
+ client.code = code
64
+ client.fetch_access_token!
65
+ store.write(token_data)
66
+ end
67
+
68
+ def extract_codes(url)
69
+ url = URI.parse(url)
70
+ query_params = URI.decode_www_form(url.query)
71
+ query_params.find { |arg| arg.first.casecmp("CODE").zero? }
72
+ end
73
+
74
+ def build_client(developer_token, client_id)
75
+ Signet::OAuth2::Client.new({
76
+ authorization_uri: 'https://login.live.com/oauth20_authorize.srf',
77
+ token_credential_uri: 'https://login.live.com/oauth20_token.srf',
78
+ redirect_uri: 'https://login.live.com/oauth20_desktop.srf',
79
+ developer_token: developer_token,
80
+ client_id: client_id
81
+ })
82
+ end
83
+
84
+ def token_data
85
+ {
86
+ access_token: client.access_token,
87
+ refresh_token: client.refresh_token,
88
+ issued_at: client.issued_at,
89
+ expires_in: client.expires_in
90
+ }
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module BingAdsRubySdk
4
+ module OAuth2
5
+ # Oauth2 token default non-encrypted File System store
6
+ class FsStore
7
+ # @param filename [String] the uniq filename to identify filename storing data.
8
+ def initialize(filename)
9
+ @filename = filename
10
+ end
11
+
12
+ # Writes the token to file
13
+ # @return [File] if the file was written (doesn't mean the token is).
14
+ # @return [self] if the filename don't exist.
15
+ def write(value)
16
+ return nil unless filename
17
+ File.open(filename, 'w') { |f| JSON.dump(value, f) }
18
+ self
19
+ end
20
+
21
+ # Reads the token from file
22
+ # @return [Hash] if the token information that was stored.
23
+ # @return [nil] if the file doesn't exist.
24
+ def read
25
+ return nil unless File.file?("./#{filename}")
26
+ JSON.parse(IO.read(filename))
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :filename
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Postprocessors
5
+ class CastLongArrays
6
+
7
+ def initialize(params)
8
+ @params = params
9
+ end
10
+
11
+ def call
12
+ process(@params)
13
+ end
14
+
15
+ private
16
+
17
+ def process(obj)
18
+ return unless obj.is_a?(Hash)
19
+
20
+ obj.each do |k, v|
21
+ case v
22
+ when Hash
23
+ if v[:long].is_a?(Array)
24
+ obj[k] = v[:long].map(&:to_i)
25
+ else
26
+ process(v)
27
+ end
28
+ when Array
29
+ v.each {|elt| process(elt) }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Postprocessors
5
+ class Snakize
6
+
7
+ def initialize(params)
8
+ @params = params
9
+ end
10
+
11
+ def call
12
+ process(@params)
13
+ end
14
+
15
+ private
16
+
17
+ # NOTE: there is a potential for high memory usage here as we're using recursive method calling
18
+ def process(obj)
19
+ return obj unless obj.is_a?(Hash)
20
+
21
+ obj.each_with_object({}) do |(k, v), h|
22
+ case v
23
+ when Hash then v = process(v)
24
+ when Array then v = v.map {|elt| process(elt) }
25
+ end
26
+ h[snakize(k)] = v
27
+ end
28
+ end
29
+
30
+ def snakize(string)
31
+ BingAdsRubySdk::StringUtils.snakize(string)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Preprocessors
5
+ class Camelize
6
+
7
+ def initialize(params)
8
+ @params = params
9
+ end
10
+
11
+ def call
12
+ process(@params)
13
+ end
14
+
15
+ private
16
+
17
+ # NOTE: there is a potential for high memory usage here as we're using recursive method calling
18
+ def process(obj)
19
+ return obj unless obj.is_a?(Hash)
20
+
21
+ obj.each_with_object({}) do |(k, v), h|
22
+ case v
23
+ when Hash then v = process(v)
24
+ when Array then v = v.map {|elt| process(elt) }
25
+ end
26
+ h[transform_key(k.to_s)] = v
27
+ end
28
+ end
29
+
30
+ def transform_key(key)
31
+ if BLACKLIST.include?(key)
32
+ key
33
+ else
34
+ camelize(key)
35
+ end
36
+ end
37
+
38
+ def camelize(string)
39
+ BingAdsRubySdk::StringUtils.camelize(string)
40
+ end
41
+
42
+ BLACKLIST = %w(long string)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BingAdsRubySdk
4
+ module Preprocessors
5
+ class Order
6
+
7
+ def initialize(wsdl_wrapper, params)
8
+ @wrapper = wsdl_wrapper
9
+ @params = params
10
+ end
11
+
12
+ def call
13
+ process(params, wrapper.request_namespace_type)
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :wrapper, :params
19
+
20
+ # NOTE: there is a potential for high memory usage here as we're using recursive method calling
21
+ def process(obj, namespace_type)
22
+ return obj unless obj.is_a?(Hash)
23
+
24
+ allowed_attributes = wrapper.ordered_fields_hash(namespace_type)
25
+
26
+ order(obj, allowed_attributes).tap do |ordered_hash|
27
+ ordered_hash.each do |type_name, value|
28
+ ordered_hash[type_name] = ordered_value(allowed_attributes, type_name, value)
29
+ end
30
+ end
31
+ end
32
+
33
+ def ordered_value(allowed_attributes, type_name, value)
34
+ case value
35
+ when Hash
36
+ namespace_type = wrapper.namespace_and_type_from_name(allowed_attributes, type_name)
37
+ process(value, namespace_type)
38
+ when Array
39
+ value.map do |elt|
40
+ namespace_type = wrapper.namespace_and_type_from_name(allowed_attributes, type_name)
41
+ process(elt, namespace_type)
42
+ end
43
+ else value
44
+ end
45
+ end
46
+
47
+ def ordered_params(namespace_type)
48
+ wrapper.ordered_fields_hash(namespace_type)
49
+ end
50
+
51
+ def order(hash, allowed_attributes)
52
+ array = allowed_attributes.keys
53
+ # basically order by index in reference array
54
+ Hash[ hash.sort_by { |k, _| array.index(wrapper.base_type_name(allowed_attributes, k)) || k.ord } ]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ module BingAdsRubySdk
2
+ module Services
3
+ class AdInsight < Base
4
+
5
+ def self.service
6
+ :ad_insight
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bing_ads_ruby_sdk/preprocessors/camelize'
4
+ require 'bing_ads_ruby_sdk/preprocessors/order'
5
+ require 'bing_ads_ruby_sdk/postprocessors/snakize'
6
+ require 'bing_ads_ruby_sdk/postprocessors/cast_long_arrays'
7
+
8
+
9
+ module BingAdsRubySdk
10
+ module Services
11
+ class Base
12
+
13
+ def initialize(soap_client)
14
+ @soap_client = soap_client
15
+ end
16
+
17
+ def call(operation_name, message = {})
18
+ camelized_name = BingAdsRubySdk::StringUtils.camelize(operation_name.to_s)
19
+ response = soap_client.call(
20
+ camelized_name,
21
+ preprocess(message, camelized_name),
22
+ )
23
+ postprocess(response)
24
+ end
25
+
26
+ def self.service
27
+ raise 'implement me'
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :soap_client
33
+
34
+ def call_wrapper(action, message, *response_nesting)
35
+ response = call(action, message)
36
+ wrap_array(dig_response(response, response_nesting))
37
+ end
38
+
39
+ def preprocess(message, operation_name)
40
+ order(
41
+ soap_client.wsdl_wrapper(operation_name),
42
+ camelize(message)
43
+ )
44
+ end
45
+
46
+ def postprocess(message)
47
+ cast_long_arrays(
48
+ snakize(message)
49
+ )
50
+ end
51
+
52
+ def order(wrapper, hash)
53
+ ::BingAdsRubySdk::Preprocessors::Order.new(wrapper, hash).call
54
+ end
55
+
56
+ def camelize(hash)
57
+ ::BingAdsRubySdk::Preprocessors::Camelize.new(hash).call
58
+ end
59
+
60
+ def snakize(hash)
61
+ ::BingAdsRubySdk::Postprocessors::Snakize.new(hash).call
62
+ end
63
+
64
+ def cast_long_arrays(hash)
65
+ ::BingAdsRubySdk::Postprocessors::CastLongArrays.new(hash).call
66
+ end
67
+
68
+ def dig_response(response, keys)
69
+ response.dig(*keys)
70
+ rescue StandardError => e
71
+ nil
72
+ end
73
+
74
+ def wrap_array(arg)
75
+ case arg
76
+ when Array then arg
77
+ when nil, "" then []
78
+ else [arg]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,26 @@
1
+ module BingAdsRubySdk
2
+ module Services
3
+ class Bulk < Base
4
+
5
+ def download_campaigns_by_account_ids(message)
6
+ call(__method__, message)
7
+ end
8
+
9
+ def get_bulk_download_status(message)
10
+ call(__method__, message)
11
+ end
12
+
13
+ def get_bulk_upload_url(message)
14
+ call(__method__, message)
15
+ end
16
+
17
+ def get_bulk_upload_status(message)
18
+ call(__method__, message)
19
+ end
20
+
21
+ def self.service
22
+ :bulk
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,116 @@
1
+ module BingAdsRubySdk
2
+ module Services
3
+ class CampaignManagement < Base
4
+
5
+
6
+ def add_ad_extensions(message)
7
+ call(__method__, message)
8
+ end
9
+
10
+ def add_conversion_goals(message)
11
+ call(__method__, message)
12
+ end
13
+
14
+ def add_shared_entity(message)
15
+ call(__method__, message)
16
+ end
17
+
18
+ def add_uet_tags(message)
19
+ call(__method__, message)
20
+ end
21
+
22
+ def set_ad_extensions_associations(message)
23
+ call(__method__, message)
24
+ end
25
+
26
+
27
+ def update_conversion_goals(message)
28
+ call(__method__, message)
29
+ end
30
+
31
+ def update_uet_tags(message)
32
+ call(__method__, message)
33
+ end
34
+
35
+
36
+ def get_ad_extensions_associations(message)
37
+ wrap_array(
38
+ call(__method__, message)
39
+ .dig(:ad_extension_association_collection, :ad_extension_association_collection)
40
+ .first
41
+ .dig(:ad_extension_associations, :ad_extension_association)
42
+ )
43
+ rescue
44
+ []
45
+ end
46
+
47
+ def get_ad_extension_ids_by_account_id(message)
48
+ call_wrapper(__method__, message, :ad_extension_ids)
49
+ end
50
+
51
+ def get_ad_extensions_by_ids(message)
52
+ call_wrapper(__method__, message, :ad_extensions, :ad_extension)
53
+ end
54
+
55
+ def get_ad_groups_by_ids(message)
56
+ call_wrapper(__method__, message, :ad_groups, :ad_group)
57
+ end
58
+
59
+ def get_ad_groups_by_campaign_id(message)
60
+ call_wrapper(__method__, message, :ad_groups, :ad_group)
61
+ end
62
+
63
+ def get_ads_by_ad_group_id(message)
64
+ call_wrapper(__method__, message, :ads, :ad)
65
+ end
66
+
67
+ def get_budgets_by_ids(message= {})
68
+ call_wrapper(__method__, message, :budgets, :budget)
69
+ end
70
+
71
+ def get_campaigns_by_account_id(message)
72
+ call_wrapper(__method__, message, :campaigns, :campaign)
73
+ end
74
+
75
+ def get_campaigns_by_ids(message)
76
+ call_wrapper(__method__, message, :campaigns, :campaign)
77
+ end
78
+
79
+ def get_campaign_criterions_by_ids(message)
80
+ call_wrapper(__method__, message, :campaign_criterions, :campaign_criterion)
81
+ end
82
+
83
+ def get_conversion_goals_by_ids(message)
84
+ call_wrapper(__method__, message, :conversion_goals, :conversion_goal)
85
+ end
86
+
87
+ def get_keywords_by_ad_group_id(message)
88
+ call_wrapper(__method__, message, :keywords, :keyword)
89
+ end
90
+
91
+ def get_keywords_by_editorial_status(message)
92
+ call_wrapper(__method__, message, :keywords, :keyword)
93
+ end
94
+
95
+ def get_keywords_by_ids(message)
96
+ call_wrapper(__method__, message, :keywords, :keyword)
97
+ end
98
+
99
+ def get_shared_entities_by_account_id(message)
100
+ call_wrapper(__method__, message, :shared_entities, :shared_entity)
101
+ end
102
+
103
+ def get_uet_tags_by_ids(message = {})
104
+ call_wrapper(__method__, message, :uet_tags, :uet_tag)
105
+ end
106
+
107
+ def get_shared_entity_associations_by_entity_ids(message)
108
+ call_wrapper(__method__, message, :associations, :shared_entity_association)
109
+ end
110
+
111
+ def self.service
112
+ :campaign_management
113
+ end
114
+ end
115
+ end
116
+ end