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,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