PageRenderTime 28ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Net/Amazon/Request.pm

http://github.com/boumenot/p5-Net-Amazon
Perl | 428 lines | 283 code | 56 blank | 89 comment | 16 complexity | d0e785934abdf56ead3bb045c4080a6c MD5 | raw file
  1. ######################################################################
  2. package Net::Amazon::Request;
  3. ######################################################################
  4. use Log::Log4perl qw(:easy get_logger);
  5. use Net::Amazon::Validate::Type;
  6. use Net::Amazon::Validate::ItemSearch;
  7. use Data::Dumper;
  8. use warnings;
  9. use strict;
  10. use constant DEFAULT_MODE => 'books';
  11. use constant DEFAULT_TYPE => 'Large';
  12. use constant DEFAULT_PAGE_COUNT => 1;
  13. use constant DEFAULT_FORMAT => 'xml';
  14. use constant PAGE_NOT_VALID => qw(TextStream);
  15. # Attempt to provide backward compatability for AWS3 types.
  16. use constant AWS3_VALID_TYPES_MAP => {
  17. 'heavy' => 'Large',
  18. 'lite' => 'Medium',
  19. };
  20. # Each key represents the REST operation used to execute the action.
  21. use constant SEARCH_TYPE_OPERATION_MAP => {
  22. Actor => 'ItemSearch',
  23. Artist => 'ItemSearch',
  24. All => 'ItemSearch',
  25. Author => 'ItemSearch',
  26. ASIN => 'ItemLookup',
  27. Blended => 'ItemSearch',
  28. BrowseNode => 'ItemSearch',
  29. Director => 'ItemSearch',
  30. EAN => 'ItemLookup',
  31. Exchange => 'SellerListingLookup',
  32. ISBN => 'ItemLookup',
  33. Keyword => 'ItemSearch',
  34. Keywords => 'ItemSearch',
  35. Manufacturer => 'ItemSearch',
  36. MP3Downloads => 'ItemSearch',
  37. MusicLabel => 'ItemSearch',
  38. Power => 'ItemSearch',
  39. Publisher => 'ItemSearch',
  40. Seller => 'SellerListingSearch',
  41. Similar => 'SimilarityLookup',
  42. TextStream => 'ItemSearch',
  43. Title => 'ItemSearch',
  44. UPC => 'ItemLookup',
  45. };
  46. # if it isn't defined it defaults to salesrank
  47. use constant DEFAULT_SORT_CRITERIA_MAP => {
  48. All => '',
  49. Blended => '',
  50. Seller => '',
  51. Exchange => '',
  52. };
  53. # if it isn't defined it defaults to ItemPage
  54. use constant DEFAULT_ITEM_PAGE_MAP => {
  55. Seller => 'ListingPage',
  56. };
  57. our $AMZN_XML_URL = 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService';
  58. ##################################################
  59. sub amzn_xml_url {
  60. ##################################################
  61. return $AMZN_XML_URL;
  62. }
  63. ##################################################
  64. sub new {
  65. ##################################################
  66. my($class, %options) = @_;
  67. my ($operation) = $class =~ m/([^:]+)$/;
  68. my $self = {
  69. Operation => SEARCH_TYPE_OPERATION_MAP->{$operation},
  70. %options,
  71. };
  72. $self->{page} = DEFAULT_PAGE_COUNT unless exists $self->{page};
  73. # TextStream doesn't allow a page (ItemPage) parameter
  74. delete $self->{page} if grep{$operation eq $_} (PAGE_NOT_VALID);
  75. # salesrank isn't a valid sort criteria for all operations
  76. if (! exists $self->{sort}) {
  77. my $sort = (defined DEFAULT_SORT_CRITERIA_MAP->{$operation})
  78. ? DEFAULT_SORT_CRITERIA_MAP->{$operation} : 'salesrank';
  79. $self->{sort} = $sort if length($sort);
  80. }
  81. my $valid = Net::Amazon::Validate::Type::factory(operation => $self->{Operation});
  82. # There is no initial default type (ResponseGroup) defined,
  83. # if there is, then attempt to map the AWS3 type to the
  84. # AWS4 type.
  85. if ($self->{type}) {
  86. if ( ref $self->{type} eq 'ARRAY' ) {
  87. my @types;
  88. for (@{$self->{type}}) {
  89. push @types, _get_valid_response_group($_, $valid);
  90. }
  91. $self->{type} = join(',', @types);
  92. } else {
  93. $self->{type} = _get_valid_response_group($self->{type}, $valid);
  94. }
  95. }
  96. # If no type was defined then try to default to Large, which is a good
  97. # all around response group. If Large is not a valid response group
  98. # let Amazon pick.
  99. else {
  100. eval { $valid->ResponseGroup(DEFAULT_TYPE) };
  101. $self->{type} = DEFAULT_TYPE unless $@;
  102. }
  103. my $item_page = (defined DEFAULT_ITEM_PAGE_MAP->{$operation})
  104. ? DEFAULT_ITEM_PAGE_MAP->{$operation} : 'ItemPage';
  105. __PACKAGE__->_convert_option($self, 'page', $item_page);
  106. __PACKAGE__->_convert_option($self, 'sort', 'Sort');
  107. __PACKAGE__->_convert_option($self, 'type', 'ResponseGroup') if defined $self->{type};
  108. # Convert all of the normal user input into Amazon's expected input. Do it
  109. # here to allow a user to narrow down there based on any field that is valid
  110. # for a search operation.
  111. #
  112. # One could add all of the different qualifiers for an ItemSearch for free.
  113. if (SEARCH_TYPE_OPERATION_MAP->{$operation} eq 'ItemSearch' ) {
  114. for (keys %{(SEARCH_TYPE_OPERATION_MAP)}) {
  115. __PACKAGE__->_convert_option($self, lc($_), $_) if defined $self->{lc($_)};
  116. }
  117. }
  118. bless $self, $class;
  119. }
  120. ##################################################
  121. sub page {
  122. ##################################################
  123. my($self) = @_;
  124. return $self->{$self->_page_type};
  125. }
  126. ##################################################
  127. sub params {
  128. ##################################################
  129. my ($self, %options) = @_;
  130. my $class = ref $self;
  131. my ($operation) = $class =~ m/([^:]+)$/;
  132. unless (grep{$operation eq $_} (PAGE_NOT_VALID)) {
  133. my $type = $self->_page_type;
  134. $self->{$type} = $options{page};
  135. }
  136. return(%$self);
  137. }
  138. ##################################################
  139. # Figure out the Response class to a given Request
  140. # class. To be used by sub classes.
  141. ##################################################
  142. sub response_class {
  143. ##################################################
  144. my($self) = @_;
  145. my $response_class = ref($self);
  146. $response_class =~ s/Request/Response/;
  147. return $response_class;
  148. }
  149. ##
  150. ## 'PRIVATE' METHODS
  151. ##
  152. # A subroutine (not a class method), to map a response group
  153. # to from AWS3 to AWS4, or validate that a response group
  154. # is valid for AWS4.
  155. sub _get_valid_response_group {
  156. my ($response_group, $valid) = @_;
  157. if (defined AWS3_VALID_TYPES_MAP->{$response_group}) {
  158. return AWS3_VALID_TYPES_MAP->{$response_group};
  159. } elsif ($valid->ResponseGroup($response_group)) {
  160. return $response_group;
  161. }
  162. # never reached, valid-> will die if the response group
  163. # is not valid for AWS4.
  164. return undef;
  165. }
  166. # CLASS->_convert_option( OPTIONS, ORIGINAL, TARGET [, CALLBACK] )
  167. #
  168. # Takes a reference to a hash of OPTIONS and renames the
  169. # ORIGINAL key name to the TARGET key name. If the optional
  170. # CALLBACK subroutine reference is defined, that subroutine
  171. # is invoked with two arguments:
  172. #
  173. # CALLBACK->( OPTIONS, TARGET )
  174. #
  175. # The result of the CALLBACK's execution is then returned to
  176. # the caller. No assumptions are made about what the CALLBACK
  177. # should return (or even *if* is should return)--that's the
  178. # caller's responsibility.
  179. #
  180. # Returns 1 in the absensence of a CALLBACK.
  181. #
  182. sub _convert_option {
  183. my ($class, $options, $original, $target, $callback) = @_;
  184. if ( exists $options->{$original} ) {
  185. $options->{$target} = $options->{$original};
  186. delete $options->{$original};
  187. }
  188. return 1 unless ( $callback );
  189. # The key name is explicitly passed-in so that the caller doesn't
  190. # have think "Hrmm.. now which key am I working on, the original
  191. # or the target key?" Confusion is bad.
  192. return $callback->($options, $target);
  193. }
  194. # CLASS->_assert_options_defined( OPTIONS, KEYS )
  195. #
  196. # Takes a reference to a hash of OPTIONS and a list of
  197. # one or more KEYS. Tests to see if each key in KEYS
  198. # has a defined value. Calls die() upon the first
  199. # missing key. Otherwise, returns undef.
  200. #
  201. sub _assert_options_defined {
  202. my ($class, $options, @keys) = @_;
  203. foreach my $key ( @keys ) {
  204. die "Mandatory parameter '$key' not defined"
  205. unless ( defined $options->{$key} );
  206. }
  207. }
  208. # CLASS->_option_or_default( OPTIONS, DEFAULT, USER )
  209. #
  210. # Takes a list of options, a default option, and a
  211. # possibly supplied user option. If the user option
  212. # is defined, it is verified that the option is valid.
  213. # If no user option is supplied, the default option is
  214. # used.
  215. sub _option_or_default {
  216. my ($self, $options, $default, $user) = @_;
  217. # if(defined $user) {
  218. # unless(grep {$user eq $_} @$options) {
  219. # die "User supplied value, $user, is not a valid option"
  220. # }
  221. # return $user;
  222. # }
  223. return $default;
  224. }
  225. # CLASS->_itemsearch_factory()
  226. #
  227. # Create an instance of an ItemSearch validator based on the
  228. # Request class. This class is used to validate user input
  229. # against valid options for a given mode, and the type of
  230. # Request.
  231. sub _itemsearch_factory {
  232. my($self) = @_;
  233. my $request_class = ref($self);
  234. my $request_type = (split(/::/, $request_class))[-1];
  235. # XXX: I'm not sure what to do here. The ItemSearch validate class
  236. # is called Keywords, but the Request/Response class is called
  237. # Keyword. For now I'm going to special case Keywords to map
  238. # to Keyword.
  239. $request_type = 'Keywords' if $request_type eq 'Keyword';
  240. return Net::Amazon::Validate::ItemSearch::factory(search_index => $request_type);
  241. }
  242. sub _convert_itemsearch {
  243. my($self) = @_;
  244. my $is = $self->_itemsearch_factory();
  245. $self->{mode} = $is->user_or_default($self->{mode});
  246. __PACKAGE__->_convert_option($self, 'mode', 'SearchIndex');
  247. }
  248. sub _page_type {
  249. my ($self, %options) = @_;
  250. my $class = ref $self;
  251. my ($operation) = $class =~ m/([^:]+)$/;
  252. my $type = (defined DEFAULT_ITEM_PAGE_MAP->{$operation})
  253. ? DEFAULT_ITEM_PAGE_MAP->{$operation} : 'ItemPage';
  254. return $type;
  255. }
  256. 1;
  257. __END__
  258. =head1 NAME
  259. Net::Amazon::Request - Baseclass for requests to Amazon's web service
  260. =head1 SYNOPSIS
  261. my $req = Net::Amazon::Request::XXX->new(
  262. [ type => 'Large', ]
  263. [ page => $start_page, ]
  264. [ mode => $mode, ]
  265. [ offer => 'All', ]
  266. [ sort => $sort_type, ]
  267. );
  268. =head1 DESCRIPTION
  269. Don't use this class directly, use derived classes (like
  270. C<Net::Amazon::Request::ASIN>, etc.) instead to specify the type of request and
  271. its parameters.
  272. However, there's a bunch of parameters to the constructor
  273. that all request types have in common, here they are:
  274. =over 4
  275. =item type
  276. Defaults to C<Large>, but can also be set to C<Medium>, or C<Small>.
  277. =over 8
  278. =item Large
  279. The C<Large> type provides everything in C<Medium> as well as music track
  280. information, customer reviews, similar products, offers, and accessory data,
  281. i.e. the kitchen sink.
  282. =item Medium
  283. The C<Medium> type provides everything in C<Small> as well as sales rank,
  284. editorial reviews, and image URLs.
  285. =item Small
  286. The C<Small> type provies ASIN, product title, creator (author, artist, etc.),
  287. product group, URL, and manufacturer.
  288. =back
  289. =item mode
  290. Defaults to C<books>, but can be set to other catalog values.
  291. =item page
  292. Defaults to C<1>, but can be set to a different number to
  293. start with a different result page. Used in conjunction with the
  294. C<max_pages> parameter of the C<Net::Amazon> object. C<page> is the
  295. offset, C<max_pages> is the maximum number of pages pulled in starting
  296. at C<page>.
  297. =item sort
  298. Defaults to C<salesrank>, but search results can be sorted in various
  299. ways, depending on the type of product returned by the search. Search
  300. results may be sorted by the following criteria:
  301. =over 8
  302. =item *
  303. Featured Items
  304. =item *
  305. Bestselling
  306. =item *
  307. Alphabetical (A-Z and Z-A)
  308. =item *
  309. Price (High to Low and Low to High)
  310. =item *
  311. Publication or Release Date
  312. =item *
  313. Manufacturer
  314. =item *
  315. Average Customer Review
  316. =item *
  317. Artist Name
  318. =back
  319. Consult L<Net::Amazon::Request::Sort> for details.
  320. =item offer
  321. To receive values for the fields
  322. C<CollectibleCount>, C<NumberOfOfferings>, C<UsedCount>,
  323. specify C<offer =E<gt> "All">.
  324. =back
  325. =head1 AUTHOR
  326. Mike Schilli, E<lt>m@perlmeister.comE<gt>
  327. =head1 COPYRIGHT AND LICENSE
  328. Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt>
  329. This library is free software; you can redistribute it and/or modify
  330. it under the same terms as Perl itself.
  331. =cut