amazon-ecs 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGELOG +3 -0
  2. data/README +101 -0
  3. data/lib/amazon/ecs.rb +264 -0
  4. data/test/amazon/ecs_test.rb +91 -0
  5. metadata +57 -0
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ 0.5.0 2006-09-12
2
+ ----------------
3
+ Initial Release
data/README ADDED
@@ -0,0 +1,101 @@
1
+ == amazon-ecs
2
+
3
+ Generic Amazon E-commerce REST API using Hpricot with configurable
4
+ default options and method call options. Uses Response and
5
+ Element classes for easy access access to REST XML output. Supports ECS 4.0.
6
+
7
+ It is generic, so you can easily extend <tt>Amazon::Ecs</tt> to support
8
+ other not implemented REST operations; and it is also generic because it just wraps around
9
+ Hpricot element object, instead of providing one-to-one object/attributes to XML elements map.
10
+
11
+ If in the future, there is a change in REST XML output structure,
12
+ no changes will be required on <tt>amazon-ecs</tt> library,
13
+ instead you just need to change the element path.
14
+
15
+ Version: 0.5.0
16
+
17
+ Links:
18
+ * http://amazon-ecs.rubyforge.org
19
+ * http://www.pluitsolutions.com/amazon-ecs
20
+
21
+ == INSTALLATION
22
+
23
+ $ gem install amazon-ecs
24
+
25
+ == EXAMPLE
26
+
27
+ # set the default options; options will be camelized and converted to REST request parameters.
28
+ Amazon::Ecs.options = {:aWS_access_key_id => [your developer token]}
29
+
30
+ # options provided on method call will merge with the default options
31
+ res = Amazon::Ecs.item_search('ruby', {:response_group => 'Medium', :sort => 'salesrank'})
32
+
33
+ # some common response object methods
34
+ res.is_valid_request? # return true request is valid
35
+ res.has_error? # return true if there is an error
36
+ res.error # return error message if there is any
37
+ res.total_pages # return total pages
38
+ res.total_results # return total pages
39
+ res.item_page # return current page no if :item_page option is provided
40
+
41
+ # traverse through each item (Amazon::Element)
42
+ res.items.each do |item|
43
+ # retrieve element text value, following the XML output structure
44
+ item.get('asin')
45
+ item.get('itemattributes/title')
46
+
47
+ # or you can also do it this way, to retrieve the title
48
+ atts = item.get('itemattributes')
49
+ atts.get('title')
50
+
51
+ # return first author or a string array of authors
52
+ atts.get('author') # 'Author 1'
53
+ atts.get_array('author') # ['Author 1', 'Author 2', ...]
54
+
55
+ # return an hash of children text values with the element names as the keys
56
+ item.get_hash('smallimage') # {:url => ..., :width => ..., :height => ...}
57
+
58
+ # note that '/' returns Hpricot::Elements array object, nil if not found
59
+ reviews = item/'editorialreview'
60
+
61
+ # traverse through Hpricot elements
62
+ reviews.each do |review|
63
+ # Getting hash value out of Hpricot element
64
+ Amazon::Element.get_hash(review) # [:source => ..., :content ==> ...]
65
+
66
+ # Or can retrieve them seperately
67
+ Amazon::Element.get(review, 'source')
68
+ Amazon::Element.get(review, 'content')
69
+ end
70
+ end
71
+
72
+ Refer to Amazon ECS documentation for more information on Amazon REST request parameters and XML output:
73
+ http://docs.amazonwebservices.com/AWSEcommerceService/2005-10-05/index.html
74
+
75
+ To get a sample of Amazon REST response XML output, use AWSZone.com scratch pad:
76
+ http://www.awszone.com/scratchpads/aws/ecs.us/index.aws
77
+
78
+ == LICENSE
79
+
80
+ (The MIT License)
81
+
82
+ Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining
85
+ a copy of this software and associated documentation files (the
86
+ "Software"), to deal in the Software without restriction, including
87
+ without limitation the rights to use, copy, modify, merge, publish,
88
+ distribute, sublicense, and/or sell copies of the Software, and to
89
+ permit persons to whom the Software is furnished to do so, subject to
90
+ the following conditions:
91
+
92
+ The above copyright notice and this permission notice shall be
93
+ included in all copies or substantial portions of the Software.
94
+
95
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
96
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
97
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
98
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
99
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
100
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
101
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/amazon/ecs.rb ADDED
@@ -0,0 +1,264 @@
1
+ require 'net/http'
2
+ require 'hpricot'
3
+ require 'cgi'
4
+
5
+ module Amazon
6
+ class RequestError < StandardError; end
7
+
8
+ # :include: README
9
+ class Ecs
10
+ SERVICE_URLS = {:us => 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService',
11
+ :uk => 'http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService',
12
+ :ca => 'http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService',
13
+ :de => 'http://webservices.amazon.de/onca/xml?Service=AWSECommerceService',
14
+ :jp => 'http://webservices.amazon.de/onca/xml?Service=AWSECommerceService',
15
+ :fr => 'http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService'
16
+ }
17
+
18
+ @@debug, @@options = nil
19
+
20
+ # Default search options
21
+ def self.options
22
+ @@options
23
+ end
24
+
25
+ # Set default search options
26
+ def self.options=(opts)
27
+ @@options = opts
28
+ end
29
+
30
+ # Get debug flag.
31
+ def self.debug
32
+ @@debug
33
+ end
34
+
35
+ # Set debug flag to true or false.
36
+ def self.debug=(dbg)
37
+ @@debug = dbg
38
+ end
39
+
40
+ # Search amazon items with search terms. Default search index option is 'Books'.
41
+ # For other search type other than keywords, please specify :type => [search type param name].
42
+ def self.item_search(terms, opts = {})
43
+ opts = self.options.merge(opts) if self.options
44
+ opts[:operation] = 'ItemSearch'
45
+ opts[:search_index] = opts[:search_index] || 'Books'
46
+
47
+ type = opts.delete(:type)
48
+ if type
49
+ opts[type.to_sym] = terms
50
+ else
51
+ opts[:keywords] = terms
52
+ end
53
+
54
+ self.send_request(opts)
55
+ end
56
+
57
+ # Search an item by ASIN no.
58
+ def self.item_lookup(item_id, opts = {})
59
+ opts = self.options.merge(opts) if self.options
60
+ opts[:operation] = 'ItemLookup'
61
+ opts[:item_id] = item_id
62
+
63
+ # not allowed in item_lookup
64
+ opts.delete(:search_index)
65
+
66
+ self.send_request(opts)
67
+ end
68
+
69
+ # Generic send request to ECS REST service. You have to specify the :operation parameter.
70
+ def self.send_request(opts)
71
+ request_url = prepare_url(opts)
72
+ log "Request URL: #{request_url}"
73
+
74
+ res = Net::HTTP.get_response(URI::parse(request_url))
75
+ unless res.kind_of? Net::HTTPSuccess
76
+ raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
77
+ end
78
+ Response.new(res.body)
79
+ end
80
+
81
+ # Response object returned after a REST call to Amazon service.
82
+ class Response
83
+ # XML input is in string format
84
+ def initialize(xml)
85
+ @doc = Hpricot(xml)
86
+ end
87
+
88
+ # Return Hpricot object.
89
+ def doc
90
+ @doc
91
+ end
92
+
93
+ # Return true if request is valid.
94
+ def is_valid_request?
95
+ (@doc/"isvalid").inner_html == "True"
96
+ end
97
+
98
+ # Return true if response has an error.
99
+ def has_error?
100
+ !(error.nil? || error.empty?)
101
+ end
102
+
103
+ # Return error message.
104
+ def error
105
+ Element.get(@doc, "error/message")
106
+ end
107
+
108
+ # Return an array of Amazon::Element item objects.
109
+ def items
110
+ unless @items
111
+ @items = (@doc/"item").collect {|item| Element.new(item)}
112
+ end
113
+ @items
114
+ end
115
+
116
+ # Return the first item (Amazon::Element)
117
+ def first_item
118
+ items.first
119
+ end
120
+
121
+ # Return current page no if :item_page option is when initiating the request.
122
+ def item_page
123
+ unless @item_page
124
+ @item_page = (@doc/"itemsearchrequest/itempage").inner_html.to_i
125
+ end
126
+ @item_page
127
+ end
128
+
129
+ # Return total results.
130
+ def total_results
131
+ unless @total_results
132
+ @total_results = (@doc/"totalresults").inner_html.to_i
133
+ end
134
+ @total_results
135
+ end
136
+
137
+ # Return total pages.
138
+ def total_pages
139
+ unless @total_pages
140
+ @total_pages = (@doc/"totalpages").inner_html.to_i
141
+ end
142
+ @total_pages
143
+ end
144
+ end
145
+
146
+ protected
147
+ def self.log(s)
148
+ return unless self.debug
149
+ if RAILS_DEFAULT_LOGGER
150
+ RAILS_DEFAULT_LOGGER.error(s)
151
+ else
152
+ puts s
153
+ end
154
+ end
155
+
156
+ private
157
+ def self.prepare_url(opts)
158
+ country = opts.delete(:country)
159
+ country = (country.nil?) ? 'us' : country
160
+ request_url = SERVICE_URLS[country.to_sym]
161
+ raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
162
+
163
+ qs = ''
164
+ opts.each {|k,v|
165
+ next unless v
166
+ v = v.join(',') if v.is_a? Array
167
+ qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
168
+ }
169
+ "#{request_url}#{qs}"
170
+ end
171
+
172
+ def self.camelize(s)
173
+ s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
174
+ end
175
+ end
176
+
177
+ # Internal wrapper class to provide convenient method to access Hpricot element value.
178
+ class Element
179
+ # Pass Hpricot::Elements object
180
+ def initialize(element)
181
+ @element = element
182
+ end
183
+
184
+ # Returns Hpricot::Elments object
185
+ def elem
186
+ @element
187
+ end
188
+
189
+ # Find Hpricot::Elements matching the given path. Example: element/"author".
190
+ def /(path)
191
+ elements = @element/path
192
+ return nil if elements.size == 0
193
+ elements
194
+ end
195
+
196
+ # Get the text value of the given path, leave empty to retrieve current element value.
197
+ def get(path='')
198
+ Element.get(@element, path)
199
+ end
200
+
201
+ # Get the unescaped HTML text of the given path.
202
+ def get_unescaped(path='')
203
+ Element.get_unescaped(@element, path)
204
+ end
205
+
206
+ # Get the array values of the given path.
207
+ def get_array(path='')
208
+ Element.get_array(@element, path)
209
+ end
210
+
211
+ # Get the children element text values in hash format with the element names as the hash keys.
212
+ def get_hash(path='')
213
+ Element.get_hash(@element, path)
214
+ end
215
+
216
+ # Similar to #get, except an element object must be passed-in.
217
+ def self.get(element, path='')
218
+ return unless element
219
+ result = element.at(path)
220
+ result = result.inner_html if result
221
+ result
222
+ end
223
+
224
+ # Similar to #get_unescaped, except an element object must be passed-in.
225
+ def self.get_unescaped(element, path='')
226
+ CGI::unescapeHTML(get(element, path))
227
+ end
228
+
229
+ # Similar to #get_array, except an element object must be passed-in.
230
+ def self.get_array(element, path='')
231
+ return unless element
232
+
233
+ result = element/path
234
+ if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
235
+ parsed_result = []
236
+ result.each {|item|
237
+ parsed_result << Element.get(item)
238
+ }
239
+ parsed_result
240
+ else
241
+ [Element.get(result)]
242
+ end
243
+ end
244
+
245
+ # Similar to #get_hash, except an element object must be passed-in.
246
+ def self.get_hash(element, path='')
247
+ return unless element
248
+
249
+ result = element.at(path)
250
+ if result
251
+ hash = {}
252
+ result = result.children
253
+ result.each do |item|
254
+ hash[item.name.to_sym] = item.inner_html
255
+ end
256
+ hash
257
+ end
258
+ end
259
+
260
+ def to_s
261
+ elem.to_s if elem
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class Amazon::EcsTest < Test::Unit::TestCase
4
+
5
+ Amazon::Ecs.options = {:response_group => 'Large', :aWS_access_key_id => '0XQXXC6YV2C85DX1BF02'}
6
+
7
+ def test_item_search
8
+ resp = Amazon::Ecs.item_search('ruby')
9
+ assert(resp.is_valid_request?)
10
+ assert(resp.total_results >= 3600)
11
+ assert(resp.total_pages >= 360)
12
+ end
13
+
14
+ def test_item_search_with_paging
15
+ resp = Amazon::Ecs.item_search('ruby', :item_page => 2)
16
+ assert resp.is_valid_request?
17
+ assert 2, resp.item_page
18
+ end
19
+
20
+ def test_item_search_with_invalid_request
21
+ resp = Amazon::Ecs.item_search(nil)
22
+ assert !resp.is_valid_request?
23
+ end
24
+
25
+ def test_item_search_with_no_result
26
+ resp = Amazon::Ecs.item_search('afdsafds')
27
+
28
+ assert resp.is_valid_request?
29
+ assert_equal "We did not find any matches for your request.",
30
+ resp.error
31
+ end
32
+
33
+ def test_item_search_uk
34
+ resp = Amazon::Ecs.item_search('ruby', :country => :uk)
35
+ assert resp.is_valid_request?
36
+ end
37
+
38
+ def test_item_search_by_author
39
+ resp = Amazon::Ecs.item_search('dave', :type => :author)
40
+ assert resp.is_valid_request?
41
+ end
42
+
43
+ def test_item_get
44
+ resp = Amazon::Ecs.item_search("0974514055")
45
+ item = resp.first_item
46
+
47
+ # test get
48
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
49
+ item.get("itemattributes/title")
50
+
51
+ # test get_array
52
+ assert_equal ['Dave Thomas', 'Chad Fowler', 'Andy Hunt'],
53
+ item.get_array("author")
54
+
55
+ # test get_hash
56
+ small_image = item.get_hash("smallimage")
57
+
58
+ assert_equal 3, small_image.keys.size
59
+ assert_match "images/P/0974514055.01._SCTHUMBZZZ_V1128790749_.jpg",
60
+ small_image[:url]
61
+ assert_equal "75", small_image[:height]
62
+ assert_equal "59", small_image[:width]
63
+
64
+ # test /
65
+ reviews = item/"editorialreview"
66
+ reviews.each do |review|
67
+ # returns unescaped HTML content, Hpricot escapes all text values
68
+ assert Amazon::Element.get_unescaped(review, 'source')
69
+ assert Amazon::Element.get_unescaped(review, 'content')
70
+ end
71
+ end
72
+
73
+ def test_item_lookup
74
+ resp = Amazon::Ecs.item_lookup('0974514055')
75
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
76
+ resp.first_item.get("itemattributes/title")
77
+ end
78
+
79
+ def test_item_lookup_with_invalid_request
80
+ resp = Amazon::Ecs.item_lookup(nil)
81
+ assert resp.has_error?
82
+ assert resp.error
83
+ end
84
+
85
+ def test_item_lookup_with_no_result
86
+ resp = Amazon::Ecs.item_lookup('abc')
87
+
88
+ assert resp.is_valid_request?
89
+ assert_match(/ABC is not a valid value for ItemId/, resp.error)
90
+ end
91
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: amazon-ecs
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.5.0
7
+ date: 2006-12-07 00:00:00 +08:00
8
+ summary: Generic Amazon E-commerce Service (ECS) REST API. Supports ECS 4.0.
9
+ require_paths:
10
+ - lib
11
+ email: herryanto@pluitsolutions.com
12
+ homepage: http://amazon-ecs.rubyforge.net/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: name
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Herryanto Siatono
30
+ files:
31
+ - lib/amazon
32
+ - lib/amazon/ecs.rb
33
+ - README
34
+ - CHANGELOG
35
+ test_files:
36
+ - test/amazon/ecs_test.rb
37
+ rdoc_options: []
38
+
39
+ extra_rdoc_files:
40
+ - README
41
+ - CHANGELOG
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ requirements: []
47
+
48
+ dependencies:
49
+ - !ruby/object:Gem::Dependency
50
+ name: hpricot
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Version::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0.4"
57
+ version: