hollownest-ruby-aws 0.0.0

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.
data/README.rdoc ADDED
@@ -0,0 +1,138 @@
1
+ #--
2
+ # $Id: README.rdoc,v 1.21 2009/02/20 00:45:17 ianmacd Exp $
3
+ #++
4
+ #
5
+ #
6
+ # = Ruby/AWS - A Ruby interface to the Amazon Associates Web Services API.
7
+ #
8
+ # == Introduction
9
+ #
10
+ # Ruby/AWS is a Ruby language library that allows programmatic access to
11
+ # the popular Amazon Web sites via the AWS v4 API. It is the successor to the
12
+ # now obsolete Ruby/Amazon.
13
+ #
14
+ # In addition to the original
15
+ # amazon.com[http://www.amazon.com/exec/obidos/redirect-home/calibanorg-20]
16
+ # site, the local sites
17
+ # amazon.co.uk[http://www.amazon.co.uk/exec/obidos/redirect-home/caliban-21],
18
+ # amazon.de[http://www.amazon.de/exec/obidos/redirect-home/calibanorg0a-21],
19
+ # amazon.fr[http://www.amazon.fr/exec/obidos/redirect-home/caliban08-21],
20
+ # amazon.ca[http://www.amazon.ca/exec/obidos/redirect-home/caliban-20] and
21
+ # amazon.co.jp[http://www.amazon.co.jp/exec/obidos/redirect-home/calibanorg-20]
22
+ # are also supported.
23
+ #
24
+ # Although the library is still in development, the AWS v4 API is now more or
25
+ # less fully supported, with only tiny gaps in the functionality of some
26
+ # operations.
27
+ #
28
+ # The following operations are supported:
29
+ #
30
+ # BrowseNodeLookup
31
+ # CustomerContentLookup
32
+ # CustomerContentSearch
33
+ # Help
34
+ # ItemLookup
35
+ # ItemSearch
36
+ # ListLookup
37
+ # ListSearch
38
+ # SellerListingLookup
39
+ # SellerListingSearch
40
+ # SellerLookup
41
+ # SimilarityLookup
42
+ # TagLookup
43
+ # TransactionLookup
44
+ # VehiclePartLookup
45
+ # VehiclePartSearch
46
+ # VehicleSearch
47
+ #
48
+ # Remote shopping-carts are also supported. This adds the following operations:
49
+ #
50
+ # CartCreate
51
+ # CartAdd
52
+ # CartModify
53
+ # CartClear
54
+ # CartGet
55
+ #
56
+ # In addition, multiple operations and batch requests are also supported.
57
+ #
58
+ # Ruby/AWS also offers advanced features not directly available in the AWS
59
+ # API, such as the ability to retrieve *all* results pages for a particular
60
+ # search, rather than having to manually deal with AWS responses of 10 results
61
+ # per page.
62
+ #
63
+ # You can also retrieve product images and optionally overlay them with
64
+ # percentage discount icons.
65
+ #
66
+ # Another advanced feature is the ability to cache responses returned by AWS.
67
+ # If the cache is used (as it is by default), the results of each unique
68
+ # query will be cached and used for 24 hours. The cache can be manually
69
+ # flushed of all or just the expired entries.
70
+ #
71
+ # One other useful advanced feature is the ability to determine the
72
+ # appropriate Amazon locale for a given client, based on its IP address or
73
+ # host name. This allows you to perform AWS operations using the correct
74
+ # geographical Amazon site for any given client. German and Austrian clients
75
+ # can be made to interact with amazon.de, British and Irish clients with
76
+ # amazon.co.uk, etc.
77
+ #
78
+ #
79
+ # == Installation
80
+ #
81
+ # Please see the +INSTALL+ file supplied with the software for details of how
82
+ # to install Ruby/AWS. You can choose between an installation script and a
83
+ # RubyGems[http://www.rubygems.org/] installation.
84
+ #
85
+ # Note, however, if choosing the gem installation, that whilst Ruby/AWS's
86
+ # RubyForge UNIX name is now ruby-aaws. The ruby-aws name was taken by
87
+ # {another project}[http://rubyforge.org/projects/ruby-aws/] and this clash
88
+ # prevented remote installation of the Ruby/AWS gem.
89
+ #
90
+ #
91
+ # == Prerequisites
92
+ #
93
+ # Before you can use this library, you need to obtain an Amazon Web Services
94
+ # {access key
95
+ # ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html].
96
+ #
97
+ # You should also apply for an {Associates
98
+ # account}[http://docs.amazonwebservices.com/AWSECommerceService/2009-01-06/GSG/BecominganAssociate.html],
99
+ # although this isn't strictly necessary. If you do not explicitly provide an
100
+ # Associates tag in your calls through Ruby/AWS, the tag of the Ruby/AWS
101
+ # author will be used by default.
102
+ #
103
+ #
104
+ # == See Also
105
+ #
106
+ # Ultimately, the way to get the most from this library is to read the AWS
107
+ # documentation to get a feel for what is possible, and then experiment with
108
+ # this library to see how the AWS calls are mapped into the Ruby world. You
109
+ # should also review this library's
110
+ # RDoc[http://www.ruby-doc.org/core/classes/RDoc.html]
111
+ # documentation[http://www.caliban.org/ruby/ruby-aws/] as well as the
112
+ # plain-text +README+ file that came with the archive.
113
+ #
114
+ # Additionally, there's a
115
+ # {mailing-list}[http://www.caliban.org/mailman/listinfo/ruby-aws] available,
116
+ # where you can discuss all Ruby/AWS-related subjects and issues.
117
+ #
118
+ # Please see the Amazon Web Services
119
+ # documentation[http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=5]
120
+ # for definitive information on the capabilities and inner workings of the AWS
121
+ # API.
122
+ #
123
+ #
124
+ # == Download
125
+ #
126
+ # Version 0.5.0
127
+ # === {gzip'ed tar archive}[http://www.caliban.org/files/ruby/ruby-aws-0.5.0.tar.gz]
128
+ # === {Ruby Gem}[http://www.caliban.org/files/ruby/ruby-aaws-0.5.0.gem]
129
+ # === {Fedora 9 RPM}[http://www.caliban.org/files/redhat/RPMS/noarch/ruby-aws-0.5.0-1.fc9.noarch.rpm]
130
+ # === {Fedora 9 doc RPM}[http://www.caliban.org/files/redhat/RPMS/noarch/ruby-aws-doc-0.5.0-1.fc9.noarch.rpm]
131
+ # === {Fedora 9 source RPM}[http://www.caliban.org/files/redhat/SRPMS/ruby-aws-0.5.0-1.fc9.src.rpm]
132
+ #
133
+ #
134
+ # ---
135
+ # Author:: Ian Macdonald <mailto:ian@caliban.org>
136
+ # Version:: 0.5.0
137
+ # Copyright:: (C) 2008-2009 Ian Macdonald
138
+ # Licence:: GPL[http://www.gnu.org/copyleft/gpl.html]
data/lib/amazon.rb ADDED
@@ -0,0 +1,153 @@
1
+ # $Id: amazon.rb,v 1.26 2009/01/19 16:45:11 ianmacd Exp $
2
+ #
3
+
4
+ module Amazon
5
+
6
+ # A top-level exception container class.
7
+ #
8
+ class AmazonError < StandardError; end
9
+
10
+ NAME = 'Ruby/Amazon'
11
+ @@config = {}
12
+
13
+ # Prints debugging messages and works like printf, except that it prints
14
+ # only when Ruby is run with the -d switch.
15
+ #
16
+ def Amazon.dprintf(format='', *args)
17
+ $stderr.printf( format + "\n", *args ) if $DEBUG
18
+ end
19
+
20
+
21
+ # Encode a string, such that it is suitable for HTTP transmission.
22
+ #
23
+ def Amazon.url_encode(string)
24
+
25
+ # Shamelessly plagiarised from Wakou Aoyama's cgi.rb.
26
+ #
27
+ string.gsub( /([^ a-zA-Z0-9_.-]+)/n ) do
28
+ '%' + $1.unpack( 'H2' * $1.size ).join( '%' ).upcase
29
+ end.tr( ' ', '+' )
30
+ end
31
+
32
+
33
+ # Convert a string from CamelCase to ruby_case.
34
+ #
35
+ def Amazon.uncamelise(str)
36
+ # Avoid modifying by reference.
37
+ #
38
+ str = str.dup
39
+
40
+ # Don't mess with string if all caps.
41
+ #
42
+ str.gsub!( /(.+?)(([A-Z][a-z]|[A-Z]+$))/, "\\1_\\2" ) if str =~ /[a-z]/
43
+
44
+ # Convert to lower case.
45
+ #
46
+ str.downcase
47
+ end
48
+
49
+
50
+ # A Class for dealing with configuration files, such as
51
+ # <tt>/etc/amazonrc</tt> and <tt>~/.amazonrc</tt>.
52
+ #
53
+ class Config < Hash
54
+
55
+ require 'stringio'
56
+
57
+ # Exception class for configuration file errors.
58
+ #
59
+ class ConfigError < AmazonError; end
60
+
61
+ # A configuration may be passed in as a string. Otherwise, the files
62
+ # <tt>/etc/amazonrc</tt> and <tt>~/.amazonrc</tt> are read if they exist
63
+ # and are readable.
64
+ #
65
+ def initialize(config_str=nil)
66
+ locale = nil
67
+
68
+ if config_str
69
+
70
+ # We have been passed a config file as a string.
71
+ #
72
+ config_files = [ config_str ]
73
+ config_class = StringIO
74
+
75
+ else
76
+
77
+ # Perform the usual search for the system and user config files.
78
+ #
79
+ config_files = [ File.join( '', 'etc', 'amazonrc' ) ]
80
+
81
+ # Figure out where home is. The locations after HOME are for Windows.
82
+ # [ruby-core:12347]
83
+ #
84
+ home = ENV['AMAZONRCDIR'] ||
85
+ ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] ||
86
+ ENV['USERPROFILE']
87
+ user_rcfile = ENV['AMAZONRCFILE'] || '.amazonrc'
88
+
89
+ if home
90
+ config_files << File.expand_path( File.join( home, user_rcfile ) )
91
+ end
92
+
93
+ config_class = File
94
+ end
95
+
96
+ config_files.each do |cf|
97
+
98
+ if config_class == StringIO
99
+ readable = true
100
+ else
101
+ # We must determine whether the file is readable.
102
+ #
103
+ readable = File.exists?( cf ) && File.readable?( cf )
104
+ end
105
+
106
+ if readable
107
+
108
+ Amazon.dprintf( 'Opening %s ...', cf ) if config_class == File
109
+
110
+ config_class.open( cf ) { |f| lines = f.readlines }.each do |line|
111
+ line.chomp!
112
+
113
+ # Skip comments and blank lines.
114
+ #
115
+ next if line =~ /^(#|$)/
116
+
117
+ Amazon.dprintf( 'Read: %s', line )
118
+
119
+ # Determine whether we're entering the subsection of a new locale.
120
+ #
121
+ if match = line.match( /^\[(\w+)\]$/ )
122
+ locale = match[1]
123
+ Amazon.dprintf( "Config locale is now '%s'.", locale )
124
+ next
125
+ end
126
+
127
+ # Store these, because we'll probably find a use for these later.
128
+ #
129
+ begin
130
+ match = line.match( /^\s*(\S+)\s*=\s*(['"]?)([^'"]+)(['"]?)/ )
131
+ key, begin_quote, val, end_quote = match[1, 4]
132
+ raise ConfigError if begin_quote != end_quote
133
+
134
+ rescue NoMethodError, ConfigError
135
+ raise ConfigError, "bad config line: #{line}"
136
+ end
137
+
138
+ if locale && locale != 'global'
139
+ self[locale] ||= {}
140
+ self[locale][key] = val
141
+ else
142
+ self[key] = val
143
+ end
144
+
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+ end
152
+
153
+ end
data/lib/amazon/aws.rb ADDED
@@ -0,0 +1,1339 @@
1
+ # $Id: aws.rb,v 1.84 2009/02/19 16:01:11 ianmacd Exp $
2
+ #
3
+ #:include: ../../README.rdoc
4
+
5
+ module Amazon
6
+
7
+ module AWS
8
+
9
+ require 'uri'
10
+ require 'amazon'
11
+ require 'amazon/aws/cache'
12
+ require 'rexml/document'
13
+
14
+ NAME = '%s/%s' % [ Amazon::NAME, 'AWS' ]
15
+ VERSION = '0.4.4'
16
+ USER_AGENT = '%s %s' % [ NAME, VERSION ]
17
+
18
+ # Default Associate tags to use per locale.
19
+ #
20
+ DEF_ASSOC = {
21
+ 'ca' => 'caliban-20',
22
+ 'de' => 'calibanorg0a-21',
23
+ 'fr' => 'caliban08-21',
24
+ 'jp' => 'calibanorg-20',
25
+ 'uk' => 'caliban-21',
26
+ 'us' => 'calibanorg-20'
27
+ }
28
+
29
+ # Service name and API version for AWS. The version of the API used can be
30
+ # changed via the user configuration file.
31
+ #
32
+ SERVICE = { 'Service' => 'AWSECommerceService',
33
+ 'Version' => '2009-01-06'
34
+ }
35
+
36
+ # Maximum number of 301 and 302 HTTP responses to follow, should Amazon
37
+ # later decide to change the location of the service.
38
+ #
39
+ MAX_REDIRECTS = 3
40
+
41
+ # Maximum number of results pages that can be retrieved for a given
42
+ # search operation, using whichever pagination parameter is relevant to
43
+ # that type of operation.
44
+ #
45
+ PAGINATION = {
46
+ 'ItemSearch' => { 'parameter' => 'ItemPage',
47
+ 'max_page' => 400 },
48
+ 'ItemLookup' => { 'paraneter' => 'OfferPage',
49
+ 'max_page' => 100 },
50
+ 'ListLookup' => { 'parameter' => 'ProductPage',
51
+ 'max_page' => 30 },
52
+ 'ListSearch' => { 'parameter' => 'ListPage',
53
+ 'max_page' => 20 },
54
+ 'CustomerContentLookup' => { 'parameter' => 'ReviewPage',
55
+ 'max_page' => 10 },
56
+ 'CustomerContentSearch' => { 'parameter' => 'CustomerPage',
57
+ 'max_page' => 20 },
58
+ 'VehiclePartLookup' => { 'parameter' => 'FitmentPage',
59
+ 'max_page' => 10 }
60
+ }
61
+ # N.B. ItemLookup can also use the following two pagination parameters
62
+ #
63
+ # max. page
64
+ # ---------
65
+ # VariationPage 150
66
+ # ReviewPage 20
67
+
68
+
69
+ # Exception class for HTTP errors.
70
+ #
71
+ class HTTPError < AmazonError; end
72
+
73
+
74
+ # Exception class for faulty batch operations.
75
+ #
76
+ class BatchError < AmazonError; end
77
+
78
+
79
+ class Endpoint
80
+
81
+ attr_reader :host, :path
82
+
83
+ def initialize(endpoint)
84
+ uri = URI.parse( endpoint )
85
+ @host = uri.host
86
+ @path = uri.path
87
+ end
88
+ end
89
+
90
+ ENDPOINT = {
91
+ 'ca' => Endpoint.new( 'http://ecs.amazonaws.ca/onca/xml' ),
92
+ 'de' => Endpoint.new( 'http://ecs.amazonaws.de/onca/xml' ),
93
+ 'fr' => Endpoint.new( 'http://ecs.amazonaws.fr/onca/xml' ),
94
+ 'jp' => Endpoint.new( 'http://ecs.amazonaws.jp/onca/xml' ),
95
+ 'uk' => Endpoint.new( 'http://ecs.amazonaws.co.uk/onca/xml' ),
96
+ 'us' => Endpoint.new( 'http://ecs.amazonaws.com/onca/xml' )
97
+ }
98
+
99
+ # Fetch a page, either from the cache or by HTTP. This is used internally.
100
+ #
101
+ def AWS.get_page(request, query) # :nodoc:
102
+
103
+ url = ENDPOINT[request.locale].path + query
104
+ cache_url = ENDPOINT[request.locale].host + url
105
+
106
+ # Check for cached page and return that if it's there.
107
+ #
108
+ if request.cache && request.cache.cached?( cache_url )
109
+ body = request.cache.fetch( cache_url )
110
+ return body if body
111
+ end
112
+
113
+ # Get the existing connection. If there isn't one, force a new one.
114
+ #
115
+ conn = request.conn || request.reconnect.conn
116
+ user_agent = request.user_agent
117
+
118
+ Amazon.dprintf( 'Fetching http://%s%s ...', conn.address, url )
119
+
120
+ begin
121
+ response = conn.get( url, { 'user-agent' => user_agent } )
122
+
123
+ # If we've pulled and processed a lot of pages from the cache (or
124
+ # just not passed by here recently), the HTTP connection to the server
125
+ # will probably have timed out.
126
+ #
127
+ rescue Errno::ECONNRESET
128
+ conn = request.reconnect.conn
129
+ retry
130
+ end
131
+
132
+ redirects = 0
133
+ while response.key? 'location'
134
+ if ( redirects += 1 ) > MAX_REDIRECTS
135
+ raise HTTPError, "More than #{MAX_REDIRECTS} redirections"
136
+ end
137
+
138
+ old_url = url
139
+ url = URI.parse( response['location'] )
140
+ url.scheme = old_url.scheme unless url.scheme
141
+ url.host = old_url.host unless url.host
142
+ Amazon.dprintf( 'Following HTTP %s to %s ...', response.code, url )
143
+ response = Net::HTTP::start( url.host ).
144
+ get( url.path, { 'user-agent' => user_agent } )
145
+ end
146
+
147
+ if response.code != '200'
148
+ raise HTTPError, "HTTP response code #{response.code}"
149
+ end
150
+
151
+ # Cache the page if we're using a cache.
152
+ #
153
+ if request.cache
154
+ request.cache.store( cache_url, response.body )
155
+ end
156
+
157
+ response.body
158
+ end
159
+
160
+
161
+ def AWS.assemble_query(items) # :nodoc:
162
+ query = ''
163
+
164
+ # We must sort the items into an array to get reproducible ordering
165
+ # of the query parameters. Otherwise, URL caching would not work. We
166
+ # must also convert the keys to strings, in case Symbols have been used
167
+ # as the keys.
168
+ #
169
+ items.sort { |a,b| a.to_s <=> b.to_s }.each do |k, v|
170
+ query << '&%s=%s' % [ k, Amazon.url_encode( v.to_s ) ]
171
+ end
172
+
173
+ # Replace initial ampersand with question-mark.
174
+ #
175
+ query[0] = '?'
176
+
177
+ query
178
+ end
179
+
180
+
181
+ # Everything returned by AWS is an AWSObject.
182
+ #
183
+ class AWSObject
184
+
185
+ include REXML
186
+
187
+ # This method can be used to load AWSObject data previously serialised
188
+ # by Marshal.dump.
189
+ #
190
+ # Example:
191
+ #
192
+ # File.open( 'aws.dat' ) { |f| Amazon::AWS::AWSObject.load( f ) }
193
+ #
194
+ # Marshal.load cannot be used directly, because subclasses of AWSObject
195
+ # are dynamically defined as needed when AWS XML responses are parsed.
196
+ #
197
+ # Later attempts to load objects instantiated from these classes cause a
198
+ # problem for Marshal, because it knows nothing of classes that were
199
+ # dynamically defined by a separate process.
200
+ #
201
+ def AWSObject.load(io)
202
+ begin
203
+ Marshal.load( io )
204
+ rescue ArgumentError => ex
205
+ m = ex.to_s.match( /Amazon::AWS::AWSObject::([^ ]+)/ )
206
+ const_set( m[1], Class.new( AWSObject ) )
207
+
208
+ io.rewind
209
+ retry
210
+ end
211
+ end
212
+
213
+
214
+ # This method can be used to load AWSObject data previously serialised
215
+ # by YAML.dump.
216
+ #
217
+ # Example:
218
+ #
219
+ # File.open( 'aws.yaml' ) { |f| Amazon::AWS::AWSObject.yaml_load( f ) }
220
+ #
221
+ # The standard YAML.load cannot be used directly, because subclasses of
222
+ # AWSObject are dynamically defined as needed when AWS XML responses are
223
+ # parsed.
224
+ #
225
+ # Later attempts to load objects instantiated from these classes cause a
226
+ # problem for YAML, because it knows nothing of classes that were
227
+ # dynamically defined by a separate process.
228
+ #
229
+ def AWSObject.yaml_load(io)
230
+ io.each do |line|
231
+
232
+ # File data is external, so it's deemed unsafe when $SAFE > 0, which
233
+ # is the case with mod_ruby, for example, where $SAFE == 1.
234
+ #
235
+ # YAML data isn't eval'ed or anything dangerous like that, so we
236
+ # consider it safe to untaint it. If we don't, mod_ruby will complain
237
+ # when Module#const_defined? is invoked a few lines down from here.
238
+ #
239
+ line.untaint
240
+
241
+ m = line.match( /Amazon::AWS::AWSObject::([^ ]+)/ )
242
+ if m
243
+ cl_name = [ m[1] ]
244
+
245
+ # Module#const_defined? takes 2 parameters in Ruby 1.9.
246
+ #
247
+ cl_name << false if Object.method( :const_defined? ).arity == -1
248
+
249
+ unless AWSObject.const_defined?( *cl_name )
250
+ AWSObject.const_set( m[1], Class.new( AWSObject ) )
251
+ end
252
+
253
+ end
254
+ end
255
+
256
+ io.rewind
257
+ YAML.load( io )
258
+ end
259
+
260
+
261
+ def initialize(op=nil)
262
+ # The name of this instance variable must never clash with the
263
+ # uncamelised name of an Amazon tag.
264
+ #
265
+ # This is used to store the REXML::Text value of an element, which
266
+ # exists only when the element contains no children.
267
+ #
268
+ @__val__ = nil
269
+ @__op__ = op if op
270
+ end
271
+
272
+
273
+ def method_missing(method, *params)
274
+ iv = '@' + method.id2name
275
+
276
+ if instance_variables.include?( iv )
277
+ instance_variable_get( iv )
278
+ elsif instance_variables.include?( iv.to_sym )
279
+
280
+ # Ruby 1.9 Object#instance_variables method returns Array of Symbol,
281
+ # not String.
282
+ #
283
+ instance_variable_get( iv.to_sym )
284
+ else
285
+ nil
286
+ end
287
+ end
288
+ private :method_missing
289
+
290
+
291
+ def remove_val
292
+ remove_instance_variable( :@__val__ )
293
+ end
294
+ private :remove_val
295
+
296
+
297
+ # Iterator method for cycling through an object's properties and values.
298
+ #
299
+ def each # :yields: property, value
300
+ self.properties.each do |iv|
301
+ yield iv, instance_variable_get( "@#{iv}" )
302
+ end
303
+ end
304
+
305
+ alias :each_property :each
306
+
307
+
308
+ def inspect # :nodoc:
309
+ remove_val if instance_variable_defined?( :@__val__ ) && @__val__.nil?
310
+ str = super
311
+ str.sub( /@__val__=/, 'value=' ) if str
312
+ end
313
+
314
+
315
+ def to_s # :nodoc:
316
+ if instance_variable_defined?( :@__val__ )
317
+ return @__val__ if @__val__.is_a?( String )
318
+ remove_val
319
+ end
320
+
321
+ string = ''
322
+
323
+ # Assemble the object's details.
324
+ #
325
+ each { |iv, value| string << "%s = %s\n" % [ iv, value ] }
326
+
327
+ string
328
+ end
329
+
330
+ alias :to_str :to_s
331
+
332
+
333
+ def to_i # :nodoc:
334
+ @__val__.to_i
335
+ end
336
+
337
+
338
+ def ==(other) # :nodoc:
339
+ @__val__.to_s == other
340
+ end
341
+
342
+
343
+ def =~(other) # :nodoc:
344
+ @__val__.to_s =~ other
345
+ end
346
+
347
+
348
+ # This alias makes the ability to determine an AWSObject's properties a
349
+ # little more intuitive. It's pretty much just an alias for the
350
+ # inherited <em>Object#instance_variables</em> method, with a little
351
+ # tidying.
352
+ #
353
+ def properties
354
+ # Make sure we remove the leading @.
355
+ #
356
+ iv = instance_variables.collect { |v| v = v[1..-1] }
357
+ iv.delete( '__val__' )
358
+ iv
359
+ end
360
+
361
+
362
+ # Provide a shortcut down to the data likely to be of most interest.
363
+ # This method is experimental and may be removed.
364
+ #
365
+ def kernel # :nodoc:
366
+ # E.g. Amazon::AWS::SellerListingLookup -> seller_listing_lookup
367
+ #
368
+ stub = Amazon.uncamelise( @__op__.class.to_s.sub( /^.+::/, '' ) )
369
+
370
+ # E.g. seller_listing_response
371
+ #
372
+ level1 = stub + '_response'
373
+
374
+ # E.g. seller_listing
375
+ #
376
+ level3 = stub.sub( /_[^_]+$/, '' )
377
+
378
+ # E.g. seller_listings
379
+ #
380
+ level2 = level3 + 's'
381
+
382
+ # E.g.
383
+ # seller_listing_search_response[0].seller_listings[0].seller_listing
384
+ #
385
+ self.instance_variable_get( "@#{level1}" )[0].
386
+ instance_variable_get( "@#{level2}" )[0].
387
+ instance_variable_get( "@#{level3}" )
388
+ end
389
+
390
+
391
+ # Convert an AWSObject to a Hash.
392
+ #
393
+ def to_h
394
+ hash = {}
395
+
396
+ each do |iv, value|
397
+ if value.is_a? AWSObject
398
+ hash[iv] = value.to_h
399
+ elsif value.is_a?( AWSArray ) && value.size == 1
400
+ hash[iv] = value[0]
401
+ else
402
+ hash[iv] = value
403
+ end
404
+ end
405
+
406
+ hash
407
+ end
408
+
409
+
410
+ # Fake the appearance of an AWSObject as a hash. _key_ should be any
411
+ # attribute of the object and can be a String, Symbol or anything else
412
+ # that can be converted to a String with to_s.
413
+ #
414
+ def [](key)
415
+ instance_variable_get( "@#{key}" )
416
+ end
417
+
418
+
419
+ # Recursively walk through an XML tree, starting from _node_. This is
420
+ # called internally and is not intended for user code.
421
+ #
422
+ def walk(node) # :nodoc:
423
+
424
+ if node.instance_of?( REXML::Document )
425
+ walk( node.root )
426
+
427
+ elsif node.instance_of?( REXML::Element )
428
+ name = Amazon.uncamelise( node.name )
429
+
430
+ cl_name = [ node.name ]
431
+
432
+ # Module#const_defined? takes 2 parameters in Ruby 1.9.
433
+ #
434
+ cl_name << false if Object.method( :const_defined? ).arity == -1
435
+
436
+ # Create a class for the new element type unless it already exists.
437
+ #
438
+ unless AWS::AWSObject.const_defined?( *cl_name )
439
+ cl = AWS::AWSObject.const_set( node.name, Class.new( AWSObject ) )
440
+
441
+ # Give it an accessor for @attrib.
442
+ #
443
+ cl.send( :attr_accessor, :attrib )
444
+ end
445
+
446
+ # Instantiate an object in the newly created class.
447
+ #
448
+ obj = AWS::AWSObject.const_get( node.name ).new
449
+
450
+ sym_name = "@#{name}".to_sym
451
+
452
+ if instance_variable_defined?( sym_name)
453
+ instance_variable_set( sym_name,
454
+ instance_variable_get( sym_name ) << obj )
455
+ else
456
+ instance_variable_set( sym_name, AWSArray.new( [ obj ] ) )
457
+ end
458
+
459
+ if node.has_attributes?
460
+ obj.attrib = {}
461
+ node.attributes.each_pair do |a_name, a_value|
462
+ obj.attrib[a_name.downcase] =
463
+ a_value.to_s.sub( /^#{a_name}=/, '' )
464
+ end
465
+ end
466
+
467
+ node.children.each { |child| obj.walk( child ) }
468
+
469
+ else # REXML::Text
470
+ @__val__ = node.to_s
471
+ end
472
+ end
473
+
474
+
475
+ # For objects of class AWSObject::.*Image, fetch the image in question,
476
+ # optionally overlaying a discount icon for the percentage amount of
477
+ # _discount_ to the image.
478
+ #
479
+ def get(discount=nil)
480
+ if self.class.to_s =~ /Image$/ && @url
481
+ url = URI.parse( @url[0] )
482
+ url.path.sub!( /(\.\d\d\._)/, "\\1PE#{discount}" ) if discount
483
+
484
+ # FIXME: All HTTP in Ruby/AWS should go through the same method.
485
+ #
486
+ Net::HTTP.start( url.host, url.port ) do |http|
487
+ http.get( url.path )
488
+ end.body
489
+
490
+ else
491
+ nil
492
+ end
493
+ end
494
+
495
+ end
496
+
497
+
498
+ # Everything we get back from AWS is transformed into an array. Many of
499
+ # these, however, have only one element, because the corresponding XML
500
+ # consists of a parent element containing only a single child element.
501
+ #
502
+ # This class consists solely to allow single element arrays to pass a
503
+ # method call down to their one element, thus obviating the need for lots
504
+ # of references to <tt>foo[0]</tt> in user code.
505
+ #
506
+ # For example, the following:
507
+ #
508
+ # items = resp.item_search_response[0].items[0].item
509
+ #
510
+ # can be reduced to:
511
+ #
512
+ # items = resp.item_search_response.items.item
513
+ #
514
+ class AWSArray < Array
515
+
516
+ def method_missing(method, *params)
517
+ self.size == 1 ? self[0].send( method, *params ) : super
518
+ end
519
+ private :method_missing
520
+
521
+
522
+ # In the case of a single-element array, return the first element,
523
+ # converted to a String.
524
+ #
525
+ def to_s # :nodoc:
526
+ self.size == 1 ? self[0].to_s : super
527
+ end
528
+
529
+ alias :to_str :to_s
530
+
531
+
532
+ # In the case of a single-element array, return the first element,
533
+ # converted to an Integer.
534
+ #
535
+ def to_i # :nodoc:
536
+ self.size == 1 ? self[0].to_i : super
537
+ end
538
+
539
+
540
+ # In the case of a single-element array, compare the first element with
541
+ # _other_.
542
+ #
543
+ def ==(other) # :nodoc:
544
+ self.size == 1 ? self[0].to_s == other : super
545
+ end
546
+
547
+
548
+ # In the case of a single-element array, perform a pattern match on the
549
+ # first element against _other_.
550
+ #
551
+ def =~(other) # :nodoc:
552
+ self.size == 1 ? self[0].to_s =~ other : super
553
+ end
554
+
555
+ end
556
+
557
+
558
+ # This is the base class of all AWS operations.
559
+ #
560
+ class Operation
561
+
562
+ # These are the types of AWS operation currently implemented by Ruby/AWS.
563
+ #
564
+ OPERATIONS = %w[
565
+ BrowseNodeLookup CustomerContentLookup CustomerContentSearch
566
+ Help ItemLookup ItemSearch
567
+ ListLookup ListSearch SellerListingLookup
568
+ SellerListingSearch SellerLookup SimilarityLookup
569
+ TagLookup TransactionLookup VehiclePartLookup
570
+ VehiclePartSearch VehicleSearch
571
+
572
+ CartAdd CartClear CartCreate
573
+ CartGet CartModify
574
+ ]
575
+
576
+ attr_reader :kind
577
+ attr_accessor :params
578
+
579
+ def initialize(parameters)
580
+
581
+ op_kind = self.class.to_s.sub( /^.*::/, '' )
582
+ unless OPERATIONS.include?( op_kind ) || op_kind == 'MultipleOperation'
583
+ raise "Bad operation: #{op_kind}"
584
+ end
585
+ #raise 'Too many parameters' if parameters.size > 10
586
+
587
+ @kind = op_kind
588
+ @params = { 'Operation' => op_kind }.merge( parameters )
589
+ end
590
+
591
+
592
+ # Group together operations of the same class in a batch request.
593
+ # _operations_ should be either an operation of the same class as *self*
594
+ # or an array of such operations.
595
+ #
596
+ # If you need to batch operations from different classes, use a
597
+ # MultipleOperation instead.
598
+ #
599
+ # Example:
600
+ #
601
+ # is = ItemSearch.new( 'Books', { 'Title' => 'ruby programming' } )
602
+ # is2 = ItemSearch.new( 'Music', { 'Artist' => 'stranglers' } )
603
+ # is.batch( is2 )
604
+ #
605
+ # Please see MultipleOperation.new for details of a couple of
606
+ # restrictions that also apply to batched operations.
607
+ #
608
+ def batch(*operations)
609
+
610
+ # Remove the Operation parameter to avoid batch syntax being applied.
611
+ # We'll readd it at the end.
612
+ #
613
+ op_type = @params.delete( 'Operation' )
614
+
615
+ operations.flatten.each do |op|
616
+
617
+ unless self.class == op.class
618
+ raise BatchError, "You can't batch different classes of operation. Use class MultipleOperation."
619
+ end
620
+
621
+ # Remove the Operation parameter.
622
+ #
623
+ op.params.delete( 'Operation' )
624
+
625
+ # Apply batch syntax.
626
+ #
627
+ @params = batch_parameters( @params, op.params )
628
+ end
629
+
630
+ # Reinstate the Operation parameter.
631
+ #
632
+ @params.merge!( { 'Operation' => op_type } )
633
+ end
634
+
635
+
636
+ # Convert parameters to batch format, e.g. ItemSearch.1.Title.
637
+ #
638
+ def batch_parameters(params, *b_params) # :nodoc:
639
+
640
+ @index ||= 1
641
+
642
+ unless b_params.empty?
643
+ op_str = self.class.to_s.sub( /^.+::/, '' )
644
+
645
+ # Fudge the operation string if we're dealing with a shopping cart.
646
+ #
647
+ op_str = 'Item' if op_str =~ /^Cart/
648
+
649
+ all_parameters = [ params ].concat( b_params )
650
+ params = {}
651
+
652
+ all_parameters.each_with_index do |hash, index|
653
+
654
+ # Don't batch an already batched hash.
655
+ #
656
+ if ! hash.empty? && hash.to_a[0][0] =~ /^.+\..+\..+$/
657
+ params = hash
658
+ next
659
+ end
660
+
661
+ hash.each do |tag, val|
662
+ shared_param = '%s.%d.%s' % [ op_str, @index + index, tag ]
663
+ params[shared_param] = val
664
+ end
665
+ end
666
+
667
+ @index += b_params.size
668
+
669
+ end
670
+
671
+ params
672
+ end
673
+
674
+ end
675
+
676
+
677
+ # This class can be used to merge multiple operations into a single
678
+ # operation for greater efficiency.
679
+ #
680
+ class MultipleOperation < Operation
681
+
682
+ # This will allow you to take two Operation objects and combine them to
683
+ # form a single object, which can then be used to perform a single
684
+ # request to AWS. This allows for greater efficiency, reducing the
685
+ # number of requests sent to AWS.
686
+ #
687
+ # AWS currently imposes a limit of two combined operations in a multiple
688
+ # operation.
689
+ #
690
+ # <em>operation1</em> and <em>operation2</em> are both objects from a
691
+ # subclass of Operation, such as ItemSearch, ItemLookup, etc.
692
+ #
693
+ # There are currently a few restrictions in the Ruby/AWS implementation
694
+ # of multiple operations:
695
+ #
696
+ # - ResponseGroup objects used when calling AWS::Search::Request#search
697
+ # apply to both operations. You cannot use a different ResponseGroup
698
+ # with each operation.
699
+ #
700
+ # - One or both operations may have multiple results pages available,
701
+ # but only the first page is returned. If you need the subsequent
702
+ # pages, perform the operations separately, not as part of a
703
+ # MultipleOperation.
704
+ #
705
+ # Example:
706
+ #
707
+ # is = ItemSearch.new( 'Books', { 'Title' => 'Ruby' } )
708
+ # il = ItemLookup.new( 'ASIN', { 'ItemId' => 'B0013DZAYO',
709
+ # 'MerchantId' => 'Amazon' } )
710
+ # mo = MultipleOperation.new( is, il )
711
+ #
712
+ # As you can see, the operations that are combined as a
713
+ # MultipleOperation do not have to belong to the same class. In the
714
+ # above example, we compose a multiple operation consisting of an
715
+ # ItemSearch and an ItemLookup.
716
+ #
717
+ # If you want to batch operations belonging to the same class,
718
+ # Operation#batch provides an alternative.
719
+ #
720
+ def initialize(operation1, operation2)
721
+
722
+ # Safeguard against changing original Operation objects in place. This
723
+ # is to protect me, not for user code.
724
+ #
725
+ operation1.freeze
726
+ operation2.freeze
727
+
728
+ op_kind = '%s,%s' % [ operation1.kind, operation2.kind ]
729
+
730
+ # Duplicate Operation objects and remove their Operation parameter.
731
+ #
732
+ op1 = operation1.dup
733
+ op1.params = op1.params.dup
734
+ op1.params.delete( 'Operation' )
735
+
736
+ op2 = operation2.dup
737
+ op2.params = op2.params.dup
738
+ op2.params.delete( 'Operation' )
739
+
740
+ if op1.class == op2.class
741
+
742
+ # If both operations are of the same type, we combine the parameters
743
+ # of both.
744
+ #
745
+ b_params = op1.batch_parameters( op1.params, op2.params )
746
+ else
747
+
748
+ # We have to convert the parameters to batch format.
749
+ #
750
+ bp1 = op1.batch_parameters( op1.params, {} )
751
+ bp2 = op2.batch_parameters( op2.params, {} )
752
+ b_params = bp1.merge( bp2 )
753
+ end
754
+
755
+ params = { 'Operation' => op_kind }.merge( b_params )
756
+ super( params )
757
+
758
+ end
759
+
760
+ end
761
+
762
+
763
+ # This class of operation aids in finding out about AWS operations and
764
+ # response groups.
765
+ #
766
+ class Help < Operation
767
+
768
+ # Return information on AWS operations and response groups.
769
+ #
770
+ # For operations, required and optional parameters are returned, along
771
+ # with information about which response groups the operation can use.
772
+ #
773
+ # For response groups, The list of operations that can use that group is
774
+ # returned, as well as the list of response tags returned by the group.
775
+ #
776
+ # _help_type_ is the type of object for which help is being sought, such
777
+ # as *Operation* or *ResponseGroup*. _about_ is the name of the
778
+ # operation or response group you need help with, and _parameters_ is an
779
+ # optional hash of parameters that further refine the request for help.
780
+ #
781
+ def initialize(help_type, about, parameters={})
782
+ super( { 'HelpType' => help_type,
783
+ 'About' => about
784
+ }.merge( parameters ) )
785
+ end
786
+
787
+ end
788
+
789
+
790
+ # This is the class for the most common type of AWS look-up, an
791
+ # ItemSearch. This allows you to search for items that match a set of
792
+ # broad criteria. It returns items for sale by Amazon merchants and most
793
+ # types of seller.
794
+ #
795
+ class ItemSearch < Operation
796
+
797
+ # Not all search indices work in all locales. It is the user's
798
+ # responsibility to ensure that a given index is valid within a given
799
+ # locale.
800
+ #
801
+ # According to the AWS documentation:
802
+ #
803
+ # - *All* searches through all indices (but currently exists only in the
804
+ # *US* locale).
805
+ # - *Blended* combines Apparel, Automotive, Books, DVD, Electronics,
806
+ # GourmetFood, Kitchen, Music, PCHardware, PetSupplies, Software,
807
+ # SoftwareVideoGames, SportingGoods, Tools, Toys, VHS and VideoGames.
808
+ # - *Merchants* combines all search indices for a merchant given with
809
+ # MerchantId.
810
+ # - *Music* combines the Classical, DigitalMusic, and MusicTracks
811
+ # indices.
812
+ # - *Video* combines the DVD and VHS search indices.
813
+ #
814
+ # Note that {page
815
+ # 53}[http://docs.amazonwebservices.com/AWSECommerceService/2009-01-06/DG/SearchIndices.html]
816
+ # of the PDF of the AWS Developer Guide (revision 2009-01-06) contains
817
+ # an outdated description of *Blended* with too few subindices. {Page
818
+ # 95}[http://docs.amazonwebservices.com/AWSECommerceService/2009-01-06/DG/CommonItemSearchParameters.html]
819
+ # of the PDF contains the correct list.
820
+ #
821
+ SEARCH_INDICES = %w[
822
+ All
823
+ Apparel
824
+ Automotive
825
+ Baby
826
+ Beauty
827
+ Blended
828
+ Books
829
+ Classical
830
+ DigitalMusic
831
+ DVD
832
+ Electronics
833
+ ForeignBooks
834
+ GourmetFood
835
+ Grocery
836
+ HealthPersonalCare
837
+ Hobbies
838
+ HomeGarden
839
+ Industrial
840
+ Jewelry
841
+ KindleStore
842
+ Kitchen
843
+ Magazines
844
+ Merchants
845
+ Miscellaneous
846
+ MP3Downloads
847
+ Music
848
+ MusicalInstruments
849
+ MusicTracks
850
+ OfficeProducts
851
+ OutdoorLiving
852
+ PCHardware
853
+ PetSupplies
854
+ Photo
855
+ SilverMerchant
856
+ Software
857
+ SoftwareVideoGames
858
+ SportingGoods
859
+ Tools
860
+ Toys
861
+ VHS
862
+ Video
863
+ VideoGames
864
+ Watches
865
+ Wireless
866
+ WirelessAccessories
867
+ ]
868
+
869
+
870
+ # Search AWS for items. _search_index_ must be one of _SEARCH_INDICES_
871
+ # and _parameters_ is an optional hash of parameters that further refine
872
+ # the scope of the search.
873
+ #
874
+ # Example:
875
+ #
876
+ # is = ItemSearch.new( 'Books', { 'Title' => 'ruby programming' } )
877
+ #
878
+ # In the above example, we search for books with <b>Ruby Programming</b>
879
+ # in the title.
880
+ #
881
+ def initialize(search_index, parameters)
882
+ unless SEARCH_INDICES.include? search_index.to_s
883
+ raise "Invalid search index: #{search_index}"
884
+ end
885
+
886
+ super( { 'SearchIndex' => search_index }.merge( parameters ) )
887
+ end
888
+
889
+ end
890
+
891
+
892
+ # This class of look-up deals with searching for *specific* items by some
893
+ # uniquely identifying attribute, such as the ASIN (*A*mazon *S*tandard
894
+ # *I*tem *N*umber).
895
+ #
896
+ class ItemLookup < Operation
897
+
898
+ # Look up a specific item in the AWS catalogue. _id_type_ is the type of
899
+ # identifier and _parameters_ is a hash that identifies the item to be
900
+ # located and narrows the scope of the search.
901
+ #
902
+ # Example:
903
+ #
904
+ # il = ItemLookup.new( 'ASIN', { 'ItemId' => 'B000AE4QEC'
905
+ # 'MerchantId' => 'Amazon' } )
906
+ #
907
+ # In the above example, we search for an item, based on its ASIN. The
908
+ # use of _MerchantId_ restricts the offers returned to those for sale
909
+ # by Amazon (as opposed to third-party sellers).
910
+ #
911
+ def initialize(id_type, parameters)
912
+ super( { 'IdType' => id_type }.merge( parameters ) )
913
+ end
914
+
915
+ end
916
+
917
+
918
+ # Search for items for sale by a particular seller.
919
+ #
920
+ class SellerListingSearch < Operation
921
+
922
+ # Search for items for sale by a particular seller. _seller_id_ is the
923
+ # Amazon seller ID and _parameters_ is an optional hash of parameters
924
+ # that further refine the scope of the search.
925
+ #
926
+ # Example:
927
+ #
928
+ # sls = SellerListingSearch.new( 'A33J388YD2MWJZ',
929
+ # { 'Keywords' => 'Killing Joke' } )
930
+ #
931
+ # In the above example, we search seller <b>A33J388YD2MWJ</b>'s listings
932
+ # for items with the keywords <b>Killing Joke</b>.
933
+ #
934
+ def initialize(seller_id, parameters)
935
+ super( { 'SellerId' => seller_id }.merge( parameters ) )
936
+ end
937
+
938
+ end
939
+
940
+
941
+ # Return specified items in a seller's store.
942
+ #
943
+ class SellerListingLookup < ItemLookup
944
+
945
+ # Look up a specific item for sale by a specific seller. _id_type_ is
946
+ # the type of identifier and _parameters_ is a hash that identifies the
947
+ # item to be located and narrows the scope of the search.
948
+ #
949
+ # Example:
950
+ #
951
+ # sll = SellerListingLookup.new( 'AP8U6Y3PYQ9VO', 'ASIN',
952
+ # { 'Id' => 'B0009RRRC8' } )
953
+ #
954
+ # In the above example, we search seller <b>AP8U6Y3PYQ9VO</b>'s listings
955
+ # to find items for sale with the ASIN <b>B0009RRRC8</b>.
956
+ #
957
+ def initialize(seller_id, id_type, parameters)
958
+ super( id_type, { 'SellerId' => seller_id }.merge( parameters ) )
959
+ end
960
+
961
+ end
962
+
963
+
964
+ # Return information about a specific seller.
965
+ #
966
+ class SellerLookup < Operation
967
+
968
+ # Search for the details of a specific seller. _seller_id_ is the Amazon
969
+ # ID of the seller in question and _parameters_ is an optional hash of
970
+ # parameters that further refine the scope of the search.
971
+ #
972
+ # Example:
973
+ #
974
+ # sl = SellerLookup.new( 'A3QFR0K2KCB7EG' )
975
+ #
976
+ # In the above example, we look up the details of the seller with ID
977
+ # <b>A3QFR0K2KCB7EG</b>.
978
+ #
979
+ def initialize(seller_id, parameters={})
980
+ super( { 'SellerId' => seller_id }.merge( parameters ) )
981
+ end
982
+
983
+ end
984
+
985
+
986
+ # Obtain the information an Amazon customer has made public about
987
+ # themselves.
988
+ #
989
+ class CustomerContentLookup < Operation
990
+
991
+ # Search for public customer data. _customer_id_ is the unique ID
992
+ # identifying the customer on Amazon and _parameters_ is an optional
993
+ # hash of parameters that further refine the scope of the search.
994
+ #
995
+ # Example:
996
+ #
997
+ # ccl = CustomerContentLookup.new( 'AJDWXANG1SYZP' )
998
+ #
999
+ # In the above example, we look up public data about the customer with
1000
+ # the ID <b>AJDWXANG1SYZP</b>.
1001
+ #
1002
+ def initialize(customer_id, parameters={})
1003
+ super( { 'CustomerId' => customer_id }.merge( parameters ) )
1004
+ end
1005
+
1006
+ end
1007
+
1008
+
1009
+ # Retrieve basic Amazon customer data.
1010
+ #
1011
+ class CustomerContentSearch < Operation
1012
+
1013
+ # Retrieve customer information, using an e-mail address or name.
1014
+ #
1015
+ # If _customer_id_ contains an '@' sign, it is assumed to be an e-mail
1016
+ # address. Otherwise, it is assumed to be the customer's name.
1017
+ #
1018
+ # Example:
1019
+ #
1020
+ # ccs = CustomerContentSearch.new( 'ian@caliban.org' )
1021
+ #
1022
+ # In the above example, we look up customer information about
1023
+ # <b>ian@caliban.org</b>. The *CustomerInfo* response group will return,
1024
+ # amongst other things, a _customer_id_ property, which can then be
1025
+ # plugged into CustomerContentLookup to retrieve more detailed customer
1026
+ # information.
1027
+ #
1028
+ def initialize(customer_id)
1029
+ id = customer_id =~ /@/ ? 'Email' : 'Name'
1030
+ super( { id => customer_id } )
1031
+ end
1032
+
1033
+ end
1034
+
1035
+
1036
+ # Find wishlists, registry lists, etc. created by users and placed on
1037
+ # Amazon. These are items that customers would like to receive as
1038
+ # presnets.
1039
+ #
1040
+ class ListSearch < Operation
1041
+
1042
+ # Search for Amazon lists. _list_type_ is the type of list to search for
1043
+ # and _parameters_ is an optional hash of parameters that narrow the
1044
+ # scope of the search.
1045
+ #
1046
+ # Example:
1047
+ #
1048
+ # ls = ListSearch.new( 'WishList', { 'Name' => 'Peter Duff' }
1049
+ #
1050
+ # In the above example, we retrieve the wishlist for the Amazon user,
1051
+ # <b>Peter Duff</b>.
1052
+ #
1053
+ def initialize(list_type, parameters)
1054
+ super( { 'ListType' => list_type }.merge( parameters ) )
1055
+ end
1056
+
1057
+ end
1058
+
1059
+
1060
+ # Find the details of specific wishlists, registries, etc.
1061
+ #
1062
+ class ListLookup < Operation
1063
+
1064
+ # Look up and return details about a specific list. _list_id_ is the
1065
+ # Amazon list ID, _list_type_ is the type of list and _parameters_ is an
1066
+ # optional hash of parameters that narrow the scope of the search.
1067
+ #
1068
+ # Example:
1069
+ #
1070
+ # ll = ListLookup.new( '3P722DU4KUPCP', 'Listmania' )
1071
+ #
1072
+ # In the above example, a *Listmania* list with the ID
1073
+ # <b>3P722DU4KUPCP</b> is retrieved from AWS.
1074
+ #
1075
+ def initialize(list_id, list_type, parameters={})
1076
+ super( { 'ListId' => list_id,
1077
+ 'ListType' => list_type
1078
+ }.merge( parameters ) )
1079
+ end
1080
+
1081
+ end
1082
+
1083
+
1084
+ # Amazon use browse nodes as a means of organising the millions of items
1085
+ # in their inventory. An example might be *Carving Knives*. Looking up a
1086
+ # browse node enables you to determine that group's ancestors and
1087
+ # descendants.
1088
+ #
1089
+ class BrowseNodeLookup < Operation
1090
+
1091
+ # Look up and return the details of an Amazon browse node. _node_ is the
1092
+ # browse node to look up and _parameters_ is an optional hash of
1093
+ # parameters that further refine the scope of the search. _parameters_
1094
+ # is currently unused.
1095
+ #
1096
+ # Example:
1097
+ #
1098
+ # bnl = BrowseNodeLookup.new( '11232', {} )
1099
+ #
1100
+ # In the above example, we look up the browse node with the ID
1101
+ # <b>11232</b>. This is the <b>Social Sciences</b> browse node.
1102
+ #
1103
+ def initialize(node, parameters={})
1104
+ super( { 'BrowseNodeId' => node }.merge( parameters ) )
1105
+ end
1106
+
1107
+ end
1108
+
1109
+
1110
+ # Similarity look-up is for items similar to others.
1111
+ #
1112
+ class SimilarityLookup < Operation
1113
+
1114
+ # Look up items similar to _asin_, which can be a single item or an
1115
+ # array. _parameters_ is an optional hash of parameters that further
1116
+ # refine the scope of the search.
1117
+ #
1118
+ # Example:
1119
+ #
1120
+ # sl = SimilarityLookup.new( 'B000051WBE' )
1121
+ #
1122
+ # In the above example, we search for items similar to the one with ASIN
1123
+ # <b>B000051WBE</b>.
1124
+ #
1125
+ def initialize(asin, parameters={})
1126
+ super( { 'ItemId' => asin.to_a.join( ',' ) }.merge( parameters ) )
1127
+ end
1128
+
1129
+ end
1130
+
1131
+
1132
+ # Search for entities based on user-defined tags. A tag is a descriptive
1133
+ # word that a customer uses to label entities on Amazon's Web site.
1134
+ # Entities can be items for sale, Listmania lists, guides, etc.
1135
+ #
1136
+ class TagLookup < Operation
1137
+
1138
+ # Look up entities based on user-defined tags. _tag_name_ is the tag to
1139
+ # search on and _parameters_ is an optional hash of parameters that
1140
+ # further refine the scope of the search.
1141
+ #
1142
+ # Example:
1143
+ #
1144
+ # tl = TagLookup.new( 'Awful' )
1145
+ #
1146
+ # In the example above, we search for entities tagged by users with the
1147
+ # word *Awful*.
1148
+ #
1149
+ def initialize(tag_name, parameters={})
1150
+ super( { 'TagName' => tag_name }.merge( parameters ) )
1151
+ end
1152
+
1153
+ end
1154
+
1155
+
1156
+ # Search for information on previously completed purchases.
1157
+ #
1158
+ class TransactionLookup < Operation
1159
+
1160
+ # Return information on an already completed purchase. _transaction_id_
1161
+ # is actually the order number that is created when you place an order
1162
+ # on Amazon.
1163
+ #
1164
+ # Example:
1165
+ #
1166
+ # tl = TransactionLookup.new( '103-5663398-5028241' )
1167
+ #
1168
+ # In the above example, we retrieve the details of order number
1169
+ # <b>103-5663398-5028241</b>.
1170
+ #
1171
+ def initialize(transaction_id)
1172
+ super( { 'TransactionId' => transaction_id } )
1173
+ end
1174
+
1175
+ end
1176
+
1177
+
1178
+ # Look up individual vehicle parts.
1179
+ #
1180
+ class VehiclePartLookup < Operation
1181
+
1182
+ # Look up a particular vehicle part. _item_id_ is the ASIN of the part
1183
+ # in question and _parameters_ is an optional hash of parameters that
1184
+ # further refine the scope of the search.
1185
+ #
1186
+ # Although the _item_id_ alone is enough to locate the part, providing
1187
+ # _parameters_ can be useful in determining whether the part looked up
1188
+ # is a fit for a particular vehicle type, as with the *VehiclePartFit*
1189
+ # response group.
1190
+ #
1191
+ # Example:
1192
+ #
1193
+ # vpl = VehiclePartLookup.new( 'B000C1ZLI8',
1194
+ # { 'Year' => 2008,
1195
+ # 'MakeId' => 73,
1196
+ # 'ModelId' => 6039,
1197
+ # 'TrimId' => 20 } )
1198
+ #
1199
+ # Here, we search for a <b>2008</b> model *Audi* <b>R8</b> with *Base*
1200
+ # trim. The required Ids can be found using VehiclePartSearch.
1201
+ #
1202
+ def initialize(item_id, parameters={})
1203
+ super( { 'ItemId' => item_id }.merge( parameters ) )
1204
+ end
1205
+
1206
+ end
1207
+
1208
+
1209
+ # Search for parts for a given vehicle.
1210
+ #
1211
+ class VehiclePartSearch < Operation
1212
+
1213
+ # Find parts for a given _year_, _make_id_ and _model_id_ of vehicle.
1214
+ # _parameters_ is an optional hash of parameters that further refine the
1215
+ # scope of the search.
1216
+ #
1217
+ # Example:
1218
+ #
1219
+ # vps = VehiclePartSearch.new( 2008, 73, 6039,
1220
+ # { 'TrimId' => 20,
1221
+ # 'EngineId' => 8914 } )
1222
+ #
1223
+ # In this example, we look for parts that will fit a <b>2008</b> model
1224
+ # *Audi* <b>R8</b> with *Base* trim and a <b>4.2L V8 Gas DOHC
1225
+ # Distributorless Naturally Aspirated Bosch Motronic Electronic FI
1226
+ # MFI</b> engine.
1227
+ #
1228
+ # Note that pagination of VehiclePartSearch results is not currently
1229
+ # supported.
1230
+ #
1231
+ # Use VehicleSearch to learn the MakeId and ModelId of the vehicle in
1232
+ # which you are interested.
1233
+ #
1234
+ def initialize(year, make_id, model_id, parameters={})
1235
+ super( { 'Year' => year,
1236
+ 'MakeId' => make_id,
1237
+ 'ModelId' => model_id }.merge( parameters ) )
1238
+ end
1239
+
1240
+ end
1241
+
1242
+
1243
+ # Search for vehicles.
1244
+ #
1245
+ class VehicleSearch < Operation
1246
+
1247
+ # Search for vehicles, based on one or more of the following
1248
+ # _parameters_: Year, MakeId, ModelId and TrimId.
1249
+ #
1250
+ # This method is best used iteratively. For example, first search on
1251
+ # year with a response group of *VehicleMakes* to return all makes for
1252
+ # that year.
1253
+ #
1254
+ # Next, search on year and make with a response group of *VehicleModels*
1255
+ # to find all models for that year and make.
1256
+ #
1257
+ # Then, search on year, make and model with a response group of
1258
+ # *VehicleTrims* to find all trim packages for that year, make and model.
1259
+ #
1260
+ # Finally, if required, search on year, make, model and trim package
1261
+ # with a response group of *VehicleOptions* to find all vehicle options
1262
+ # for that year, make, model and trim package.
1263
+ #
1264
+ # Example:
1265
+ #
1266
+ # vs = VehicleSearch.new( { 'Year' => 2008,
1267
+ # 'MakeId' => 20,
1268
+ # 'ModelId' => 6039,
1269
+ # 'TrimId' => 20 } )
1270
+ #
1271
+ # In this example, we search for <b>2008 Audi R8</b> vehicles with a
1272
+ # *Base* trim package. Used with the *VehicleOptions* response group,
1273
+ # a list of vehicle options would be returned.
1274
+ #
1275
+ def initialize(parameters={})
1276
+ super
1277
+ end
1278
+
1279
+ end
1280
+
1281
+ # Response groups determine which data pertaining to the item(s) being
1282
+ # sought is returned. They can strongly influence the amount of data
1283
+ # returned, so you should always use the smallest response group(s)
1284
+ # containing the data of interest to you, to avoid masses of unnecessary
1285
+ # data being returned.
1286
+ #
1287
+ class ResponseGroup
1288
+
1289
+ attr_reader :list, :params
1290
+
1291
+ # Define a set of one or more response groups to be applied to items
1292
+ # retrieved by an AWS operation.
1293
+ #
1294
+ # If no response groups are given in _rg_ when instantiating an object,
1295
+ # *Small* will be used by default.
1296
+ #
1297
+ # Example:
1298
+ #
1299
+ # rg = ResponseGroup.new( 'Medium', 'Offers', 'Reviews' )
1300
+ #
1301
+ def initialize(*rg)
1302
+ rg << 'Small' if rg.empty?
1303
+ @list = rg
1304
+ @params = { 'ResponseGroup' => @list.join( ',' ) }
1305
+ end
1306
+
1307
+ end
1308
+
1309
+
1310
+ # All dynamically generated exceptions occur within this namespace.
1311
+ #
1312
+ module Error
1313
+
1314
+ # The base exception class for errors that result from AWS operations.
1315
+ # Classes for these are dynamically generated as subclasses of this one.
1316
+ #
1317
+ class AWSError < AmazonError; end
1318
+
1319
+ def Error.exception(xml)
1320
+ err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
1321
+ err_msg = xml.elements['Message'].text
1322
+
1323
+ # Dynamically define a new exception class for this class of error,
1324
+ # unless it already exists.
1325
+ #
1326
+ unless Amazon::AWS::Error.const_defined?( err_class )
1327
+ Amazon::AWS::Error.const_set( err_class, Class.new( AWSError ) )
1328
+ end
1329
+
1330
+ # Generate and return a new exception from the relevant class.
1331
+ #
1332
+ Amazon::AWS::Error.const_get( err_class ).new( err_msg )
1333
+ end
1334
+
1335
+ end
1336
+
1337
+ end
1338
+
1339
+ end