vacuum 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de7766702e1ee1d5084f61f8a337ff64b9e1210fd70167ee8e932b742eb522e5
4
- data.tar.gz: b2a917e6f5d91bf0177471a65d319bc0d4438b773643d977f53c148d7d2f3770
3
+ metadata.gz: 93f97f47ef585594e52efc75ac3824e47992ad0ac6cbd688f05363579c9feff3
4
+ data.tar.gz: 4d0399b9b897cd955cb3c2739a3b2fcb4e393c6ee092270d21d28dab8ee503fc
5
5
  SHA512:
6
- metadata.gz: 276f630beba5150eb04b20c81ab9feb41db2488773e548da9d9f0df8c3b6a4197eb1948b84d467614de450b74e81023ef8ce5533abb03f4ebf8fec27a322b8a5
7
- data.tar.gz: 87b683c87cb51462030f0ba2880e54e1b683723179a658f94dec92cad496b5ae5133c8cfd70b2844b187a108162adca3a3f4153cc5c45a5301826342e9040507
6
+ metadata.gz: eac6cca761cd2ccc75e7eb9d626398505c06400116efe5bb765e5112debb0f3b22da3dab840aa1bbde19de752d841bd836029c6c1bb7f3b12738d74bfecd0e3b
7
+ data.tar.gz: 8571a8e9c4b64b71038bf7713364c8b56bca14bb08abae6743aaf2cea0a976fe1b0025432ad659b300d8d5763deb05bd9ff4200bdfcb80e52de9a2f3d3e8952c
data/README.md CHANGED
@@ -1,211 +1,138 @@
1
1
  # Vacuum
2
- [![Travis](https://travis-ci.org/hakanensari/vacuum.svg)](https://travis-ci.org/hakanensari/vacuum)
3
2
 
4
- Vacuum is a fast, light-weight Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html).
3
+ [![CircleCI](https://circleci.com/gh/hakanensari/vacuum/tree/master.svg?style=svg)](https://circleci.com/gh/hakanensari/vacuum/tree/master)
4
+
5
+ Vacuum is a light-weight Ruby wrapper to [Amazon Product Advertising API 5.0](https://webservices.amazon.com/paapi5/documentation/). The API provides programmatic access to search and get detailed product information on the Amazon marketplaces.
5
6
 
6
7
  ![vacuum](http://f.cl.ly/items/2k2X0e2u0G3k1c260D2u/vacuum.png)
7
8
 
8
9
  ## Usage
9
10
 
10
- Refer to the [API docs](https://docs.aws.amazon.com/AWSECommerceService/latest/DG/CHAP_ApiReference.html) as necessary.
11
-
12
- ### Prerequisite
11
+ Vacuum follows the nomenclature of the Product Advertising API. The examples below are based on examples in the [Amazon docs](https://webservices.amazon.com/paapi5/documentation/).
13
12
 
14
- You must [register as an affiliate](https://affiliate-program.amazon.com) to access the API. Amazon will issue your AWS credentials when you register.
13
+ ### Getting Started
15
14
 
16
- ### Setup
15
+ You need to [register as an affiliate](https://affiliate-program.amazon.com) and [apply for API access](https://affiliate-program.amazon.com/assoc_credentials/home) on each marketplace you want to query product information.
17
16
 
18
- Create a request:
17
+ Create a request with your marketplace credentials, passing the two-letter country code of the marketplace.
19
18
 
20
19
  ```ruby
21
- request = Vacuum.new
20
+ request = Vacuum.new(marketplace: 'US',
21
+ access_key: '<ACCESS_KEY>',
22
+ secret_key: '<SECRET_KEY>',
23
+ partner_tag: '<PARTNER_TAG>')
22
24
  ```
23
25
 
24
- The locale will default to the US. To use another locale, reference its two-letter country code:
26
+ Vacuum uses [HTTPI](https://github.com/savonrb/httpi) under the hood. You can swap the HTTP library it uses if you prefer an alternative one for speed or introspection.
25
27
 
26
28
  ```ruby
27
- request = Vacuum.new('GB')
29
+ HTTPI.adapter = :http
28
30
  ```
29
31
 
30
- Configure the request credentials:
32
+ ### Operations
31
33
 
32
- ```ruby
33
- request.configure(
34
- aws_access_key_id: 'key',
35
- aws_secret_access_key: 'secret',
36
- associate_tag: 'tag'
37
- )
38
- ```
34
+ #### GetBrowseNodes
39
35
 
40
- You can omit the above if you set your key and secret as environment variables:
41
-
42
- ```sh
43
- export AWS_ACCESS_KEY_ID=key
44
- export AWS_SECRET_ACCESS_KEY=secret
45
- ```
46
-
47
- You will still need to set an associate tag:
36
+ Given a BrowseNodeId, the `GetBrowseNodes` operation returns details about the specified browse node like name, children and ancestors depending on the resources specified in the request. The names and browse node IDs of the children and ancestor browse nodes are also returned. `GetBrowseNodes` enables you to traverse the browse node hierarchy to find a browse node.
48
37
 
49
38
  ```ruby
50
- request.associate_tag = 'tag'
39
+ request.get_browse_nodes(
40
+ browse_node_ids: ['283155', '3040'],
41
+ resources: ['BrowseNodes.Ancestor', 'BrowseNodes.Children']
42
+ )
51
43
  ```
52
44
 
53
- Provided you are looking to earn commission, you have to register independently with each locale you query. Otherwise, you may reuse any dummy associate tag.
45
+ #### GetItems
54
46
 
55
- The API version defaults to `2013-08-01`. To use another version, reference its date string:
47
+ Given an Item identifier, the `GetItems` operation returns the item attributes, based on the resources specified in the request.
56
48
 
57
49
  ```ruby
58
- request.version = '2011-08-01'
50
+ request.get_items(
51
+ item_ids: ['B0199980K4', 'B000HZD168', 'B01180YUXS', 'B00BKQTA4A'],
52
+ resources: ['Images.Primary.Small', 'ItemInfo.Title', 'ItemInfo.Features',
53
+ 'Offers.Summaries.HighestPrice' , 'ParentASIN']
54
+ )
59
55
  ```
60
56
 
61
- ### Request
62
-
63
- #### Browse Node Lookup
57
+ #### GetVariations
64
58
 
65
- **BrowseNodeLookup** returns a specified browse node’s name and ancestors:
59
+ Given an ASIN, the `GetVariations` operation returns a set of items that are the same product, but differ according to a consistent theme, for example size and color. These items which differ according to a consistent theme are called variations. A variation is a child ASIN. The parent ASIN is an abstraction of the children items. For example, a shirt is a parent ASIN and parent ASINs cannot be sold. A child ASIN would be a blue shirt, size 16, sold by MyApparelStore. This child ASIN is one of potentially many variations. The ways in which variations differ are called dimensions.
66
60
 
67
61
  ```ruby
68
- response = request.browse_node_lookup(
69
- query: {
70
- 'BrowseNodeId' => 123
71
- }
62
+ request.get_variations(
63
+ asin: 'B00422MCUS',
64
+ resources: ['ItemInfo.Title', 'VariationSummary.Price.HighestPrice',
65
+ 'VariationSummary.Price.LowestPrice',
66
+ 'VariationSummary.VariationDimension']
72
67
  )
73
68
  ```
74
69
 
75
- #### Cart Operations
70
+ #### SearchItems
76
71
 
77
- The **CartCreate** operation creates a remote shopping cart:
72
+ The `SearchItems` operation searches for items on Amazon based on a search query. The Amazon Product Advertising API returns up to ten items per search request.
78
73
 
79
74
  ```ruby
80
- response = request.cart_create(
81
- query: {
82
- 'HMAC' => 'secret',
83
- 'Item.1.OfferListingId' => '123',
84
- 'Item.1.Quantity' => 1
85
- }
86
- )
75
+ request.search_items(keywords: 'harry potter')
87
76
  ```
88
77
 
89
- The **CartAdd** operation adds items to an existing remote shopping cart:
90
-
91
- ```ruby
92
- response = request.cart_add(
93
- query: {
94
- 'CartId' => '123',
95
- 'HMAC' => 'secret',
96
- 'Item.1.OfferListingId' => '123',
97
- 'Item.1.Quantity' => 1
98
- }
99
- )
100
- ```
78
+ ### Response
101
79
 
102
- The **CartClear** operation removes all of the items in a remote shopping cart:
80
+ The quick and dirty way to consume a response is to parse into a Ruby hash:
103
81
 
104
82
  ```ruby
105
- response = request.cart_clear(
106
- query: {
107
- 'CartId' => '123',
108
- 'HMAC' => 'secret'
109
- }
110
- )
83
+ response.to_h
111
84
  ```
112
85
 
113
- The **CartGet** operation retrieves the IDs, quantities, and prices of the items, including SavedForLater ones, in a remote shopping cart:
86
+ You can also `#dig` into the returned Hash:
114
87
 
115
88
  ```ruby
116
- response = request.cart_get(
117
- query: {
118
- 'CartId' => '123',
119
- 'HMAC' => 'secret',
120
- 'CartItemId' => '123'
121
- }
122
- )
89
+ response.dig('ItemsResult', 'Items')
123
90
  ```
124
91
 
125
- #### Item Lookup
126
-
127
- The **ItemLookup** operation returns some or all of the attributes of an item, depending on the response group specified in the request. By default, the operation returns an item’s ASIN, manufacturer, product group, and title.
92
+ You can extend Vacuum with a custom parser. Just swap the original with a class or module that responds to `.parse`.
128
93
 
129
94
  ```ruby
130
- response = request.item_lookup(
131
- query: {
132
- 'ItemId' => '0679753354'
133
- }
134
- )
95
+ response.parser = MyParser
96
+ response.parse
135
97
  ```
136
98
 
137
- #### Item Search
138
-
139
- The **ItemSearch** operation returns items that satisfy the search criteria, including one or more search indices.
140
-
141
- ```ruby
142
- response = request.item_search(
143
- query: {
144
- 'Keywords' => 'Architecture',
145
- 'SearchIndex' => 'Books'
146
- }
147
- )
148
- ```
99
+ If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
149
100
 
150
- #### Similarity Lookup
101
+ ### VCR Support
151
102
 
152
- The **SimilarityLookup** operation returns up to ten products per page that are similar to one or more items specified in the request. This operation is typically used to pique a customer’s interest in buying something similar to what they’ve already ordered.
103
+ If you are using [VCR](https://github.com/vcr/vcr) to test an app that accesses the Product Advertising API, you can use the custom VCR matcher of Vacuum to stub requests.
153
104
 
154
105
  ```ruby
155
- response = request.similarity_lookup(
156
- query: {
157
- 'ItemId' => '0679753354'
158
- }
159
- )
160
- ```
106
+ require 'vacuum/matcher'
161
107
 
162
- #### Configuring a request
108
+ VCR.insert_cassette('paapi', match_requests_on: [Vacuum::Matcher])
109
+ ```
163
110
 
164
- Vacuum wraps [Excon](https://github.com/geemus/excon). Use the latter's API to tweak your request.
111
+ ## Testing
165
112
 
166
- For example, to use a persistent connection:
113
+ Tests should pass as-is once you install dependencies.
167
114
 
168
- ```ruby
169
- response = request.item_search(
170
- query: {
171
- 'ItemId' => '0679753354'
172
- },
173
- persistent: true
174
- )
115
+ ```sh
116
+ bundle exec rake
175
117
  ```
176
118
 
177
- ### Response
119
+ By default, all requests are stubbed. Use the `RECORD` env var to record new or modified interactions.
178
120
 
179
- The quick and dirty way to consume a response is to parse into a Ruby hash:
180
-
181
- ```ruby
182
- response.to_h
121
+ ```sh
122
+ bundle exec RECORD=true rake
183
123
  ```
184
124
 
185
- You can also `#dig` into returned Hash:
125
+ You can also run tests against live data.
186
126
 
187
- ```ruby
188
- response.dig('ItemSearchResponse', 'Items', 'Item')
127
+ ```shell
128
+ bundle exec LIVE=true rake
189
129
  ```
190
130
 
191
- In production, you may prefer to use a custom parser to do some XML heavy-lifting:
131
+ In either case, you will want to add actual API credentials to a [`locales.yml`](https://github.com/hakanensari/vacuum/blob/master/test/locales.yml.example) file in the `test` directory.
192
132
 
193
- ```ruby
194
- class MyParser
195
- # A parser has to respond to this.
196
- def self.parse(body)
197
- new(body)
198
- end
199
-
200
- def initialize(body)
201
- @body = body
202
- end
133
+ ## Getting Help
203
134
 
204
- # Implement parser here.
205
- end
135
+ * Ask specific questions about the API on the [Amazon forum](https://forums.aws.amazon.com/forum.jspa?forumID=9).
136
+ * Report bugs and discuss potential features in [GitHub issues](https://github.com/hakanensari/vacuum/issues).
206
137
 
207
- response.parser = MyParser
208
- response.parse
209
- ```
210
138
 
211
- If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+
4
5
  require 'vacuum/request'
5
6
  require 'vacuum/version'
6
7
 
7
- # Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
8
+ # Ruby wrapper to the Amazon Product Advertising API
8
9
  module Vacuum
9
10
  class << self
10
11
  extend Forwardable
11
12
 
12
- def_delegator Vacuum::Request, :new
13
+ def_delegator 'Vacuum::Request', :new
13
14
  end
14
15
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vacuum
4
+ # The target Amazon locale
5
+ # @api private
6
+ class Locale
7
+ class NotFound < ArgumentError; end
8
+
9
+ attr_reader :code, :domain, :region
10
+
11
+ def self.find(code)
12
+ code = code.to_sym.downcase
13
+ code = :gb if code == :uk
14
+
15
+ @all.find { |locale| locale.code == code } || raise(NotFound)
16
+ end
17
+
18
+ def initialize(code, domain, region)
19
+ @code = code
20
+ @domain = domain
21
+ @region = region
22
+ end
23
+
24
+ def endpoint
25
+ "webservices.#{domain}"
26
+ end
27
+
28
+ def marketplace
29
+ "www.#{domain}"
30
+ end
31
+
32
+ def build_url(operation)
33
+ "https://#{endpoint}/paapi5/#{operation.downcase}"
34
+ end
35
+
36
+ @all = [
37
+ [:au, 'amazon.com.au', 'us-west-2'],
38
+ [:br, 'amazon.com.br', 'us-east-1'],
39
+ [:ca, 'amazon.ca', 'us-east-1'],
40
+ [:fr, 'amazon.fr', 'eu-west-1'],
41
+ [:de, 'amazon.de', 'eu-west-1'],
42
+ [:in, 'amazon.in', 'eu-west-1'],
43
+ [:it, 'amazon.it', 'eu-west-1'],
44
+ [:jp, 'amazon.co.jp', 'us-west-2'],
45
+ [:mx, 'amazon.com.mx', 'us-east-1'],
46
+ [:es, 'amazon.es', 'eu-west-1'],
47
+ [:tr, 'amazon.com.tr', 'eu-west-1'],
48
+ [:ae, 'amazon.ae', 'eu-west-1'],
49
+ [:gb, 'amazon.co.uk', 'eu-west-1'],
50
+ [:us, 'amazon.com', 'us-east-1']
51
+ ].map { |attributes| new(*attributes) }
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Vacuum
6
+ # Custom VCR matcher for stubbing calls to the Product Advertising API
7
+ # @api private
8
+ class Matcher
9
+ IGNORED_KEYS = %w[PartnerTag].freeze
10
+
11
+ attr_reader :requests
12
+
13
+ def self.call(*requests)
14
+ new(*requests).compare
15
+ end
16
+
17
+ def initialize(*requests)
18
+ @requests = requests
19
+ end
20
+
21
+ def compare
22
+ uris.reduce(:==) && bodies.reduce(:==)
23
+ end
24
+
25
+ private
26
+
27
+ def uris
28
+ requests.map(&:uri)
29
+ end
30
+
31
+ def bodies
32
+ requests.map do |req|
33
+ params = JSON.parse(req.body)
34
+ IGNORED_KEYS.each { |k| params.delete(k) }
35
+
36
+ params
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,122 +1,187 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jeff'
3
+ require 'aws-sigv4'
4
+ require 'httpi'
5
+ require 'json'
6
+
7
+ require 'vacuum/locale'
4
8
  require 'vacuum/response'
5
9
 
6
10
  module Vacuum
7
- # An Amazon Product Advertising API request.
11
+ # A request to the Amazon Product Advertising API
8
12
  class Request
9
- include Jeff
10
-
11
- BadLocale = Class.new(ArgumentError)
12
-
13
- LATEST_VERSION = '2013-08-01'
14
-
15
- HOSTS = {
16
- 'AU' => 'webservices.amazon.com.au',
17
- 'BR' => 'webservices.amazon.com.br',
18
- 'CA' => 'webservices.amazon.ca',
19
- 'CN' => 'webservices.amazon.cn',
20
- 'DE' => 'webservices.amazon.de',
21
- 'ES' => 'webservices.amazon.es',
22
- 'FR' => 'webservices.amazon.fr',
23
- 'GB' => 'webservices.amazon.co.uk',
24
- 'IN' => 'webservices.amazon.in',
25
- 'IT' => 'webservices.amazon.it',
26
- 'JP' => 'webservices.amazon.co.jp',
27
- 'MX' => 'webservices.amazon.com.mx',
28
- 'TR' => 'webservices.amazon.com.tr',
29
- 'US' => 'webservices.amazon.com'
30
- }.freeze
31
-
32
- OPERATIONS = %w[
33
- BrowseNodeLookup
34
- CartAdd
35
- CartClear
36
- CartCreate
37
- CartGet
38
- CartModify
39
- ItemLookup
40
- ItemSearch
41
- SimilarityLookup
42
- ].freeze
43
- private_constant :OPERATIONS
44
-
45
- params 'AssociateTag' => -> { associate_tag },
46
- 'Service' => 'AWSECommerceService',
47
- 'SubscriptionId' => -> { subscription_id },
48
- 'Version' => -> { version }
49
-
50
- attr_accessor :associate_tag, :subscription_id
51
- attr_writer :version
52
-
53
- # Create a new request for given locale.
54
- #
55
- # locale - The String Product Advertising API locale (default: US).
56
- # secure - Whether to use the secure version of the endpoint (default:
57
- # false)
58
- #
59
- # Raises a Bad Locale error if locale is not valid.
60
- def initialize(locale = 'US', secure = false)
61
- locale = 'GB' if locale == 'UK'
62
- host = HOSTS.fetch(locale) { raise BadLocale }
63
- @aws_endpoint = "#{secure ? 'https' : 'http'}://#{host}/onca/xml"
13
+ SERVICE = 'ProductAdvertisingAPI'
14
+ private_constant :SERVICE
15
+
16
+ # @api private
17
+ attr_reader :access_key, :secret_key, :locale, :partner_tag, :partner_type
18
+
19
+ # Creates a new request
20
+ # @param [Symbol,String] marketplace the two-letter country code of the
21
+ # target Amazon locale
22
+ # @param [String] access_key your access key
23
+ # @param [String] secret_key your secret key
24
+ # @param [String] partner_tag your partner tag
25
+ # @param [String] partner_type your partner type
26
+ def initialize(marketplace: :us, access_key:, secret_key:, partner_tag:,
27
+ partner_type: 'Associates')
28
+ @locale = Locale.find(marketplace)
29
+ @access_key = access_key
30
+ @secret_key = secret_key
31
+ @partner_tag = partner_tag
32
+ @partner_type = partner_type
33
+ end
34
+
35
+ # Returns details about specified browse nodes
36
+ # @see https://webservices.amazon.com/paapi5/documentation/getbrowsenodes.html
37
+ # @overload get_browse_nodes(browse_node_ids:, languages_of_preference: nil, marketplace: nil, partner_tag: nil, partner_type: nil, resources: nil)
38
+ # @param [Array<String,Integer>,String,Integer] browse_node_ids
39
+ # @param [Array<String>,nil] languages_of_preference
40
+ # @param [String,nil] marketplace
41
+ # @param [String,nil] partner_tag
42
+ # @param [String,nil] partner_type
43
+ # @param [Array<String>,nil] resources
44
+ # @return [Response]
45
+ def get_browse_nodes(browse_node_ids:, **params)
46
+ params.update(browse_node_ids: Array(browse_node_ids))
47
+ request('GetBrowseNodes', params)
48
+ end
49
+
50
+ # Returns the attributes of one or more items
51
+ # @see https://webservices.amazon.com/paapi5/documentation/get-items.html
52
+ # @overload get_items(condition: nil, currency_of_preference: nil, item_id_type: nil, item_ids:, languages_of_preference: nil, marketplace: nil, merchant: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil)
53
+ # @param [String,nil] condition
54
+ # @param [String,nil] currency_of_preference
55
+ # @param [String,nil] item_id_type
56
+ # @param [Array<String>,String] item_ids
57
+ # @param [Array<String>,nil] languages_of_preference
58
+ # @param [String,nil] marketplace
59
+ # @param [String,nil] merchant
60
+ # @param [Integer,nil] offer_count
61
+ # @param [String,nil] partner_tag
62
+ # @param [String,nil] partner_type
63
+ # @param [Array<String>,nil] resources
64
+ # @return [Response]
65
+ def get_items(item_ids:, **params)
66
+ params.update(item_ids: Array(item_ids))
67
+ request('GetItems', params)
68
+ end
69
+
70
+ # Returns a set of items that are the same product, but differ according to
71
+ # a consistent theme
72
+ # @see https://webservices.amazon.com/paapi5/documentation/get-variations.html
73
+ # @overload get_variations(asin:, condition: nil, currency_of_preference: nil, languages_of_preference: nil, marketplace: nil, merchant: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil, variation_count: nil, variation_page: nil)
74
+ # @param [String] asin
75
+ # @param [String,nil] condition
76
+ # @param [String,nil] currency_of_preference
77
+ # @param [Array<String>,nil] languages_of_preference
78
+ # @param [String,nil] marketplace
79
+ # @param [String,nil] merchant
80
+ # @param [Integer,nil] offer_count
81
+ # @param [String,nil] partner_tag
82
+ # @param [String,nil] partner_type
83
+ # @param [Array<String>,nil] resources
84
+ # @param [Integer,nil] variation_count
85
+ # @param [Integer,nil] variation_page
86
+ # @return [Response]
87
+ def get_variations(**params)
88
+ request('GetVariations', params)
89
+ end
90
+
91
+ # Searches for items on Amazon based on a search query
92
+ # @see https://webservices.amazon.com/paapi5/documentation/search-items.html
93
+ # @overload search_items(actor: nil, artist: nil, author: nil, availability: nil, brand: nil, browse_node_id: nil, condition: nil, currency_of_preference: nil, delivery_flags: nil, item_count: nil, item_page: nil, keywords: nil, languages_of_preference: nil, marketplace: nil, max_price: nil, merchant: nil, min_price: nil, min_reviews_rating: nil, min_savings_percent: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil, search_index: nil, sort_by: nil, title: nil)
94
+ # @param [String,nil] actor
95
+ # @param [String,nil] artist
96
+ # @param [String,nil] availability
97
+ # @param [String,nil] brand
98
+ # @param [Integer,nil] browse_node_id
99
+ # @param [String,nil] condition
100
+ # @param [String,nil] currency_of_preference
101
+ # @param [Array<String>,nil] delivery_flags
102
+ # @param [Integer,nil] item_count
103
+ # @param [Integer,nil] item_page
104
+ # @param [String,nil] keywords
105
+ # @param [Array<String>,nil] languages_of_preference
106
+ # @param [Integer,nil] max_price
107
+ # @param [String,nil] merchant
108
+ # @param [Integer,nil] min_price
109
+ # @param [Integer,nil] min_reviews_rating
110
+ # @param [Integer,nil] min_savings_percent
111
+ # @param [Integer,nil] offer_count
112
+ # @param [Hash,nil] properties
113
+ # @param [Array<String>,nil] resources
114
+ # @param [String,nil] search_index
115
+ # @param [String,nil] sort_by
116
+ # @param [String,nil] title
117
+ # @return [Response]
118
+ def search_items(**params)
119
+ request('SearchItems', params)
64
120
  end
65
121
 
66
- # Configure the Amazon Product Advertising API request.
67
- #
68
- # credentials - The Hash credentials of the API endpoint.
69
- # :aws_access_key_id - The String Amazon Web Services
70
- # (AWS) key.
71
- # :aws_secret_access_key - The String AWS secret.
72
- # :associate_tag - The String Associate Tag.
73
- # :aws_version - The String AWS version.
74
- #
75
- # Returns self.
76
- def configure(credentials)
77
- credentials.each { |key, val| send("#{key}=", val) }
78
- self
122
+ private
123
+
124
+ def request(operation, params)
125
+ body = build_body(params)
126
+ signature = sign(operation, body)
127
+
128
+ request = HTTPI::Request.new(
129
+ headers: request_headers(operation, signature),
130
+ url: locale.build_url(operation),
131
+ body: body
132
+ )
133
+
134
+ Response.new(HTTPI.post(request))
79
135
  end
80
136
 
81
- # Returns the API version.
82
- def version
83
- @version || LATEST_VERSION
137
+ def sign(operation, body)
138
+ signer.sign_request(
139
+ http_method: 'POST',
140
+ url: locale.build_url(operation),
141
+ headers: headers(operation),
142
+ body: body
143
+ )
84
144
  end
85
145
 
86
- # Execute an API operation. See `OPERATIONS` constant above for available
87
- # operation names.
88
- #
89
- # params - The Hash request parameters.
90
- # opts - Options passed to Excon (default: {}).
91
- #
92
- # Alternatively, pass Excon options as first argument and include request
93
- # parameters as query key.
94
- #
95
- # Examples
96
- #
97
- # req.item_search(
98
- # 'SearchIndex' => 'All',
99
- # 'Keywords' => 'Architecture'
100
- # )
101
- #
102
- # req.item_search(
103
- # query: {
104
- # 'SearchIndex' => 'All',
105
- # 'Keywords' => 'Architecture'
106
- # },
107
- # persistent: true
108
- # )
109
- #
110
- # Returns a Vacuum Response.
111
- OPERATIONS.each do |operation|
112
- method_name = operation.gsub(/(.)([A-Z])/, '\1_\2').downcase
113
- define_method(method_name) do |params, opts = {}|
114
- params.key?(:query) ? opts = params : opts.update(query: params)
115
- opts[:expects] ||= [200, 400, 403]
116
- opts[:query].update('Operation' => operation)
117
-
118
- Response.new(get(opts))
146
+ def request_headers(operation, signature)
147
+ headers(operation).merge(
148
+ 'Content-Type' => 'application/json; charset=utf-8',
149
+ 'Authorization' => signature.headers['authorization'],
150
+ 'X-Amz-Content-Sha256' => signature.headers['x-amz-content-sha256'],
151
+ 'X-Amz-Date' => signature.headers['x-amz-date'],
152
+ 'Host' => locale.endpoint
153
+ )
154
+ end
155
+
156
+ def headers(operation)
157
+ {
158
+ 'X-Amz-Target' => "com.amazon.paapi5.v1.#{SERVICE}v1.#{operation}",
159
+ 'Content-Encoding' => 'amz-1.0'
160
+ }
161
+ end
162
+
163
+ def signer
164
+ Aws::Sigv4::Signer.new(
165
+ service: SERVICE,
166
+ region: locale.region,
167
+ access_key_id: access_key,
168
+ secret_access_key: secret_key,
169
+ http_method: 'POST',
170
+ endpoint: locale.endpoint
171
+ )
172
+ end
173
+
174
+ def build_body(params)
175
+ hsh = { 'PartnerTag' => partner_tag,
176
+ 'PartnerType' => partner_type }
177
+
178
+ params.each do |key, val|
179
+ key = key.to_s.split('_')
180
+ .map { |word| word == 'asin' ? 'ASIN' : word.capitalize }.join
181
+ hsh[key] = val
119
182
  end
183
+
184
+ JSON.generate(hsh)
120
185
  end
121
186
  end
122
187
  end