iron_bank 0.1.0 → 0.7.1
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.
- checksums.yaml +5 -5
- data/.env.example +7 -0
- data/.github/CODEOWNERS +1 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- data/.gitignore +6 -1
- data/.reek +95 -0
- data/.rspec +1 -2
- data/.rubocop.yml +55 -0
- data/.travis.yml +22 -3
- data/Gemfile +3 -3
- data/LICENSE +176 -0
- data/README.md +133 -0
- data/Rakefile +39 -3
- data/bin/console +13 -1
- data/bin/setup +1 -0
- data/iron_bank.gemspec +34 -10
- data/lib/generators/iron_bank/install/install_generator.rb +20 -0
- data/lib/generators/iron_bank/install/templates/README +16 -0
- data/lib/generators/iron_bank/install/templates/iron_bank.rb +29 -0
- data/lib/iron_bank/action.rb +39 -0
- data/lib/iron_bank/actions/amend.rb +16 -0
- data/lib/iron_bank/actions/create.rb +19 -0
- data/lib/iron_bank/actions/delete.rb +19 -0
- data/lib/iron_bank/actions/execute.rb +21 -0
- data/lib/iron_bank/actions/generate.rb +19 -0
- data/lib/iron_bank/actions/query.rb +16 -0
- data/lib/iron_bank/actions/query_more.rb +21 -0
- data/lib/iron_bank/actions/subscribe.rb +32 -0
- data/lib/iron_bank/actions/update.rb +19 -0
- data/lib/iron_bank/associations.rb +73 -0
- data/lib/iron_bank/authentication.rb +37 -0
- data/lib/iron_bank/authentications/cookie.rb +80 -0
- data/lib/iron_bank/authentications/token.rb +82 -0
- data/lib/iron_bank/cacheable.rb +52 -0
- data/lib/iron_bank/client.rb +96 -0
- data/lib/iron_bank/collection.rb +31 -0
- data/lib/iron_bank/configuration.rb +86 -0
- data/lib/iron_bank/csv.rb +29 -0
- data/lib/iron_bank/describe/field.rb +65 -0
- data/lib/iron_bank/describe/object.rb +81 -0
- data/lib/iron_bank/describe/related.rb +40 -0
- data/lib/iron_bank/describe/tenant.rb +50 -0
- data/lib/iron_bank/endpoint.rb +36 -0
- data/lib/iron_bank/error.rb +45 -0
- data/lib/iron_bank/instrumentation.rb +14 -0
- data/lib/iron_bank/local.rb +72 -0
- data/lib/iron_bank/local_records.rb +52 -0
- data/lib/iron_bank/logger.rb +23 -0
- data/lib/iron_bank/metadata.rb +36 -0
- data/lib/iron_bank/object.rb +89 -0
- data/lib/iron_bank/open_tracing.rb +17 -0
- data/lib/iron_bank/operation.rb +33 -0
- data/lib/iron_bank/operations/billing_preview.rb +16 -0
- data/lib/iron_bank/query_builder.rb +72 -0
- data/lib/iron_bank/queryable.rb +55 -0
- data/lib/iron_bank/resource.rb +70 -0
- data/lib/iron_bank/resources/account.rb +62 -0
- data/lib/iron_bank/resources/amendment.rb +13 -0
- data/lib/iron_bank/resources/catalog_tiers/discount_amount.rb +26 -0
- data/lib/iron_bank/resources/catalog_tiers/discount_percentage.rb +26 -0
- data/lib/iron_bank/resources/catalog_tiers/price.rb +26 -0
- data/lib/iron_bank/resources/contact.rb +13 -0
- data/lib/iron_bank/resources/export.rb +11 -0
- data/lib/iron_bank/resources/import.rb +12 -0
- data/lib/iron_bank/resources/invoice.rb +37 -0
- data/lib/iron_bank/resources/invoice_adjustment.rb +13 -0
- data/lib/iron_bank/resources/invoice_item.rb +25 -0
- data/lib/iron_bank/resources/invoice_payment.rb +13 -0
- data/lib/iron_bank/resources/payment.rb +17 -0
- data/lib/iron_bank/resources/payment_method.rb +14 -0
- data/lib/iron_bank/resources/product.rb +14 -0
- data/lib/iron_bank/resources/product_rate_plan.rb +32 -0
- data/lib/iron_bank/resources/product_rate_plan_charge.rb +27 -0
- data/lib/iron_bank/resources/product_rate_plan_charge_tier.rb +60 -0
- data/lib/iron_bank/resources/rate_plan.rb +21 -0
- data/lib/iron_bank/resources/rate_plan_charge.rb +30 -0
- data/lib/iron_bank/resources/rate_plan_charge_tier.rb +26 -0
- data/lib/iron_bank/resources/subscription.rb +28 -0
- data/lib/iron_bank/resources/usage.rb +16 -0
- data/lib/iron_bank/response/raise_error.rb +16 -0
- data/lib/iron_bank/schema.rb +58 -0
- data/lib/iron_bank/utils.rb +59 -0
- data/lib/iron_bank/version.rb +4 -1
- data/lib/iron_bank.rb +152 -2
- metadata +300 -12
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Handle the credentials to Zuora and establish a connection when making an
|
5
|
+
# authenticated request, reusing the same session cookie for future requests.
|
6
|
+
#
|
7
|
+
class Client
|
8
|
+
include IronBank::Instrumentation
|
9
|
+
include IronBank::OpenTracing
|
10
|
+
|
11
|
+
# Generic client error.
|
12
|
+
#
|
13
|
+
class Error < StandardError; end
|
14
|
+
|
15
|
+
# Thrown when the base_url cannot be found for the given domain.
|
16
|
+
#
|
17
|
+
class InvalidHostname < Error; end
|
18
|
+
|
19
|
+
# Alias each actions as a `Client` instance method
|
20
|
+
IronBank::Actions.constants.each do |action|
|
21
|
+
method_name = IronBank::Utils.underscore(action)
|
22
|
+
klass = IronBank::Actions.const_get(action)
|
23
|
+
|
24
|
+
define_method :"#{method_name}" do |args|
|
25
|
+
klass.call(args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(domain:, client_id:, client_secret:, auth_type: 'token')
|
30
|
+
@domain = domain
|
31
|
+
@client_id = client_id
|
32
|
+
@client_secret = client_secret
|
33
|
+
@auth_type = auth_type
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
%(#<IronBank::Client:0x#{(object_id << 1).to_s(16)} domain="#{domain}">)
|
38
|
+
end
|
39
|
+
|
40
|
+
def connection
|
41
|
+
validate_domain
|
42
|
+
reset_connection if auth.expired?
|
43
|
+
|
44
|
+
@connection ||= Faraday.new(faraday_config) do |conn|
|
45
|
+
conn.use :ddtrace, open_tracing_options if open_tracing_enabled?
|
46
|
+
conn.use instrumenter, instrumenter_options if instrumenter
|
47
|
+
conn.use IronBank::Response::RaiseError
|
48
|
+
conn.request :json
|
49
|
+
conn.response :logger, IronBank.logger
|
50
|
+
conn.response :json, content_type: /\bjson$/
|
51
|
+
conn.adapter Faraday.default_adapter
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def describe(object_name)
|
56
|
+
IronBank::Describe::Object.from_connection(connection, object_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_reader :domain, :client_id, :client_secret, :auth_type
|
62
|
+
|
63
|
+
def auth
|
64
|
+
@auth ||= IronBank::Authentication.new(
|
65
|
+
client_id: client_id,
|
66
|
+
client_secret: client_secret,
|
67
|
+
base_url: base_url,
|
68
|
+
auth_type: auth_type
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def base_url
|
73
|
+
@base_url ||= IronBank::Endpoint.base_url(domain)
|
74
|
+
end
|
75
|
+
|
76
|
+
def faraday_config
|
77
|
+
{
|
78
|
+
url: base_url,
|
79
|
+
headers: headers
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def headers
|
84
|
+
{ 'Content-Type' => 'application/json' }.merge(auth.header)
|
85
|
+
end
|
86
|
+
|
87
|
+
def reset_connection
|
88
|
+
@connection = nil
|
89
|
+
auth.renew_session
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_domain
|
93
|
+
raise InvalidHostname, domain.to_s unless base_url
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Collection class which allows records reloadable from the source
|
5
|
+
class Collection
|
6
|
+
include Enumerable
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@records,
|
10
|
+
:[],
|
11
|
+
:each,
|
12
|
+
:length,
|
13
|
+
:size
|
14
|
+
|
15
|
+
def initialize(klass, conditions, records)
|
16
|
+
@klass = klass
|
17
|
+
@conditions = conditions
|
18
|
+
@records = records
|
19
|
+
end
|
20
|
+
|
21
|
+
# Update records from source
|
22
|
+
def reload
|
23
|
+
@records = @klass.where(@conditions)
|
24
|
+
end
|
25
|
+
|
26
|
+
# In case you need to access all array methods
|
27
|
+
def to_a
|
28
|
+
@records
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# The Zuora configuration class.
|
5
|
+
#
|
6
|
+
class Configuration
|
7
|
+
# Instrumentation
|
8
|
+
attr_accessor :instrumenter
|
9
|
+
attr_accessor :instrumenter_options
|
10
|
+
|
11
|
+
# Logger
|
12
|
+
attr_accessor :logger
|
13
|
+
|
14
|
+
# The Zuora domain for our tenant (apisandbox, production, etc.).
|
15
|
+
attr_accessor :domain
|
16
|
+
|
17
|
+
# OAuth client ID associated with our platform admin user.
|
18
|
+
attr_accessor :client_id
|
19
|
+
|
20
|
+
# OAuth client secret.
|
21
|
+
attr_accessor :client_secret
|
22
|
+
|
23
|
+
# Auth type (cookie|token)
|
24
|
+
attr_accessor :auth_type
|
25
|
+
|
26
|
+
# Cache store instance, optionally used by certain resources.
|
27
|
+
attr_accessor :cache
|
28
|
+
|
29
|
+
# Open Tracing
|
30
|
+
attr_accessor :open_tracing_enabled
|
31
|
+
|
32
|
+
# Open Tracing service name
|
33
|
+
attr_accessor :open_tracing_service_name
|
34
|
+
|
35
|
+
# Directory where the XML describe files are located.
|
36
|
+
attr_reader :schema_directory
|
37
|
+
|
38
|
+
# Directory where the local records are exported.
|
39
|
+
attr_reader :export_directory
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@schema_directory = './config/schema'
|
43
|
+
@export_directory = './config/export'
|
44
|
+
@logger = IronBank::Logger.new
|
45
|
+
@auth_type = 'token'
|
46
|
+
@open_tracing_enabled = false
|
47
|
+
@open_tracing_service_name = 'ironbank'
|
48
|
+
end
|
49
|
+
|
50
|
+
def schema_directory=(value)
|
51
|
+
@schema_directory = value
|
52
|
+
|
53
|
+
return unless defined? IronBank::Schema
|
54
|
+
|
55
|
+
IronBank::Schema.reset
|
56
|
+
|
57
|
+
# Call `with_schema` on each resource to redefine accessors
|
58
|
+
IronBank::Resources.constants.each do |resource|
|
59
|
+
klass = IronBank::Resources.const_get(resource)
|
60
|
+
klass.with_schema if klass.is_a?(Class)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def export_directory=(value)
|
65
|
+
@export_directory = value
|
66
|
+
return unless defined? IronBank::Product
|
67
|
+
|
68
|
+
IronBank::LocalRecords::RESOURCES.each do |resource|
|
69
|
+
IronBank::Resources.const_get(resource).reset_store
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def credentials
|
74
|
+
{
|
75
|
+
domain: domain,
|
76
|
+
client_id: client_id,
|
77
|
+
client_secret: client_secret,
|
78
|
+
auth_type: auth_type
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def credentials?
|
83
|
+
credentials.values.all?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# A custom CSV converter
|
5
|
+
#
|
6
|
+
class CSV < ::CSV
|
7
|
+
CSV::Converters[:decimal_integer] = lambda { |field|
|
8
|
+
begin
|
9
|
+
encoding = field.encode(CSV::ConverterEncoding)
|
10
|
+
|
11
|
+
# Match: [1, 10, 100], No match: [0.1, .1, 1., 0b10]
|
12
|
+
encoding =~ /^[+-]?\d+$/ ? encoding.to_i : field
|
13
|
+
rescue # encoding or integer conversion
|
14
|
+
field
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
CSV::Converters[:decimal_float] = lambda { |field|
|
19
|
+
begin
|
20
|
+
encoding = field.encode(CSV::ConverterEncoding)
|
21
|
+
|
22
|
+
# Match: [1.0, 1., 0.1, .1], No match: [1, 0b10]
|
23
|
+
encoding =~ /^[+-]?(?:\d*\.|\.\d*)\d*$/ ? encoding.to_f : field
|
24
|
+
rescue # encoding or float conversion
|
25
|
+
field
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# Describe a field in Zuora: name, label, type, etc.
|
6
|
+
#
|
7
|
+
class Field
|
8
|
+
private_class_method :new
|
9
|
+
|
10
|
+
TEXT_VALUES = %i[
|
11
|
+
name
|
12
|
+
label
|
13
|
+
type
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
PLURAL_VALUES = %i[
|
17
|
+
options
|
18
|
+
contexts
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
BOOLEAN_VALUES = %i[
|
22
|
+
selectable
|
23
|
+
createable
|
24
|
+
updateable
|
25
|
+
filterable
|
26
|
+
custom
|
27
|
+
required
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
def self.from_xml(doc)
|
31
|
+
new(doc)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Defined separately because the node name is not ruby-friendly
|
35
|
+
def max_length
|
36
|
+
doc.at_xpath('.//maxlength').text.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
TEXT_VALUES.each do |val|
|
40
|
+
define_method(val) { doc.at_xpath(".//#{val}").text }
|
41
|
+
end
|
42
|
+
|
43
|
+
PLURAL_VALUES.each do |val|
|
44
|
+
singular = val.to_s.chop
|
45
|
+
define_method(val) { doc.xpath(".//#{val}/#{singular}").map(&:text) }
|
46
|
+
end
|
47
|
+
|
48
|
+
BOOLEAN_VALUES.each do |val|
|
49
|
+
define_method(:"#{val}?") { doc.at_xpath(".//#{val}").text == 'true' }
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)} #{name} (#{type})>"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :doc
|
59
|
+
|
60
|
+
def initialize(doc)
|
61
|
+
@doc = doc
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# Describe an object in Zuora: name, label, fields, etc.
|
6
|
+
#
|
7
|
+
class Object
|
8
|
+
# Raised when the XML does not provide the expected node (see #name)
|
9
|
+
#
|
10
|
+
class InvalidXML < StandardError; end
|
11
|
+
|
12
|
+
private_class_method :new
|
13
|
+
|
14
|
+
def self.from_xml(doc)
|
15
|
+
new(doc)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_connection(connection, name)
|
19
|
+
xml = connection.get("v1/describe/#{name}").body
|
20
|
+
new(Nokogiri::XML(xml))
|
21
|
+
rescue TypeError
|
22
|
+
# NOTE: Zuora returns HTTP 401 (unauthorized) roughly 1 out of 3 times
|
23
|
+
# we make this call. Since this is a setup-only call and not a
|
24
|
+
# runtime one, we deemed it acceptable to keep retrying until it
|
25
|
+
# works.
|
26
|
+
retry
|
27
|
+
rescue IronBank::InternalServerError
|
28
|
+
# TODO: Need to properly store which object failed to be described by
|
29
|
+
# Zuora API and send a report to the console.
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def export
|
34
|
+
File.open(file_path, 'w') { |file| file << doc.to_xml }
|
35
|
+
end
|
36
|
+
|
37
|
+
def name
|
38
|
+
node = doc.at_xpath('.//object/name')
|
39
|
+
raise InvalidXML unless node
|
40
|
+
|
41
|
+
node.text
|
42
|
+
end
|
43
|
+
|
44
|
+
def label
|
45
|
+
doc.at_xpath('.//object/label').text
|
46
|
+
end
|
47
|
+
|
48
|
+
def fields
|
49
|
+
@fields ||= doc.xpath('.//fields/field').map do |node|
|
50
|
+
IronBank::Describe::Field.from_xml(node)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def query_fields
|
55
|
+
@query_fields ||= fields.select(&:selectable?).map(&:name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def related
|
59
|
+
@related ||= doc.xpath('.//related-objects/object').map do |node|
|
60
|
+
IronBank::Describe::Related.from_xml(node)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)} #{name}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :doc
|
71
|
+
|
72
|
+
def initialize(doc)
|
73
|
+
@doc = doc
|
74
|
+
end
|
75
|
+
|
76
|
+
def file_path
|
77
|
+
File.expand_path "#{name}.xml", IronBank.configuration.schema_directory
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# Describe a related object in Zuora, e.g., an account has a default payment
|
6
|
+
# method
|
7
|
+
#
|
8
|
+
class Related
|
9
|
+
private_class_method :new
|
10
|
+
|
11
|
+
def self.from_xml(doc)
|
12
|
+
new(doc)
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
@type ||= doc.attributes['href'].value.split('/').last
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
doc.at_xpath('.//name').text
|
21
|
+
end
|
22
|
+
|
23
|
+
def label
|
24
|
+
doc.at_xpath('.//label').text
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)} #{name} (#{type})>"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :doc
|
34
|
+
|
35
|
+
def initialize(doc)
|
36
|
+
@doc = doc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# Describe a Zuora tenant, including its objects.
|
6
|
+
#
|
7
|
+
class Tenant
|
8
|
+
private_class_method :new
|
9
|
+
|
10
|
+
def self.from_xml(doc)
|
11
|
+
new(doc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_connection(connection)
|
15
|
+
xml = connection.get('v1/describe').body
|
16
|
+
new(Nokogiri::XML(xml), connection)
|
17
|
+
rescue TypeError
|
18
|
+
# NOTE: Zuora returns HTTP 401 (unauthorized) roughly 1 out of 3 times
|
19
|
+
# we make this call. Since this is a setup-only call and not a runtime
|
20
|
+
# one, we deemed it acceptable to keep retrying until it works.
|
21
|
+
retry
|
22
|
+
end
|
23
|
+
|
24
|
+
def objects
|
25
|
+
return object_names unless connection
|
26
|
+
|
27
|
+
@objects ||= object_names.map do |name|
|
28
|
+
IronBank::Describe::Object.from_connection(connection, name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :doc, :connection
|
39
|
+
|
40
|
+
def initialize(doc, connection = nil)
|
41
|
+
@doc = doc
|
42
|
+
@connection = connection
|
43
|
+
end
|
44
|
+
|
45
|
+
def object_names
|
46
|
+
@object_names ||= doc.xpath('.//object/name').map(&:text)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Identify and return the proper base URL for a given Zuora domain.
|
5
|
+
#
|
6
|
+
class Endpoint
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
PRODUCTION = /\Arest\.zuora\.com\z/i
|
10
|
+
SERVICES = /\Aservices(\d+)\.zuora\.com(:\d+)?\z/i
|
11
|
+
APISANDBOX = /\Arest.apisandbox.zuora\.com\z/i
|
12
|
+
|
13
|
+
def self.base_url(domain = '')
|
14
|
+
new(domain).base_url
|
15
|
+
end
|
16
|
+
|
17
|
+
def base_url
|
18
|
+
case domain
|
19
|
+
when PRODUCTION
|
20
|
+
'https://rest.zuora.com/'
|
21
|
+
when SERVICES
|
22
|
+
"https://#{domain}/".downcase
|
23
|
+
when APISANDBOX
|
24
|
+
'https://rest.apisandbox.zuora.com/'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :domain
|
31
|
+
|
32
|
+
def initialize(domain)
|
33
|
+
@domain = domain
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Custom error class for rescuing from all Zuora API errors
|
5
|
+
class Error < StandardError
|
6
|
+
# Returns the appropriate IronBank::Error subclass based on status and
|
7
|
+
# response message
|
8
|
+
def self.from_response(response)
|
9
|
+
status = response[:status].to_i
|
10
|
+
|
11
|
+
klass = begin
|
12
|
+
case status
|
13
|
+
when 400 then IronBank::BadRequest
|
14
|
+
when 404 then IronBank::NotFound
|
15
|
+
when 429 then IronBank::TooManyRequests
|
16
|
+
when 500 then IronBank::InternalServerError
|
17
|
+
when 400..499 then IronBank::ClientError
|
18
|
+
when 500..599 then IronBank::ServerError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
return unless klass
|
23
|
+
|
24
|
+
klass.new(response)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Raised on errors in the 400-499 range
|
29
|
+
class ClientError < Error; end
|
30
|
+
|
31
|
+
# Raised when Zuora returns a 400 HTTP status code
|
32
|
+
class BadRequest < ClientError; end
|
33
|
+
|
34
|
+
# Raised when Zuora returns a 404 HTTP status code
|
35
|
+
class NotFound < ClientError; end
|
36
|
+
|
37
|
+
# Raised when Zuora returns a 429 HTTP status code
|
38
|
+
class TooManyRequests < ClientError; end
|
39
|
+
|
40
|
+
# Raised on errors in the 500-599 range
|
41
|
+
class ServerError < Error; end
|
42
|
+
|
43
|
+
# Raised when Zuora returns a 500 HTTP status code
|
44
|
+
class InternalServerError < ServerError; end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Instrumentation helper module
|
5
|
+
module Instrumentation
|
6
|
+
def instrumenter
|
7
|
+
IronBank.configuration.instrumenter
|
8
|
+
end
|
9
|
+
|
10
|
+
def instrumenter_options
|
11
|
+
IronBank.configuration.instrumenter_options || {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# A local store for exported records.
|
5
|
+
#
|
6
|
+
module Local
|
7
|
+
def find(id)
|
8
|
+
store[id] || super
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_each
|
12
|
+
return enum_for(:find_each) unless block_given?
|
13
|
+
|
14
|
+
values = store.values
|
15
|
+
values.any? ? values.each { |record| yield record } : super
|
16
|
+
end
|
17
|
+
|
18
|
+
def all
|
19
|
+
store.any? ? store.values : super
|
20
|
+
end
|
21
|
+
|
22
|
+
def where(conditions)
|
23
|
+
records = store.values.select do |record|
|
24
|
+
conditions.all? { |field, value| record.public_send(field) == value }
|
25
|
+
end
|
26
|
+
|
27
|
+
records.any? ? records : super
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_store
|
31
|
+
@store = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def store
|
37
|
+
@store ||= File.exist?(file_path) ? load_records : {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_records
|
41
|
+
CSV.foreach(file_path, csv_options).with_object({}) do |row, store|
|
42
|
+
# NOTE: when we move away from Ruby 2.2.x and 2.3.x we can uncomment
|
43
|
+
# this line, delete the other one, since `Hash#compact` is available in
|
44
|
+
# 2.4.x and we can remove two smells from `.reek` while we are at it
|
45
|
+
#
|
46
|
+
# store[row['Id']] = new(row.to_h.compact)
|
47
|
+
store[row['Id']] = new(row.to_h.reject { |_, value| value.nil? })
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def csv_options
|
52
|
+
{
|
53
|
+
headers: true,
|
54
|
+
converters: csv_converters
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def csv_converters
|
59
|
+
%i[
|
60
|
+
decimal_integer
|
61
|
+
decimal_float
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
def file_path
|
66
|
+
File.expand_path(
|
67
|
+
"#{object_name}.csv",
|
68
|
+
IronBank.configuration.export_directory
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Utility class to save records locally.
|
5
|
+
#
|
6
|
+
class LocalRecords
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
RESOURCES = %w[
|
10
|
+
Product
|
11
|
+
ProductRatePlan
|
12
|
+
ProductRatePlanCharge
|
13
|
+
ProductRatePlanChargeTier
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def self.directory
|
17
|
+
IronBank.configuration.export_directory
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.export
|
21
|
+
FileUtils.mkdir_p(directory) unless Dir.exist?(directory)
|
22
|
+
RESOURCES.each { |resource| new(resource).export }
|
23
|
+
end
|
24
|
+
|
25
|
+
def export
|
26
|
+
CSV.open(file_path, 'w') do |csv|
|
27
|
+
csv << klass.fields # headers
|
28
|
+
write_records(csv)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :resource
|
35
|
+
|
36
|
+
def initialize(resource)
|
37
|
+
@resource = resource
|
38
|
+
end
|
39
|
+
|
40
|
+
def klass
|
41
|
+
IronBank::Resources.const_get(resource)
|
42
|
+
end
|
43
|
+
|
44
|
+
def file_path
|
45
|
+
File.expand_path("#{resource}.csv", self.class.directory)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_records(csv)
|
49
|
+
klass.find_each { |record| csv << record.to_csv_row }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|