peddler 2.1.1 → 2.4.4

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -41
  3. data/lib/mws.rb +2 -1
  4. data/lib/mws/easy_ship.rb +3 -0
  5. data/lib/mws/easy_ship/client.rb +93 -0
  6. data/lib/mws/fulfillment_outbound_shipment/client.rb +1 -1
  7. data/lib/mws/orders/client.rb +7 -9
  8. data/lib/mws/shipment_invoicing.rb +3 -0
  9. data/lib/mws/shipment_invoicing/client.rb +74 -0
  10. data/lib/mws/subscriptions/client.rb +2 -2
  11. data/lib/peddler/client.rb +17 -13
  12. data/lib/peddler/errors/builder.rb +7 -2
  13. data/lib/peddler/errors/class_generator.rb +9 -4
  14. data/lib/peddler/errors/error.rb +6 -2
  15. data/lib/peddler/errors/parser.rb +1 -1
  16. data/lib/peddler/flat_file_parser.rb +5 -3
  17. data/lib/peddler/headers.rb +6 -1
  18. data/lib/peddler/marketplace.rb +25 -19
  19. data/lib/peddler/operation.rb +17 -11
  20. data/lib/peddler/parser.rb +1 -1
  21. data/lib/peddler/structured_list.rb +1 -1
  22. data/lib/peddler/vcr_matcher.rb +7 -7
  23. data/lib/peddler/version.rb +1 -1
  24. data/lib/peddler/xml_parser.rb +1 -1
  25. data/lib/peddler/xml_response_parser.rb +3 -2
  26. metadata +15 -152
  27. data/lib/mws/off_amazon_payments/client.rb +0 -362
  28. data/lib/peddler/content.rb +0 -69
  29. data/lib/peddler/mws.rb +0 -58
  30. data/test/credentials.rb +0 -23
  31. data/test/helper.rb +0 -15
  32. data/test/integration/internals/test_errors.rb +0 -17
  33. data/test/integration/internals/test_multibyte_queries.rb +0 -17
  34. data/test/integration/internals/test_mws_headers.rb +0 -21
  35. data/test/integration/internals/test_string_encodings.rb +0 -38
  36. data/test/integration/test_feeds.rb +0 -37
  37. data/test/integration/test_fulfillment_inbound_shipment.rb +0 -35
  38. data/test/integration/test_fulfillment_inventory.rb +0 -20
  39. data/test/integration/test_fulfillment_outbound_shipment.rb +0 -13
  40. data/test/integration/test_merchant_fulfillment.rb +0 -69
  41. data/test/integration/test_off_amazon_payments.rb +0 -13
  42. data/test/integration/test_orders.rb +0 -30
  43. data/test/integration/test_products.rb +0 -97
  44. data/test/integration/test_recommendations.rb +0 -20
  45. data/test/integration/test_reports.rb +0 -51
  46. data/test/integration/test_sellers.rb +0 -13
  47. data/test/integration/test_subscriptions.rb +0 -27
  48. data/test/integration_helper.rb +0 -53
  49. data/test/mws.yml +0 -36
  50. data/test/mws.yml.example +0 -32
  51. data/test/null_client.rb +0 -25
  52. data/test/recorder.rb +0 -39
  53. data/test/unit/mws/test_feeds_client.rb +0 -103
  54. data/test/unit/mws/test_finances_client.rb +0 -74
  55. data/test/unit/mws/test_fulfillment_inbound_shipment_client.rb +0 -426
  56. data/test/unit/mws/test_fulfillment_inventory_client.rb +0 -48
  57. data/test/unit/mws/test_fulfillment_outbound_shipment_client.rb +0 -202
  58. data/test/unit/mws/test_merchant_fulfillment_client.rb +0 -109
  59. data/test/unit/mws/test_off_amazon_payments_client.rb +0 -286
  60. data/test/unit/mws/test_orders_client.rb +0 -111
  61. data/test/unit/mws/test_products_client.rb +0 -248
  62. data/test/unit/mws/test_recommendations_client.rb +0 -62
  63. data/test/unit/mws/test_reports_client.rb +0 -209
  64. data/test/unit/mws/test_sellers_client.rb +0 -47
  65. data/test/unit/mws/test_subscriptions_client.rb +0 -182
  66. data/test/unit/peddler/errors/test_builder.rb +0 -65
  67. data/test/unit/peddler/errors/test_class_generator.rb +0 -18
  68. data/test/unit/peddler/errors/test_error.rb +0 -33
  69. data/test/unit/peddler/errors/test_parser.rb +0 -44
  70. data/test/unit/peddler/test_client.rb +0 -203
  71. data/test/unit/peddler/test_flat_file_parser.rb +0 -111
  72. data/test/unit/peddler/test_headers.rb +0 -103
  73. data/test/unit/peddler/test_marketplace.rb +0 -47
  74. data/test/unit/peddler/test_operation.rb +0 -87
  75. data/test/unit/peddler/test_parser.rb +0 -36
  76. data/test/unit/peddler/test_structured_list.rb +0 -39
  77. data/test/unit/peddler/test_vcr_matcher.rb +0 -55
  78. data/test/unit/peddler/test_xml_parser.rb +0 -37
  79. data/test/unit/peddler/test_xml_response_parser.rb +0 -39
  80. data/test/unit/test_mws.rb +0 -11
  81. data/test/vcr_cassettes/CartInformation.yml +0 -433
  82. data/test/vcr_cassettes/CustomerInformation.yml +0 -433
  83. data/test/vcr_cassettes/Errors.yml +0 -444
  84. data/test/vcr_cassettes/Feeds.yml +0 -9957
  85. data/test/vcr_cassettes/FulfillmentInboundShipment.yml +0 -6166
  86. data/test/vcr_cassettes/FulfillmentInventory.yml +0 -850
  87. data/test/vcr_cassettes/FulfillmentOutboundShipment.yml +0 -433
  88. data/test/vcr_cassettes/MWSHeaders.yml +0 -2964
  89. data/test/vcr_cassettes/MerchantFulfillment.yml +0 -753
  90. data/test/vcr_cassettes/MultibyteQueries.yml +0 -437
  91. data/test/vcr_cassettes/OffAmazonPayments.yml +0 -433
  92. data/test/vcr_cassettes/Orders.yml +0 -4740
  93. data/test/vcr_cassettes/PeddlerVCRMatcher.yml +0 -41
  94. data/test/vcr_cassettes/Products.yml +0 -7139
  95. data/test/vcr_cassettes/Recommendations.yml +0 -3145
  96. data/test/vcr_cassettes/Reports.yml +0 -5549
  97. data/test/vcr_cassettes/Sellers.yml +0 -433
  98. data/test/vcr_cassettes/Subscriptions.yml +0 -1529
@@ -139,7 +139,7 @@ module MWS
139
139
  marketplace_id)
140
140
  operation('UpdateSubscription')
141
141
  .add('MarketplaceId' => marketplace_id)
142
- .add(build_subscription(notification_type, sqs_queue_url, enabled))
142
+ .add(build_subscription(notification_type, sqs_queue_url, enabled: enabled))
143
143
 
144
144
  run
145
145
  end
@@ -163,7 +163,7 @@ module MWS
163
163
  }
164
164
  end
165
165
 
166
- def build_subscription(notification_type, sqs_queue_url, enabled = true)
166
+ def build_subscription(notification_type, sqs_queue_url, enabled: true)
167
167
  {
168
168
  'Subscription.IsEnabled' => enabled,
169
169
  'Subscription.NotificationType' => notification_type,
@@ -16,12 +16,13 @@ module Peddler
16
16
  include Jeff
17
17
 
18
18
  class << self
19
- # @api private
19
+ # @!visibility private
20
20
  attr_accessor :parser, :path, :version
21
21
 
22
22
  private
23
23
 
24
24
  def inherited(base)
25
+ super
25
26
  base.parser = parser
26
27
  base.params params
27
28
  end
@@ -52,6 +53,7 @@ module Peddler
52
53
  attr_accessor :auth_token
53
54
 
54
55
  # The seller's Merchant ID
56
+ # @note Amazon also refers to this as Seller ID or Merchant Token
55
57
  # @return [String]
56
58
  attr_accessor :merchant_id
57
59
 
@@ -80,38 +82,38 @@ module Peddler
80
82
  str ? add_content(str) : clear_content!
81
83
  end
82
84
 
83
- # @api private
85
+ # @!visibility private
84
86
  attr_writer :path
85
87
 
86
- # @api private
88
+ # @!visibility private
87
89
  def path
88
90
  @path ||= self.class.path
89
91
  end
90
92
 
91
- # @api private
93
+ # @!visibility private
92
94
  def defaults
93
95
  @defaults ||= { expects: 200 }
94
96
  end
95
97
 
96
- # @api private
98
+ # @!visibility private
97
99
  def headers
98
100
  @headers ||= {}
99
101
  end
100
102
 
101
- # @api private
103
+ # @!visibility private
102
104
  def aws_endpoint
103
105
  "https://#{host}#{path}"
104
106
  end
105
107
 
106
- # @api private
108
+ # @!visibility private
107
109
  def operation(action = nil)
108
110
  action ? @operation = Operation.new(action) : @operation
109
111
  end
110
112
 
111
- # @api private
112
- def run
113
+ # @!visibility private
114
+ def run(&block)
113
115
  opts = build_options
114
- opts.store(:response_block, Proc.new) if block_given?
116
+ opts.store(:response_block, block) if block_given?
115
117
  res = post(opts)
116
118
  self.body = nil if res.status == 200
117
119
 
@@ -128,13 +130,15 @@ module Peddler
128
130
  end
129
131
 
130
132
  def add_content(content)
131
- if content.start_with?('<?xml')
133
+ @body = content
134
+ if content.encoding.names.include?('BINARY')
135
+ headers['Content-Type'] = 'application/octet-stream'
136
+ elsif content.start_with?('<?xml')
132
137
  headers['Content-Type'] = 'text/xml'
133
- @body = content
134
138
  else
135
139
  headers['Content-Type'] =
136
140
  "text/tab-separated-values; charset=#{encoding}"
137
- @body = content.encode(encoding)
141
+ @body = @body.encode(encoding)
138
142
  end
139
143
  end
140
144
 
@@ -7,7 +7,7 @@ require 'peddler/errors/parser'
7
7
 
8
8
  module Peddler
9
9
  module Errors
10
- # @api private
10
+ # @!visibility private
11
11
  class Builder
12
12
  extend Forwardable
13
13
 
@@ -28,6 +28,7 @@ module Peddler
28
28
 
29
29
  def build
30
30
  parse_original_response
31
+ return if no_error_response?
31
32
  return if bad_class_name?
32
33
 
33
34
  error_class.new(error_message, error)
@@ -36,7 +37,11 @@ module Peddler
36
37
  private
37
38
 
38
39
  def bad_class_name?
39
- error_name =~ DIGIT_AS_FIRST_CHAR
40
+ error_name.match?(DIGIT_AS_FIRST_CHAR)
41
+ end
42
+
43
+ def no_error_response?
44
+ response.parse.nil?
40
45
  end
41
46
 
42
47
  def error_class
@@ -5,7 +5,7 @@ require 'peddler/errors/error'
5
5
 
6
6
  module Peddler
7
7
  module Errors
8
- # @api private
8
+ # @!visibility private
9
9
  class ClassGenerator
10
10
  include Singleton
11
11
 
@@ -19,7 +19,12 @@ module Peddler
19
19
 
20
20
  def generate(name)
21
21
  with_mutex do
22
- return Errors.const_get(name) if Errors.const_defined?(name)
22
+ if Errors.const_defined?(name)
23
+ error = Errors.const_get(name)
24
+ return error if error <= Error
25
+
26
+ raise TypeError, "#{name} must be a Peddler::Errors::Error"
27
+ end
23
28
 
24
29
  Errors.const_set(name, Class.new(Error))
25
30
  end
@@ -27,8 +32,8 @@ module Peddler
27
32
 
28
33
  private
29
34
 
30
- def with_mutex
31
- @mutex.synchronize { yield }
35
+ def with_mutex(&block)
36
+ @mutex.synchronize(&block)
32
37
  end
33
38
  end
34
39
  end
@@ -3,11 +3,15 @@
3
3
  require 'forwardable'
4
4
 
5
5
  module Peddler
6
- # @api private
6
+ # @!visibility private
7
7
  module Errors
8
8
  # These error codes are common to all Amazon MWS API sections.
9
9
  #
10
10
  # @see https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Errors.html
11
+ #
12
+ # There are quite a few other error types they do not explicitly document
13
+ # above. I decided not to define any for the sake of consistency and future
14
+ # maintenance.
11
15
  CODES = %w[
12
16
  AccessDenied
13
17
  InputStreamDisconnected
@@ -20,7 +24,7 @@ module Peddler
20
24
  SignatureDoesNotMatch
21
25
  ].freeze
22
26
 
23
- # @api private
27
+ # @!visibility private
24
28
  class Error < StandardError
25
29
  extend Forwardable
26
30
 
@@ -4,7 +4,7 @@ require 'peddler/xml_parser'
4
4
 
5
5
  module Peddler
6
6
  module Errors
7
- # @api private
7
+ # @!visibility private
8
8
  class Parser < XMLParser
9
9
  def message
10
10
  parse['Message']
@@ -6,7 +6,7 @@ require 'digest/md5'
6
6
  require 'peddler/headers'
7
7
 
8
8
  module Peddler
9
- # @api private
9
+ # @!visibility private
10
10
  class FlatFileParser < SimpleDelegator
11
11
  include Headers
12
12
 
@@ -23,7 +23,7 @@ module Peddler
23
23
  end
24
24
 
25
25
  def parse(&blk)
26
- CSV.parse(content, OPTIONS, &blk) unless content.empty?
26
+ CSV.parse(content, **OPTIONS, &blk) unless content.empty?
27
27
  end
28
28
 
29
29
  def records_count
@@ -44,7 +44,9 @@ module Peddler
44
44
 
45
45
  def extract_content_and_summary
46
46
  @content = body.encode('UTF-8', invalid: :replace, undef: :replace)
47
- @summary, @content = @content.split("\n\n", 2) if @content =~ /\t\t.*\n\n/
47
+ return unless @content.match?(/\t\t.*\n\n/)
48
+
49
+ @summary, @content = @content.split("\n\n", 2)
48
50
  end
49
51
 
50
52
  def summarize
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+
3
5
  module Peddler
4
6
  # Parses useful metadata returned in response headers
5
7
  module Headers
@@ -47,7 +49,10 @@ module Peddler
47
49
  match_data = headers['Content-Type']&.match(/charset=(.*);?/)
48
50
  return unless match_data
49
51
 
50
- Encoding.find(match_data[1])
52
+ string = match_data[1].upcase
53
+ string = 'UTF-8' if string == 'UTF8'
54
+
55
+ Encoding.find(string)
51
56
  end
52
57
 
53
58
  # The max hourly request quota for the requested operation
@@ -1,29 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Peddler
4
- # @api private
4
+ # @!visibility private
5
5
  # @see https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Endpoints.html
6
- Marketplace = Struct.new(:id, :country_code, :host) do
6
+ class Marketplace
7
7
  class << self
8
8
  attr_reader :all
9
9
 
10
10
  def find(key)
11
- marketplace = if key.nil?
12
- missing_key!
13
- elsif key.size == 2
14
- find_by_country_code(key)
15
- else
16
- find_by_id(key)
17
- end
11
+ missing_key! unless key
12
+ marketplace = key.size == 2 ? find_by_country(key) : find_by_id(key)
18
13
 
19
14
  marketplace || not_found!(key)
20
15
  end
21
16
 
22
17
  private
23
18
 
24
- def find_by_country_code(country_code)
25
- country_code = 'GB' if country_code == 'UK'
26
- all.find { |marketplace| marketplace.country_code == country_code }
19
+ def find_by_country(code)
20
+ code = 'GB' if code == 'UK'
21
+ all.find { |marketplace| marketplace.country_code == code }
27
22
  end
28
23
 
29
24
  def find_by_id(id)
@@ -39,32 +34,43 @@ module Peddler
39
34
  end
40
35
  end
41
36
 
37
+ attr_reader :id, :country_code, :host
38
+
39
+ def initialize(id, country_code, host)
40
+ @id = id
41
+ @country_code = country_code
42
+ @host = host
43
+ end
44
+
42
45
  def encoding
43
46
  case country_code
44
47
  when 'JP'
45
48
  'Windows-31J'
46
- when 'CN'
47
- 'UTF-16'
48
49
  else
49
50
  'CP1252'
50
51
  end
51
52
  end
52
53
 
53
54
  @all = [
55
+ ['A2Q3Y263D00KWC', 'BR', 'mws.amazonservices.com'],
54
56
  ['A2EUQ1WTGCTBG2', 'CA', 'mws.amazonservices.com'],
55
57
  ['A1AM78C64UM0Y8', 'MX', 'mws.amazonservices.com'],
56
58
  ['ATVPDKIKX0DER', 'US', 'mws.amazonservices.com'],
57
- ['A2Q3Y263D00KWC', 'BR', 'mws.amazonservices.com'],
59
+ ['A2VIGQ35RCS4UG', 'AE', 'mws.amazonservices.ae'],
58
60
  ['A1PA6795UKMFR9', 'DE', 'mws-eu.amazonservices.com'],
61
+ ['ARBP9OOSHTCHU', 'EG', 'mws-eu.amazonservices.com'],
59
62
  ['A1RKKUPIHCS9HS', 'ES', 'mws-eu.amazonservices.com'],
60
63
  ['A13V1IB3VIYZZH', 'FR', 'mws-eu.amazonservices.com'],
61
- ['APJ6JRA9NG5V4', 'IT', 'mws-eu.amazonservices.com'],
62
64
  ['A1F83G8C2ARO7P', 'GB', 'mws-eu.amazonservices.com'],
63
- ['A33AVAJ2PDY3EV', 'TR', 'mws-eu.amazonservices.com'],
64
65
  ['A21TJRUUN4KGV', 'IN', 'mws.amazonservices.in'],
66
+ ['APJ6JRA9NG5V4', 'IT', 'mws-eu.amazonservices.com'],
67
+ ['A1805IZSGTT6HS', 'NL', 'mws-eu.amazonservices.com'],
68
+ ['A17E79C6D8DWNP', 'SA', 'mws-eu.amazonservices.com'],
69
+ ['A33AVAJ2PDY3EV', 'TR', 'mws-eu.amazonservices.com'],
70
+ ['A2NODRKZP88ZB9', 'SE', 'mws-eu.amazonservices.com'],
71
+ ['A19VAU5U5O7RUS', 'SG', 'mws-fe.amazonservices.com'],
65
72
  ['A39IBJ37TRP1C6', 'AU', 'mws.amazonservices.com.au'],
66
- ['A1VC38T7YXB528', 'JP', 'mws.amazonservices.jp'],
67
- ['AAHKV2X7AFYLW', 'CN', 'mws.amazonservices.com.cn']
73
+ ['A1VC38T7YXB528', 'JP', 'mws.amazonservices.jp']
68
74
  ].map do |values|
69
75
  new(*values)
70
76
  end
@@ -5,7 +5,7 @@ require 'time'
5
5
  require 'peddler/structured_list'
6
6
 
7
7
  module Peddler
8
- # @api private
8
+ # @!visibility private
9
9
  class Operation < SimpleDelegator
10
10
  CAPITAL_LETTERS = /[A-Z]/.freeze
11
11
  ALL_CAPS = %w[sku cod].freeze
@@ -28,15 +28,12 @@ module Peddler
28
28
  self
29
29
  end
30
30
 
31
- def store(key, val, parent: '')
32
- key = camelize(key) if key.is_a?(Symbol)
33
- key = "#{parent}.#{key}" unless parent.empty?
34
-
35
- val = val.iso8601 if val.respond_to?(:iso8601)
36
- val = val.to_h if val.is_a?(Struct)
31
+ def store(key, val, parent = nil)
32
+ key = [parent, camelize(key)].compact.join('.')
33
+ val = format_known_types(val)
37
34
 
38
35
  if val.is_a?(Hash)
39
- val.each { |keyval| store(*keyval, parent: key) }
36
+ val.each { |keyval| store(*keyval, key) }
40
37
  else
41
38
  __getobj__.store(key, val)
42
39
  end
@@ -51,10 +48,11 @@ module Peddler
51
48
 
52
49
  private
53
50
 
54
- def camelize(sym)
55
- return sym.to_s if sym =~ CAPITAL_LETTERS
51
+ def camelize(key)
52
+ return key unless key.is_a?(Symbol)
53
+ return key.to_s if key.match?(CAPITAL_LETTERS)
56
54
 
57
- sym
55
+ key
58
56
  .to_s
59
57
  .split('_')
60
58
  .map { |token| capitalize(token) }
@@ -68,5 +66,13 @@ module Peddler
68
66
  word.capitalize
69
67
  end
70
68
  end
69
+
70
+ def format_known_types(val)
71
+ val = val.utc.iso8601(2) if val.is_a?(Time)
72
+ val = val.iso8601 if val.is_a?(Date)
73
+ val = val.to_h if val.is_a?(Struct)
74
+
75
+ val
76
+ end
71
77
  end
72
78
  end
@@ -4,7 +4,7 @@ require 'peddler/flat_file_parser'
4
4
  require 'peddler/xml_response_parser'
5
5
 
6
6
  module Peddler
7
- # @api private
7
+ # @!visibility private
8
8
  module Parser
9
9
  class << self
10
10
  # We're massaging data produced by a motley army of developers. It's
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Peddler
4
- # @api private
4
+ # @!visibility private
5
5
  class StructuredList
6
6
  def initialize(*keys)
7
7
  @keys = keys
@@ -4,23 +4,23 @@ module Peddler
4
4
  # A custom matcher that can be used to record MWS interactions when
5
5
  # writing integration tests
6
6
  class VCRMatcher
7
- # @api private
7
+ # @!visibility private
8
8
  TRANSIENT_PARAMS = %w[
9
9
  Signature Timestamp StartDate CreatedAfter QueryStartDateTime
10
10
  ].freeze
11
11
 
12
- # @api private
12
+ # @!visibility private
13
13
  SELLER_PARAMS = %w[
14
14
  AWSAccessKeyId SellerId
15
15
  ].freeze
16
16
 
17
17
  class << self
18
- # @api private
18
+ # @!visibility private
19
19
  def call(*requests)
20
20
  new(*requests).compare
21
21
  end
22
22
 
23
- # @api private
23
+ # @!visibility private
24
24
  def ignored_params
25
25
  @ignored_params ||= TRANSIENT_PARAMS.dup
26
26
  end
@@ -33,15 +33,15 @@ module Peddler
33
33
  end
34
34
  end
35
35
 
36
- # @api private
36
+ # @!visibility private
37
37
  attr_reader :requests
38
38
 
39
- # @api private
39
+ # @!visibility private
40
40
  def initialize(*requests)
41
41
  @requests = requests
42
42
  end
43
43
 
44
- # @api private
44
+ # @!visibility private
45
45
  def compare
46
46
  compare_uris && compare_bodies
47
47
  end