Catalyst-Action-REST-1.21000755000765000024 013211532667 16373 5ustar00jnapiorkowskistaff000000000000TODO100644000765000024 55213211532667 17126 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21* Override setup_classes from Catalyst::Base, so things that use C::Controller:REST don't need to have ActionClass('REST') on them * More documentation of the Serialization/Deserialization process. * More Serializers! * More status methods! * More tests * Add a preference weight for what type of content to return given an equal weight Accept header.README100644000765000024 756313211532667 17347 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21NAME Catalyst::Action::REST - Automated REST Method Dispatching SYNOPSIS sub foo :Local :ActionClass('REST') { ... do setup for HTTP method specific handlers ... } sub foo_GET { ... do something for GET requests ... } # alternatively use an Action sub foo_PUT : Action { ... do something for PUT requests ... } DESCRIPTION This Action handles doing automatic method dispatching for REST requests. It takes a normal Catalyst action, and changes the dispatch to append an underscore and method name. First it will try dispatching to an action with the generated name, and failing that it will try to dispatch to a regular method. For example, in the synopsis above, calling GET on "/foo" would result in the foo_GET method being dispatched. If a method is requested that is not implemented, this action will return a status 405 (Method Not Found). It will populate the "Allow" header with the list of implemented request methods. You can override this behavior by implementing a custom 405 handler like so: sub foo_not_implemented { ... handle not implemented methods ... } If you do not provide an _OPTIONS subroutine, we will automatically respond with a 200 OK. The "Allow" header will be populated with the list of implemented request methods. If you do not provide an _HEAD either, we will auto dispatch to the _GET one in case it exists. It is likely that you really want to look at Catalyst::Controller::REST, which brings this class together with automatic Serialization of requests and responses. When you use this module, it adds the Catalyst::TraitFor::Request::REST role to your request class. METHODS dispatch This method overrides the default dispatch mechanism to the re-dispatching mechanism described above. SEE ALSO You likely want to look at Catalyst::Controller::REST, which implements a sensible set of defaults for a controller doing REST. This class automatically adds the Catalyst::TraitFor::Request::REST role to your request class. If you're writing a web application which provides RESTful responses and still needs to accommodate web browsers, you may prefer to use Catalyst::TraitFor::Request::REST::ForBrowsers instead. Catalyst::Action::Serialize, Catalyst::Action::Deserialize TROUBLESHOOTING Q: I'm getting a "415 Unsupported Media Type" error. What gives?! A: Most likely, you haven't set Content-type equal to "application/json", or one of the accepted return formats. You can do this by setting it in your query accepted return formats. You can do this by setting it in your query string thusly: ?content-type=application%2Fjson (where %2F == / uri escaped). NOTE Apache will refuse %2F unless configured otherwise. Make sure AllowEncodedSlashes On is in your httpd.conf file in order for this to run smoothly. AUTHOR Adam Jacob , with lots of help from mst and jrockway Marchex, Inc. paid me while I developed this module. (http://www.marchex.com) CONTRIBUTORS Tomas Doran (t0m) John Goulah Christopher Laco Daisuke Maki Hans Dieter Pearcey Brian Phillips Dave Rolsky Luke Saunders Arthur Axel "fREW" Schmidt J. Shirley Gavin Henry Gerv http://www.gerv.net/ Colin Newell Wallace Reis André Walker (andrewalker) COPYRIGHT Copyright (c) 2006-2015 the above named AUTHOR and CONTRIBUTORS LICENSE You may distribute this code under the same terms as Perl itself. Changes100644000765000024 4122613211532667 17774 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21Revision history for Catalyst-Action-REST 1.21 2017-12-05 10:05:25-05:00 America/New_York 1.20 2015-10-29 15:28:27-05:00 America/Chicago - Documentation fixes. - Don't clobber some response headers if they are already set. 1.19 2015-02-06 09:40:02-06:00 America/Chicago - Make LWP a test dep instead of a hard dep (Fixes GH#3, thanks Alexander Hartmaier for the report!) - Hard dep on JSON::MaybeXS (Fixes RT#101854, thanks Emmanuel Seyman for the report and Karen Etheridge for help tracking down the cause!) 1.18 2015-01-20 12:20:46-06:00 America/Chicago - Fix tests on travisci so that Catalyst proper can run on travisci (Thanks André Walker!) 1.17 2014-10-23 19:58:46-05:00 America/Chicago - Make 3xx status codes skip serialization when there is no data to serialize (Thanks Jesse Sheidlower!) 1.16 2014-09-12 13:21:43-05:00 America/Chicago - Switch from JSON to JSON::MaybeXS to get a better choice of JSON parsers. 1.15 2014-05-07 09:02:44-05:00 CST6CDT - Added new status_see_other method for returning a 303 redirect. - Added new status_moved method for returning a 301 redirect. (Matthew Keller) 1.14 2013-12-27 15:32:19 America/Chicago - Stop prompting for features at install time 1.13 2013-11-08 09:40:00 EST - Fix tests to skip if YAML::Syck is not installed (Arthur Axel fREW Schmidt) 1.12 2013-09-03 13:00:00 EST WARNING BACK COMPAT BREAKAGE FOLLOWS Removed The YAML and HTML parser from the distro. You should install these if you actually use them. They are listed as optional dependencies going forward. This is possibly a breaking change, but necessary for security and considered acceptable since those formats have not generally become preferred for web services. In addition, the default de/serialization mappings for HTML and YAML have been removed. You can add that back by adding the following to you Configuration for the subclass of Catalyst::Controller::REST - package Foo::Controller::Bar; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller::REST' } __PACKAGE__->config( 'map' => { 'text/html' => 'YAML::HTML', 'text/x-yaml' => 'YAML', }, ); You should do this if you are using these de/serialization formats. 1.11 2013-06-16 15:23:03 BST - Fix infinite recursion in tests under Catalyst 5.90040 1.10 2013-04-22 14:36:53 BST - Use YAML rather than JSON in basic tests 1.09 2013-04-19 13:34:38 BST - Don't load Data::Serializer unnecessarily in tests 1.08 2013-04-16 08:33:00 BST - Factor Data::Serializable into it's own dist to stop breakages. If you use any of: * Data::Dumper * Data::Denter * Data::Taxi * Config::General * PHP::Serialization You'll need to install Catalyst-Action-Serialize-Data-Serializer and add the appropriate lines to your controller config. Said lines may be: 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ], 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ], 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ], 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ], 1.07 2013-04-11 20:20:00 BST - Don't serialize if a view is explicitly set. - If the controller sets the view in $c->stash->{current_view} or $c->stash->{current_view_instance}, then it is explicitly requesting a certain kind of serialization and C::A::Serialize shouldn't override that. - Remove Storable and FreezeThaw from the list of serialization methods offered by default, and from the docs - they're totally unsafe :/ 1.06 2012-12-11 22:04:00 UTC - Sort list of allowed methods. RT#81825 1.05 2012-07-02 20:13:00 BST - Bugfix get_allowed_methods list: - include HEAD - remove "not_implemented" 1.04 2012-06-30 10:25:00 BST - Bugfix to _dispatch_rest_method 1.03 2012-06-28 00:40:00 BST - Expose _get_allowed_methods to the API (wreis) - Fix default OPTIONS handler: As the ->body is defined, then serialization won't happen and we don't get wrong responses. (wreis) - Add default HEAD handler: auto dispatches to _GET method if it exists (wreis) 1.02 2012-06-05 22:23:00 BST - Fix forwarded REST methods, e.g. foo_GET to be more correctly displayed as a forward in the stats info. - Make public response building methods for errors _unsupported_media_type and _serialize_bad_request which can be extended from an action-role to return custom error response. 1.01 2012-05-29 20:19:00 BST - Add Catalyst::Action::Deserialize::JSON::XS - Fix JSON::XS useage to depend on JSON.pm v2.0, and rely on the fact that can be backed by XS code, by explicitly setting $ENV{'PERL_JSON_BACKEND'} = 2 1.00 2012-04-13 09:31:00 BST - Repack without auto_include to stop Module::Install inlining Test::More without Test::Builder. RT#76524 0.99 2012-02-28 09:09:00 UTC - Repack with new Module::Install to stop depending on an unnecessary ExtUtils::MakeMaker version. 0.98 2012-02-21 11:40:00 UTC - More fixes as per last release. 0.97 2012-02-21 09:58:00 UTC - Fix test with latest Catalyst version which passes _log into requests. 0.96 2012-01-20 11:22:00 UTC - Added fix for RT 63537 (from Gerv) and tests to check it. 0.95 2012-01-04 19:34:00 UTC - Fix regex for JSONP parameter name to be able to include the . character in Catalyst::Action::Serialize::JSONP. RT#73741 - Add optional location parameter to status_accepted handler. RT#73691 (ghenry) 0.94 2011-12-09 08:35:00 UTC - Add 403 Forbidden and 302 Not Found status methods to Catalyst::Controller::REST (Caleb Cushing) 0.93 2011-10-12 11:37:00 America/Chicago - Add a "Callback" serializer/deserializer to allow for more customization in how the REST data is parsed/generated (bphillips) 0.92 2011-10-01 11:04:00 BST - Add a Catalyst::Action::DeserializeMultiPart, allowing one part of a multipart request to be deserialized as the REST data (allowing other parts to be used for file uploads, for example) (bphillips) 0.91 2011-08-04 14:37:21 Europe/Berlin - For the deserialization action class, make the HTTP methods it operates on configurable on a per-action level (plu, rafl). 0.90 2011-02-25 13:56:00 UTC - Remove test which is no longer applicable and fails in the latest Catalyst release. 0.89 2011-01-24 21:57:42 UTC - All classes are now made immutable. (Dave Rolsky) - Added a Catalyst::Action::REST::ForBrowsers class. This will try to dispatch GET requests to a foo_GET_html method before trying foo_GET. (Dave Rolsky) 0.88 2011-01-11 23:07:00 UTC - Fix documentation for overriding Serialize and Deserialize actions in Catalyst::Controller::REST. - Avoid warning with empty response bodies and new Catalyst version (>= 5.80030) - Returning a body of '' is now possible - Catalyst::Action::Serialize acts like Catalyst::Action::RenderView (>= 0.16) by using the has_body predicate in Catalyst::Response (>= 5.80030) 0.87 2010-11-03 19:46:00 UTC - Fix Request class role when used with new Moose and other request class roles. 0.86 2010-09-01 23:14:00 BST - Add rest_serializer_json_options config key useable to set options like relaxed => 1 to be passed to the JSON serializer (Ton Voon) - Make Data::Dumper unserializer safer by using a Safe compartment (Ton Voon) 0.85 2010-05-13 10:09:19 Europe/Berlin - Make Catalyst::Action::Serialize::View return directly rather than serializing a response for 3XX status codes. This stops back-compat breakage from the previous change (in 0.84), whilst also allowing actual data serializers to still handle 3XX. - Fix docs in Catalyst::TraitFor::Request::REST::ForBrowsers. (RT#54983) 0.84 2010-05-06 09:27:56 BST - Revert always using a trait rather than Catalyst::Request::REST to improve debug messages. - Add a status_multiple_choices helper method to the Controller base class. - Allow 3XX responses to be serialized. 0.83 2010-02-08 22:17:12 UTC - Make it possible to deserialize a request with a DELETE method. This probably breaks 'strict' REST guidelines, but is useful for being able to delete multiple resources from a single call by providing a batch delete method. - Remove JSONP from the list of default serializers (RT#54336) Fix MANIFEST (RT#54408) 0.82 2010-02-04 22:31:57 UTC - Integrated Catalyst::Request::REST::ForBrowsers as Catalyst::TraitFor::Request::ForBrowsers. (Dave Rolsky) - Clarified docs so that they encourage the use of the request traits, rather than using Catalyst::Request::REST. (Dave Rolsky) - When Catalyst::Action::REST or Controller::REST automatically add the trait, your request class will no longer end up getting set to Catalyst::Request::REST. Instead, creates an anon class with the appropriate role. (Dave Rolsky) - Shut up log output from the tests. (Dave Rolsky) - Added a $VERSION to every module, mostly to make sure that when people install Catalyst::Request::REST::ForBrowsers, they get the version in this distro. (Dave Rolsky) - Change Catalyst::Action::Serialize, Catalyst::Action::Deserialize and Catalyst::Action::SerializeBase to be more Moose like. - Fix JSON and JSON::XS to encode_blessed. (fREW) - Fix Catalyst::Action::Serialize to use objects instead of classes. (fREW) - Fix doc nits. (RT#53780) 0.81 2010-01-14 20:56:00 UTC - Add a JSONP serialization type. 0.80 2009-12-19 14:54:00 UTC - Convert all classes to Moose - Change Catalyst::Request::REST to be a mostly empty class, with all the functionality in Catalyst::TraitFor::Request::REST - Simplify _get_allowed_methods method (aristotle) - Rework serializer return so that serializers throw an exception in the case of issues serializing the data (hobbs). 0.79 2009-12-11 01:08:00 UTC - Cope with invalid (missing required q parameter) header like: application/json; charset="utf-8" - Fix documentation to not mention deprecated things and generally be in better style. - Make author information consistant and only in one module. 0.78 2009-09-28 15:01:03 BST - Require Moose for the tests (RT#50066). 0.77 2009-08-27 02:21:09 BST - Allow dispatching to Catalyst Actions, for use with ActionClasses etc - fREW - Fix test if CATALYST_DEBUG environment variable is set 0.76 2009-08-21 21:20:52 BST - Added two new status response helpers (202 no content and 410 gone), and tests - Franck Cuny 0.75 2009-08-17 14:07:41 BST - Fix optional test failures in catalyst-action-serialize-accept.t - Added a serializer for JSON::XS - Made test independent of YAML::Syck bugs (dandv) 0.74 2009-07-22 23:49:16 BST (t0m) - Switch from NEXT to MRO::Compat (agladdish). - Add display of additional REST actions in the stats, and also fix a warning in Catalyst 5.80 when you forward to another action from inside an action_FOO method (as it was confusing the stats). - POD fixes - Catalyst::Action::REST no longer @ISA Catalyst or Catalyst::Controller. - Change constructor to call next::method instead of SUPER:: - Change method used to find the application class to be more correct 0.73 2009-06-27 20:20:09 America/New_York (hdp) - Packaging fixes 0.72 2009-06-25 14:52:29 America/New_York (hdp) - Refresh Module::Install 0.71 2009-03-28 09:16:09 America/Los_Angeles (hdp) - Fix RT#44641, missing documented 'end' action 0.70 2009-03-27 23:21:17 America/Los_Angeles (hdp) - Tests that use JSON were either not checking for the version or checking in a - way that was a syntax error. 0.69 2009-03-26 14:16:03 America/Los_Angeles (hdp) - Fix RT#32342, deprecated config loses default map (hdp) - Fix broken insertion of Catalyst::Request::REST for Action::REST (jshirley) 0.68 2009-03-25 22:33:38 America/Los_Angeles (hdp) - Remove prompt for ancient and deprecated Data::Denter from Makefile.PL - Remove Data::Dump, which was entirely unused - Stop tests from dying with Catalyst 5.80 0.67 2009-03-25 21:59:59 America/Los_Angeles (hdp) - (no changes from 0.67_01) 0.67_01 2009-03-25 09:36:00 America/Los_Angeles (hdp) - Fix RT#43840, improper app-level config handling - Fix RT#42859, 'wrong' Catalyst dependency - Fix RT#42025, stepping on custom request classes 0.65 2008-08-20 10:42:00 America/Los_Angeles (jshirley) - Fully revamped tests to work without any JSON support - Final removal of JSON::Syck - Special thanks to jgoulah for helping test this release 0.64 2008-08-13 08:55:00 America/Los_Angeles (jshirley) - New dist to fix issue with Module::Install 0.63 2008-07-09 11:16:00 America/Los_Angeles (jshirley) - Changing from JSON::Syck to JSON/JSON::XS - Refactored tests to be more applicable to current state of affairs 0.62 2008-07-02 07:53:00 America/Los_Angeles (jshirley) - Reshipping with current Module::Install included due to error reports about failed installs 0.61 2008-06-30 12:28:00 America/Los_Angeles (jshirley) - Support official application/json and carp about text/x-json - Accepted patch from Luke Saunders for processing all accepted content types 0.60 2008-01-03 17:23:58 America/Los_Angeles (adam) - Updated my contact information. - Fixed RT#30498 - REST controller references Catalyst without loading it first. - Fixed RT#32042 - Import of Params::Validate :all plays badly with subclasses that have their own validate() - Fixed RT#30456 - Debug messages print even with debugging disabled - Fixed an issue where YAML::Syck versions 0.92 require $c->request->body to be stringified - Updated the configuration specifiers to operate more in line with the way Catalyst expects. Most notably component based configuration through "Controller::RestClass" now works. "serialize" at the top level simply is suggested defaults that all REST classes inherit. - Fixed 'default' serializer to set a valid Content-Type: header. Fixes RT ticket 27949. Note that behavior has changed -- the default serializer must now be specified as a content-type, not as a plugin name. (dmo@roaringpenguin.com) 0.41 2007-05-24 14:01:06 America/Los_Angeles (adam) - Moved a bogus $self->class to $c->component($self->class) 0.40 2007-03-09 14:13:29 America/Los_Angeles (adam) - Refactored the Content-Type negotiation to live in Catalyst::Request::REST. (drolsky) - Added some useful debugging. (drolsky) - Added a View serializer/deserializer, which simply calls the correct - Catalyst view. ('text/html' => [ 'View', 'TT' ]) (claco, adam) 0.31 2006-12-06 00:45:02 America/Los_Angeles (adam) - Fixed a bug where we would report a blank content-type negotiation. - Added Data::Dump as a dependency. - Made the YAML::HTML view automatically append content-type=text/html on the resulting URLs. 0.30 2006-12-03 12:24:16 America/Los_Angeles (adam) - Updated the Makefile to support optional installation of the different Serialization formats. - Renamed some of the test cases, since the execution order doesn't matter. - Fixed things so that not having a Serialization module returns 415. - Fixed things so that failure to Deserialize sends the proper status. - Refactored the Plugin loading to Catalyst::Action::SerializeBase. - Updated the Documentation. - Added a whole raft of serializers. (JSON, all the Data::Serializer supported ones, and XML::Simple) - Added test cases. - Refactored the Catalyst::Action::REST dispatch, so that the default method is called before any _METHOD handlers. In addition, moved the 405 Not Implemented handler to be foo_not_implemented, instead of the default sub. (daisuke++ pointed out the inconsistency and provided a patch, and I added the foo_not_implemented support) - Added in automated OPTIONS handler, which constructs the allow header for you, just like the 405 handler. Can be overridden with a normal _METHOD sub. - Refactored Test::Rest, so that it uses closures to create the very similar $test->method() subs. - Added tests for Catalyst::Action::REST. 0.2 2006-11-30 17:14:51 America/Los_Angeles (adam) - Added documentation patch from Daisuke Maki (daisuke@endeworks.jp) - Added dependency patch from Daisuke Maki (daisuke@endeworks.jp) 0.1 2006-11-19 16:24:20 America/Los_Angeles (adam) - Added status_accepted (Code 202) - Added a first pass at documentation. - Added in Test Suite - Created Catalyst::Action::Serialize and Catalyst::Action::Deserialize - Added Data::Serializer actions - Added status_created helper method - Added more status_ helpers - Converted error helpers to return an object instead of plain-text. It's a more consistent model than a text/plain error message. - Added logging to 4xx status handlers t000755000765000024 013211532667 16557 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21isa.t100644000765000024 61513211532667 17642 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use FindBin qw/$Bin/; use lib "$Bin/lib"; use Test::More; use Test::Catalyst::Action::REST; my $controller = Test::Catalyst::Action::REST->controller('Root'); ok $controller; my $action = $controller->action_for('test'); ok $action; isa_ok($action, 'Catalyst::Action::REST'); ok(!$action->isa('Catalyst')); ok(!$action->isa('Catalyst::Controller')); done_testing; LICENSE100644000765000024 4365213211532667 17513 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21This software is copyright (c) 2017 by Tomas Doran. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2017 by Tomas Doran. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2017 by Tomas Doran. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End cpanfile100644000765000024 122513211532667 20160 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21requires 'Catalyst::Runtime' => '5.80030' ; requires 'Class::Inspector' => '1.13' ; requires 'MRO::Compat' => '0.10' ; requires 'Module::Pluggable::Object' => undef ; requires 'Moose' => '1.03'; requires 'Params::Validate' => '0.76' ; requires 'URI::Find' => undef ; requires 'namespace::autoclean'; requires 'JSON::MaybeXS'; on test => sub { requires 'Test::More' => '0.88'; requires 'Test::Requires'; requires 'LWP::UserAgent' => '5.00' ; }; suggests 'Config::General'; suggests 'Data::Taxi'; suggests 'FreezeThaw'; suggests 'HTML::Parser'; suggests 'Cpanel::JSON::XS'; suggests 'PHP::Serialization'; suggests 'XML::Simple'; suggests 'YAML::Syck'; dist.ini100644000765000024 65013211532667 20101 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21name = Catalyst-Action-REST author = Tomas Doran license = Perl_5 copyright_holder = Tomas Doran version = 1.21 [NextRelease] [@Git] [@Basic] [GithubMeta] [MetaResources] bugtracker.web = https://rt.cpan.org/Dist/Display.html?Name=Catalyst-Action-REST x_authority = cpan:BOBTFISH [MetaJSON] [PkgVersion] [ReadmeFromPod] [PodSyntaxTests] [Prereqs::FromCPANfile] json.t100644000765000024 300213211532667 20050 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use JSON::MaybeXS; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib"); use Test::Rest; use utf8; use_ok 'Catalyst::Test', 'Test::Serialize'; my $json = JSON->new->utf8; # The text/x-json should throw a warning for ('text/x-json', 'application/json') { my $t = Test::Rest->new('content_type' => $_); my $monkey_template = { monkey => 'likes chicken!', }; my $mres = request($t->get(url => '/monkey_get')); ok( $mres->is_success, 'GET the monkey succeeded' ); is_deeply($json->decode($mres->content), $monkey_template, "GET returned the right data"); my $post_data = { 'sushi' => 'is good for monkey', 'chicken' => ' 佐藤 純', }; my $mres_post = request($t->post(url => '/monkey_put', data => $json->encode($post_data))); ok( $mres_post->is_success, "POST to the monkey succeeded"); my $exp = "is good for monkey 佐藤 純"; utf8::encode($exp); is_deeply($mres_post->content, $exp, "POST data matches"); } { my $t = Test::Rest->new('content_type' => 'application/json'); my $json_data = '{ "sushi":"is good for monkey", }'; my $mres_post = request($t->post(url => '/monkey_put', data => $json_data)); ok( ! $mres_post->is_success, "Got expected failed status due to invalid JSON" ); my $relaxed_post = request( $t->post(url => "/monkey_json_put", data => $json_data)); ok( $relaxed_post->is_success, "Got success due to setting relaxed JSON input" ); } 1; done_testing; view.t100644000765000024 153213211532667 20057 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ( "$FindBin::Bin/lib", "$FindBin::Bin/../lib" ); use Test::Rest; use_ok 'Catalyst::Test', 'Test::Serialize'; my $t = Test::Rest->new( 'content_type' => 'text/view' ); my $monkey_template = "I am a simple view"; my $mres = request( $t->get( url => '/monkey_get' ) ); ok( $mres->is_success, 'GET the monkey succeeded' ); is( $mres->content, $monkey_template, "GET returned the right data" ); my $mres_post = request( $t->post( url => '/monkey_put', data => 1 ) ); ok( $mres_post->is_success, "POST to the monkey passed." ); my $t2 = Test::Rest->new( 'content_type' => 'text/explodingview' ); my $res = request( $t2->get( url => '/monkey_get' ) ); ok (! $res->is_success, 'View errors result in failure'); like( $res->content, qr/don't know how/, 'The error looks okay'); 1; done_testing; yaml.t100644000765000024 204513211532667 20047 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib"); use Test::Rest; use_ok 'Catalyst::Test', 'Test::Serialize'; # Should use the default serializer, YAML my $t = Test::Rest->new('content_type' => 'text/x-yaml'); my $has_serializer = eval "require YAML::Syck"; SKIP: { skip "YAML::Syck not available", 4, unless $has_serializer; # We should use the default serializer, YAML my $monkey_template = { monkey => 'likes chicken!', }; my $mres = request($t->get(url => '/monkey_get')); ok( $mres->is_success, 'GET the monkey succeeded' ); is_deeply(YAML::Syck::Load($mres->content), $monkey_template, "GET returned the right data"); my $post_data = { 'sushi' => 'is good for monkey', }; my $mres_post = request($t->post(url => '/monkey_put', data => YAML::Syck::Dump($post_data))); ok( $mres_post->is_success, "POST to the monkey succeeded"); is_deeply($mres_post->content, "is good for monkey", "POST data matches"); }; 1; done_testing; xt000755000765000024 013211532667 16747 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21pod.t100644000765000024 12413211532667 20033 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/xtuse strict; use warnings; use Test::More; use Test::Pod 1.14; all_pod_files_ok(); META.yml100644000765000024 155213211532667 17730 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21--- abstract: 'Automated REST Method Dispatching' author: - 'Tomas Doran ' build_requires: LWP::UserAgent: '5.00' Test::More: '0.88' Test::Requires: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.009, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Catalyst-Action-REST requires: Catalyst::Runtime: '5.80030' Class::Inspector: '1.13' JSON::MaybeXS: '0' MRO::Compat: '0.10' Module::Pluggable::Object: '0' Moose: '1.03' Params::Validate: '0.76' URI::Find: '0' namespace::autoclean: '0' resources: Authority: cpan:BOBTFISH bugtracker: https://rt.cpan.org/Dist/Display.html?Name=Catalyst-Action-REST version: '1.21' x_serialization_backend: 'YAML::Tiny version 1.70' MANIFEST100644000765000024 517213211532667 17612 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.009. Changes LICENSE MANIFEST META.json META.yml Makefile.PL README TODO cpanfile dist.ini lib/Catalyst/Action/Deserialize.pm lib/Catalyst/Action/Deserialize/Callback.pm lib/Catalyst/Action/Deserialize/JSON.pm lib/Catalyst/Action/Deserialize/JSON/XS.pm lib/Catalyst/Action/Deserialize/View.pm lib/Catalyst/Action/Deserialize/XML/Simple.pm lib/Catalyst/Action/Deserialize/YAML.pm lib/Catalyst/Action/DeserializeMultiPart.pm lib/Catalyst/Action/REST.pm lib/Catalyst/Action/REST/ForBrowsers.pm lib/Catalyst/Action/Serialize.pm lib/Catalyst/Action/Serialize/Callback.pm lib/Catalyst/Action/Serialize/JSON.pm lib/Catalyst/Action/Serialize/JSON/XS.pm lib/Catalyst/Action/Serialize/JSONP.pm lib/Catalyst/Action/Serialize/View.pm lib/Catalyst/Action/Serialize/XML/Simple.pm lib/Catalyst/Action/Serialize/YAML.pm lib/Catalyst/Action/Serialize/YAML/HTML.pm lib/Catalyst/Action/SerializeBase.pm lib/Catalyst/Controller/REST.pm lib/Catalyst/Request/REST.pm lib/Catalyst/Request/REST/ForBrowsers.pm lib/Catalyst/TraitFor/Request/REST.pm lib/Catalyst/TraitFor/Request/REST/ForBrowsers.pm t/author-pod-syntax.t t/broken/Catalyst/Action/Deserialize/Broken.pm t/broken/Catalyst/Action/Serialize/Broken.pm t/callback.t t/catalyst-action-deserialize-multipart.t t/catalyst-action-deserialize.t t/catalyst-action-rest-action-dispatch-for-browsers.t t/catalyst-action-rest-action-dispatch.t t/catalyst-action-rest.t t/catalyst-action-serialize-accept.t t/catalyst-action-serialize-query.t t/catalyst-action-serialize.t t/catalyst-controller-rest.t t/catalyst-request-rest-custom-nonrest-request-class.t t/catalyst-request-rest-custom-rest-request-class.t t/catalyst-traitfor-request-rest-forbrowsers.t t/catalyst-traitfor-request-rest.t t/isa.t t/json.t t/jsonp.t t/lib/Test/Action/Class.pm t/lib/Test/Action/Class/Sub.pm t/lib/Test/Catalyst/Action/REST.pm t/lib/Test/Catalyst/Action/REST/Controller/Actions.pm t/lib/Test/Catalyst/Action/REST/Controller/ActionsForBrowsers.pm t/lib/Test/Catalyst/Action/REST/Controller/Deserialize.pm t/lib/Test/Catalyst/Action/REST/Controller/DeserializeMultiPart.pm t/lib/Test/Catalyst/Action/REST/Controller/Override.pm t/lib/Test/Catalyst/Action/REST/Controller/REST.pm t/lib/Test/Catalyst/Action/REST/Controller/Root.pm t/lib/Test/Catalyst/Action/REST/Controller/Serialize.pm t/lib/Test/Catalyst/Log.pm t/lib/Test/Rest.pm t/lib/Test/Serialize.pm t/lib/Test/Serialize/Controller/JSON.pm t/lib/Test/Serialize/Controller/REST.pm t/lib/Test/Serialize/View/Awful.pm t/lib/Test/Serialize/View/Simple.pm t/view.t t/xml-simple.t t/yaml-html.t t/yaml.t xt/pod-spell.t xt/pod.t xt/version-numbers.t jsonp.t100644000765000024 142113211532667 20233 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use JSON::MaybeXS; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib"); use Test::Rest; use utf8; use_ok 'Catalyst::Test', 'Test::Serialize', 'Catalyst::Action::Serialize::JSON'; my $json = JSON->new->utf8; for ('text/javascript','application/x-javascript','application/javascript') { my $t = Test::Rest->new('content_type' => $_); my $monkey_template = { monkey => 'likes chicken!' }; my $mres = request($t->get(url => '/monkey_get?callback=My_Animal.omnivore')); ok( $mres->is_success, 'GET the monkey succeeded' ); my ($json_param) = $mres->content =~ /^My_Animal.omnivore\((.*)?\);$/; is_deeply($json->decode($json_param), $monkey_template, "GET returned the right data"); } 1; done_testing; META.json100644000765000024 345313211532667 20102 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21{ "abstract" : "Automated REST Method Dispatching", "author" : [ "Tomas Doran " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.009, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Catalyst-Action-REST", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "Catalyst::Runtime" : "5.80030", "Class::Inspector" : "1.13", "JSON::MaybeXS" : "0", "MRO::Compat" : "0.10", "Module::Pluggable::Object" : "0", "Moose" : "1.03", "Params::Validate" : "0.76", "URI::Find" : "0", "namespace::autoclean" : "0" }, "suggests" : { "Config::General" : "0", "Cpanel::JSON::XS" : "0", "Data::Taxi" : "0", "FreezeThaw" : "0", "HTML::Parser" : "0", "PHP::Serialization" : "0", "XML::Simple" : "0", "YAML::Syck" : "0" } }, "test" : { "requires" : { "LWP::UserAgent" : "5.00", "Test::More" : "0.88", "Test::Requires" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://rt.cpan.org/Dist/Display.html?Name=Catalyst-Action-REST" }, "x_authority" : "cpan:BOBTFISH" }, "version" : "1.21", "x_serialization_backend" : "Cpanel::JSON::XS version 3.0233" } Makefile.PL100644000765000024 312713211532667 20431 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.009. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Automated REST Method Dispatching", "AUTHOR" => "Tomas Doran ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Catalyst-Action-REST", "LICENSE" => "perl", "NAME" => "Catalyst::Action::REST", "PREREQ_PM" => { "Catalyst::Runtime" => "5.80030", "Class::Inspector" => "1.13", "JSON::MaybeXS" => 0, "MRO::Compat" => "0.10", "Module::Pluggable::Object" => 0, "Moose" => "1.03", "Params::Validate" => "0.76", "URI::Find" => 0, "namespace::autoclean" => 0 }, "TEST_REQUIRES" => { "LWP::UserAgent" => "5.00", "Test::More" => "0.88", "Test::Requires" => 0 }, "VERSION" => "1.21", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Catalyst::Runtime" => "5.80030", "Class::Inspector" => "1.13", "JSON::MaybeXS" => 0, "LWP::UserAgent" => "5.00", "MRO::Compat" => "0.10", "Module::Pluggable::Object" => 0, "Moose" => "1.03", "Params::Validate" => "0.76", "Test::More" => "0.88", "Test::Requires" => 0, "URI::Find" => 0, "namespace::autoclean" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); callback.t100644000765000024 163213211532667 20642 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib"); use Test::Rest; use_ok 'Catalyst::Test', 'Test::Serialize'; my $t = Test::Rest->new('content_type' => 'text/my-csv'); my $has_serializer = eval "require XML::Simple"; my $monkey_template = { monkey => 'likes chicken!', }; my $mres = request($t->get(url => '/monkey_get')); ok( $mres->is_success, 'GET the monkey succeeded' ); my $output = { split( /,/, $mres->content ) }; is_deeply($output, $monkey_template, "GET returned the right data"); my $post_data = { 'sushi' => 'is good for monkey', }; my $mres_post = request( $t->post( url => '/monkey_put', data => join( ',', %$post_data ) ) ); ok( $mres_post->is_success, "POST to the monkey succeeded"); is_deeply($mres_post->content, "is good for monkey", "POST data matches"); 1; done_testing; yaml-html.t100644000765000024 247713211532667 21022 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Test::Requires qw(YAML::Syck); use YAML::Syck; use FindBin; use lib ( "$FindBin::Bin/lib", "$FindBin::Bin/../lib" ); use Test::Rest; BEGIN { use_ok 'Catalyst::Test', 'Test::Serialize'; } my $has_serializer = eval "require YAML::Syck"; SKIP: { skip "YAML::Syck not available", 3, unless $has_serializer; my $t = Test::Rest->new( 'content_type' => 'text/html' ); my $monkey_template = "Test::Serialize
--- \nmonkey: likes chicken!\n
"; my $mres = request( $t->get( url => '/monkey_get' ) ); ok( $mres->is_success, 'GET the monkey succeeded' ); is( $mres->content, $monkey_template, "GET returned the right data" ); my $post_data = { 'sushi' => 'is good for monkey', }; my $mres_post = request( $t->post( url => '/monkey_put', data => Dump($post_data) ) ); ok( $mres_post->is_error, "POST to the monkey failed; no deserializer." ); # xss test - RT 63537 my $xss_template = "Test::Serialize
--- \nmonkey: likes chicken > sushi!\n
"; my $xres = request( $t->get( url => '/xss_get' ) ); ok( $xres->is_success, 'GET the xss succeeded' ); is( $xres->content, $xss_template, "GET returned the right data" ); } 1; done_testing; xml-simple.t100644000765000024 205413211532667 21174 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib"); use Test::Rest; use_ok 'Catalyst::Test', 'Test::Serialize'; my $t = Test::Rest->new('content_type' => 'text/xml'); my $has_serializer = eval "require XML::Simple"; SKIP: { skip "XML::Simple not available", 4, unless $has_serializer; my $xs = XML::Simple->new('ForceArray' => 0); my $monkey_template = { monkey => 'likes chicken!', }; my $mres = request($t->get(url => '/monkey_get')); ok( $mres->is_success, 'GET the monkey succeeded' ); my $output = $xs->XMLin($mres->content); is_deeply($xs->XMLin($mres->content)->{'data'}, $monkey_template, "GET returned the right data"); my $post_data = { 'sushi' => 'is good for monkey', }; my $mres_post = request($t->post(url => '/monkey_put', data => $xs->XMLout($post_data))); ok( $mres_post->is_success, "POST to the monkey succeeded"); is_deeply($mres_post->content, "is good for monkey", "POST data matches"); }; 1; done_testing; pod-spell.t100644000765000024 171513211532667 21177 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/xtuse strict; use warnings; use Test::More; eval "use Test::Spelling"; plan skip_all => "Test::Spelling required for testing POD coverage" if $@; my @stopwords; for () { chomp; push @stopwords, $_ unless /\A (?: \# | \s* \z)/msx; # skip comments, whitespace } add_stopwords(@stopwords); set_spell_cmd('aspell list -l en'); # This prevents a weird segfault from the aspell command - see # https://bugs.launchpad.net/ubuntu/+source/aspell/+bug/71322 local $ENV{LC_ALL} = 'C'; all_pod_files_spelling_ok(); __DATA__ Reis ActionRole de Gerv Newell Wiki json APIs ActionClass Daisuke Daisuke Deserialize Deserializer Deserializing Doran Goulah JSON Laco Maki Maki Marchex Multipart Pearcey Rolsky RESTful RESTful SERIALIZERS TT Wikipedia XHR XMLHttpRequest YAML conf deserialization deserialize deserialized deserializing fREW html http javascript jrockway mst multipart namespace plugins request's serializer thusly wildcard subclasses Axel URI Test000755000765000024 013211532667 20244 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/libRest.pm100644000765000024 356013211532667 21663 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Testpackage Test::Rest; use Moose; use namespace::autoclean; use LWP::UserAgent; use Params::Validate qw(:all); sub new { my $self = shift; my %p = validate( @_, { content_type => { type => SCALAR }, }, ); my $ref = { 'ua' => LWP::UserAgent->new, 'content_type' => $p{'content_type'}, }; bless $ref, $self; } { my @non_data_methods = qw(HEAD GET DELETE OPTIONS); foreach my $method (@non_data_methods) { no strict 'refs'; my $sub = lc($method); *$sub = sub { my $self = shift; my %p = validate( @_, { url => { type => SCALAR }, headers => { type => HASHREF, default => {} }, }, ); my $req = HTTP::Request->new( "$method" => $p{'url'} ); $req->header( $_ => $p{headers}{$_} ) for keys %{ $p{headers} }; $req->content_type( $self->{'content_type'} ); return $req; }; } my @data_methods = qw(PUT POST); foreach my $method (@data_methods) { no strict 'refs'; my $sub = lc($method); *{$sub} = sub { my $self = shift; my %p = validate( @_, { url => { type => SCALAR }, data => 1, headers => { type => HASHREF, default => {} }, }, ); my $req = HTTP::Request->new( "$method" => $p{'url'} ); $req->header( $_ => $p{headers}{$_} ) for keys %{ $p{headers} }; $req->content_type( $self->{'content_type'} ); $req->content_length( do { use bytes; length( $p{'data'} ) } ); $req->content( $p{'data'} ); return $req; }; } } 1; version-numbers.t100644000765000024 123013211532667 22426 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/xtuse strict; use warnings; use FindBin qw/$Bin/; use File::Spec; use File::Find::Rule; use Module::Info; use Test::More; my %versions; for my $pm_file ( File::Find::Rule->file->name( qr/\.pm$/ )->in(File::Spec->catdir($Bin, '..', 'lib') ) ) { my $mod = Module::Info->new_from_file($pm_file); ( my $stripped_file = $pm_file ) =~ s{.*/lib/}{}; $versions{$stripped_file} = $mod->version; } my $ver = delete $versions{'Catalyst/Action/REST.pm'}; ok $ver; ok scalar(keys %versions); for my $module ( sort keys %versions ) { is( $versions{$module}, $ver, "version for $module is the same as in Catalyst/Action/REST.pm" ); } done_testing; author-pod-syntax.t100644000765000024 45413211532667 22475 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); Serialize.pm100644000765000024 46713211532667 22660 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Testpackage Test::Serialize; use FindBin; use lib ("$FindBin::Bin/../lib"); use Moose; use namespace::autoclean; use Catalyst::Runtime '5.70'; use Catalyst; use Test::Catalyst::Log; __PACKAGE__->config( name => 'Test::Serialize', ); __PACKAGE__->setup; __PACKAGE__->log( Test::Catalyst::Log->new ); 1; catalyst-action-rest.t100644000765000024 520513211532667 23160 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ( "$FindBin::Bin/lib", "$FindBin::Bin/../lib" ); use Test::Rest; # Should use the default serializer, YAML my $t = Test::Rest->new( 'content_type' => 'text/plain' ); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; foreach my $method (qw(GET DELETE POST PUT OPTIONS)) { my $run_method = lc($method); my $result = "something $method"; my $res; if ( grep /$method/, qw(GET DELETE OPTIONS) ) { $res = request( $t->$run_method( url => '/test' ) ); } else { $res = request( $t->$run_method( url => '/test', data => '', ) ); } ok( $res->is_success, "$method request succeeded" ); is( $res->content, "something $method", "$method request had proper response" ); } my $head_res = request( $t->head(url => '/test') ); ok($head_res->is_success, 'HEAD request succeeded') or diag($head_res->code); ok(!$head_res->content, 'HEAD request had proper response'); $head_res = request( $t->head(url => '/actions/yet_other_test') ); ok($head_res->code == 405, 'HEAD request succeeded') or diag($head_res->code); my $fail_res = request( $t->delete( url => '/notreally' ) ); is( $fail_res->code, 405, "Request to bad method gets 405 Not Implemented" ); is( $fail_res->header('allow'), "GET, HEAD", "405 allow header properly set." ); my $options_res = request( $t->options( url => '/notreally' ) ); is( $options_res->code, 200, "OPTIONS request handler succeeded" ); is( $options_res->header('allow'), "GET, HEAD", "OPTIONS request allow header properly set." ); my $opts_res = request( $t->options( url => '/rest/opts' ) ); is( $opts_res->code, 200, "OPTIONS request handler succeeded" ); is( $opts_res->header('allow'), "GET, HEAD", "OPTIONS request allow header properly set." ); is($opts_res->content, q{}, 'should have no body'); $opts_res = request( $t->options( url => '/rest/opts', headers => { Accept => 'application/json' }, ) ); is( $opts_res->code, 200, "OPTIONS request handler succeeded" ); is( $opts_res->header('allow'), "GET, HEAD", "OPTIONS request allow header properly set." ); is($opts_res->content, q{}, 'should have no body'); my $modified_res = request( $t->get( url => '/not_modified' ) ); is( $modified_res->code, 304, "Not Modified request handler succeeded" ); my $ni_res = request( $t->delete( url => '/not_implemented' ) ); is( $ni_res->code, 200, "Custom not_implemented handler succeeded" ); is( $ni_res->content, "Not Implemented Handler", "not_implemented handler had proper response" ); 1; done_testing; Action000755000765000024 013211532667 21461 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/TestClass.pm100644000765000024 32413211532667 23203 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Actionpackage Test::Action::Class; use Moose; extends 'Catalyst::Action'; before execute => sub { my ($self, $controller, $c, @args) = @_; $c->response->header( 'Using-Action' => 'STATION' ); }; no Moose; 1; Catalyst000755000765000024 013211532667 22030 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/TestLog.pm100644000765000024 41713211532667 23231 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalystpackage Test::Catalyst::Log; use strict; use warnings; sub new { bless {}, __PACKAGE__; } sub is_debug { 0 } sub debug { } sub is_info { 0 } sub info { } sub is_warn { 0 } sub warn : method { } sub is_error { 0 } sub error { } sub is_fatal { 0 } sub fatal { } 1; Action000755000765000024 013211532667 22063 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/CatalystREST.pm100644000765000024 1716513211532667 23370 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Actionpackage Catalyst::Action::REST; $Catalyst::Action::REST::VERSION = '1.21'; use utf8; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; use Class::Inspector; use Catalyst::Request::REST; use Catalyst::Controller::REST; BEGIN { require 5.008001; } sub BUILDARGS { my $class = shift; my $config = shift; Catalyst::Request::REST->_insert_self_into( $config->{class} ); return $class->SUPER::BUILDARGS($config, @_); } =encoding utf-8 =head1 NAME Catalyst::Action::REST - Automated REST Method Dispatching =head1 SYNOPSIS sub foo :Local :ActionClass('REST') { ... do setup for HTTP method specific handlers ... } sub foo_GET { ... do something for GET requests ... } # alternatively use an Action sub foo_PUT : Action { ... do something for PUT requests ... } =head1 DESCRIPTION This Action handles doing automatic method dispatching for REST requests. It takes a normal Catalyst action, and changes the dispatch to append an underscore and method name. First it will try dispatching to an action with the generated name, and failing that it will try to dispatch to a regular method. For example, in the synopsis above, calling GET on "/foo" would result in the foo_GET method being dispatched. If a method is requested that is not implemented, this action will return a status 405 (Method Not Found). It will populate the "Allow" header with the list of implemented request methods. You can override this behavior by implementing a custom 405 handler like so: sub foo_not_implemented { ... handle not implemented methods ... } If you do not provide an _OPTIONS subroutine, we will automatically respond with a 200 OK. The "Allow" header will be populated with the list of implemented request methods. If you do not provide an _HEAD either, we will auto dispatch to the _GET one in case it exists. It is likely that you really want to look at L, which brings this class together with automatic Serialization of requests and responses. When you use this module, it adds the L role to your request class. =head1 METHODS =over 4 =item dispatch This method overrides the default dispatch mechanism to the re-dispatching mechanism described above. =cut sub dispatch { my $self = shift; my $c = shift; my $rest_method = $self->name . "_" . uc( $c->request->method ); return $self->_dispatch_rest_method( $c, $rest_method ); } sub _dispatch_rest_method { my $self = shift; my $c = shift; my $rest_method = shift; my $req = $c->request; my $controller = $c->component( $self->class ); my ($code, $name); # Execute normal 'foo' action. $c->execute( $self->class, $self, @{ $req->args } ); # Common case, for foo_GET etc if ( $code = $controller->action_for($rest_method) ) { return $c->forward( $code, $req->args ); # Forward to foo_GET if it's an action } elsif ($code = $controller->can($rest_method)) { $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below. } # Generic handling for foo_* if (!$code) { my $code_action = { OPTIONS => sub { $name = $rest_method; $code = sub { $self->_return_options($self->name, @_) }; }, HEAD => sub { $rest_method =~ s{_HEAD$}{_GET}i; $self->_dispatch_rest_method($c, $rest_method); }, default => sub { # Otherwise, not implemented. $name = $self->name . "_not_implemented"; $code = $controller->can($name) # User method # Generic not implemented || sub { $self->_return_not_implemented($self->name, @_) }; }, }; my ( $http_method, $action_name ) = ( $rest_method, $self->name ); $http_method =~ s{\Q$action_name\E\_}{}; my $respond = ($code_action->{$http_method} || $code_action->{'default'})->(); return $respond unless $name; } # localise stuff so we can dispatch the action 'as normal, but get # different stats shown, and different code run. # Also get the full path for the action, and make it look like a forward local $self->{code} = $code; my @name = split m{/}, $self->reverse; $name[-1] = $name; local $self->{reverse} = "-> " . join('/', @name); $c->execute( $self->class, $self, @{ $req->args } ); } sub get_allowed_methods { my ( $self, $controller, $c, $name ) = @_; my $class = ref($controller) ? ref($controller) : $controller; my $methods = { map { /^$name\_(.+)$/ ? ( $1 => 1 ) : () } @{ Class::Inspector->methods($class) } }; $methods->{'HEAD'} = 1 if $methods->{'GET'}; delete $methods->{'not_implemented'}; return sort keys %$methods; }; sub _return_options { my ( $self, $method_name, $controller, $c) = @_; my @allowed = $self->get_allowed_methods($controller, $c, $method_name); $c->response->content_type('text/plain'); $c->response->status(200); $c->response->header( 'Allow' => \@allowed ); $c->response->body(q{}); } sub _return_not_implemented { my ( $self, $method_name, $controller, $c ) = @_; my @allowed = $self->get_allowed_methods($controller, $c, $method_name); $c->response->content_type('text/plain'); $c->response->status(405); $c->response->header( 'Allow' => \@allowed ); $c->response->body( "Method " . $c->request->method . " not implemented for " . $c->uri_for( $method_name ) ); } __PACKAGE__->meta->make_immutable; 1; =back =head1 SEE ALSO You likely want to look at L, which implements a sensible set of defaults for a controller doing REST. This class automatically adds the L role to your request class. If you're writing a web application which provides RESTful responses and still needs to accommodate web browsers, you may prefer to use L instead. L, L =head1 TROUBLESHOOTING =over 4 =item Q: I'm getting a "415 Unsupported Media Type" error. What gives?! A: Most likely, you haven't set Content-type equal to "application/json", or one of the accepted return formats. You can do this by setting it in your query accepted return formats. You can do this by setting it in your query string thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >> B Apache will refuse %2F unless configured otherwise. Make sure C is in your httpd.conf file in order for this to run smoothly. =back =head1 AUTHOR Adam Jacob Eadam@stalecoffee.orgE, with lots of help from mst and jrockway Marchex, Inc. paid me while I developed this module. (L) =head1 CONTRIBUTORS Tomas Doran (t0m) Ebobtfish@bobtfish.netE John Goulah Christopher Laco Daisuke Maki Edaisuke@endeworks.jpE Hans Dieter Pearcey Brian Phillips Ebphillips@cpan.orgE Dave Rolsky Eautarch@urth.orgE Luke Saunders Arthur Axel "fREW" Schmidt Efrioux@gmail.comE J. Shirley Ejshirley@gmail.comE Gavin Henry Eghenry@surevoip.co.ukE Gerv http://www.gerv.net/ Colin Newell Wallace Reis Ewreis@cpan.orgE André Walker (andrewalker) =head1 COPYRIGHT Copyright (c) 2006-2015 the above named AUTHOR and CONTRIBUTORS =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut Request000755000765000024 013211532667 22276 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/CatalystREST.pm100644000765000024 444413211532667 23557 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Requestpackage Catalyst::Request::REST; $Catalyst::Request::REST::VERSION = '1.21'; use Moose; use Catalyst::Utils; use namespace::autoclean; extends 'Catalyst::Request'; with 'Catalyst::TraitFor::Request::REST'; # Please don't take this as a recommended way to do things. # The code below is grotty, badly factored and mostly here for back # compat.. sub _insert_self_into { my ($class, $app_class ) = @_; # the fallback to $app_class is for the (rare and deprecated) case when # people are defining actions in MyApp.pm instead of in a controller. my $app = (blessed($app_class) && $app_class->can('_application')) ? $app_class->_application : Catalyst::Utils::class2appclass( $app_class ) || $app_class; my $req_class = $app->request_class; return if $req_class->isa($class); my $req_class_meta = Moose->init_meta( for_class => $req_class ); my $role = $class->_related_role; return if $req_class_meta->does_role($role); if ($req_class eq 'Catalyst::Request') { $app->request_class($class); } else { my $meta = Moose::Meta::Class->create_anon_class( superclasses => [$req_class], roles => [$role], cache => 1 ); $meta->_add_meta_method('meta'); $app->request_class($meta->name); } } sub _related_role { 'Catalyst::TraitFor::Request::REST' } __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME Catalyst::Request::REST - A REST-y subclass of Catalyst::Request =head1 SYNOPSIS if ( $c->request->accepts('application/json') ) { ... } my $types = $c->request->accepted_content_types(); =head1 DESCRIPTION This is a subclass of C that applies the L role to your request class. That trait adds a few methods to the request object to facilitate writing REST-y code. This class is only here for backwards compatibility with applications already subclassing this class. New code should use L directly. L and L will arrange for the request trait to be applied if needed. =head1 SEE ALSO L. =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut catalyst-controller-rest.t100644000765000024 535213211532667 24071 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Test::Requires qw(YAML::Syck); use YAML::Syck; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; my $t = Test::Rest->new(content_type => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $data = { your => 'face' }; is_deeply( Load( request($t->put(url => '/rest/test', data => Dump($data)))->content ), { test => 'worked', data => $data }, 'round trip (deserialize/serialize)', ); ok my $res = request( $t->get( url => '/rest/test_status_created' ) ); is $res->code, 201, "... status created"; is $res->header('Location'), '/rest', "...location of what was created"; ok $res = request( $t->get( url => '/rest/test_status_accepted' ) ); is $res->code, 202, "... status accepted"; is $res->header('Location'), '/rest', "...location of what was accepted"; ok $res = request( $t->get( url => '/rest/test_status_no_content' ) ); is $res->code, 204, "... status no content"; is $res->content, '', '... no content'; ok $res = request( $t->get( url => '/rest/test_status_found' ) ); is $res->code, 302, '... status found'; is_deeply Load( $res->content ), { status => 'found' }, "... status found message"; is $res->header('Location'), '/rest', "...location of what was found"; ok $res = request( $t->get( url => '/rest/test_status_see_other' ) ); is $res->code, 303, "... status see other"; is $res->header('Location'), '/rest', "...location to redirect to"; ok $res = request( $t->get( url => '/rest/test_status_bad_request' ) ); is $res->code, 400, '... status bad request'; is_deeply Load( $res->content ), { error => "Cannot do what you have asked!" }, "... status bad request message"; ok $res = request( $t->get( url => '/rest/test_status_forbidden' ) ); is $res->code, 403, '... status forbidden'; is_deeply Load( $res->content ), { error => "access denied" }, "... status forbidden"; ok $res = request( $t->get( url => '/rest/test_status_not_found' ) ); is $res->code, 404, '... status not found'; is_deeply Load( $res->content ), { error => "Cannot find what you were looking for!" }, "... status bad request message"; ok $res = request( $t->get( url => '/rest/test_status_gone' ) ); is $res->code, 410, '... status gone'; is_deeply Load( $res->content ), { error => "Document have been deleted by foo" }, "... status gone message"; ok $res = request( $t->get( url => '/rest/test_status_multiple_choices' ) ); is $res->code, 300, "... multiple choices"; is_deeply Load($res->content), { choices => [qw(/rest/choice1 /rest/choice2)] }, "... 300 multiple choices has response body"; is $res->header('Location'), '/rest/choice1', "...main location of what was found"; done_testing; catalyst-action-serialize.t100644000765000024 410313211532667 24166 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More 0.88; use Test::Requires qw(YAML::Syck); use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; my $t = Test::Rest->new('content_type' => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $res = request($t->get(url => '/serialize/test')); ok( $res->is_success, 'GET the serialized request succeeded' ); is( $res->content, "--- \nlou: is my cat\n", "Request returned proper data"); my $nt = Test::Rest->new('content_type' => 'text/broken'); my $bres = request($nt->get(url => '/serialize/test')); is( $bres->code, 415, 'GET on un-useable Serialize class returns 415'); my $ut = Test::Rest->new('content_type' => 'text/not-happening'); my $ures = request($ut->get(url => '/serialize/test')); is ($bres->code, 415, 'GET on unknown Content-Type returns 415'); # This check is to make sure we can still serialize after the first # request. my $res2 = request($t->get(url => '/serialize/test_second')); ok( $res2->is_success, '2nd request succeeded' ); is( $res2->content, "--- \nlou: is my cat\n", "request returned proper data"); Test::Catalyst::Action::REST->controller('Serialize')->{serialize} = { }; $res2 = request($t->get(url => '/serialize/test_second')); ok( $res2->is_success, 'request succeeded (deprecated config)' ); is( $res2->content, "--- \nlou: is my cat\n", "request returned proper data"); $res = request($t->get(url => '/serialize/empty_serialized')); is $res->content, "--- \nfoo: bar\n", 'normal case ok'; ok $res->header('Content-Length'), 'set content-length when we serialize'; $res = request($t->get(url => '/serialize/empty_not_serialized_blank')); is $res->content, '', "body explicitly set to '' results in '' content"; ok !$res->header('Content-Length'), "body explicitly set to '' - no automatic content-length"; $res = request($t->get(url => '/serialize/explicit_view')); is $res->content, '', "view explicitly set to '' results in '' content"; ok !$res->header('Content-Length'), "view explicitly set to '' - no automatic content-length"; done_testing; Class000755000765000024 013211532667 22526 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/ActionSub.pm100644000765000024 33413211532667 23735 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Action/Classpackage Test::Action::Class::Sub; use Moose; extends 'Test::Action::Class'; before execute => sub { my ($self, $controller, $c, @args) = @_; $c->response->header( 'Using-Sub-Action' => 'MOO' ); }; no Moose; 1; Controller000755000765000024 013211532667 22771 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/CatalystREST.pm100644000765000024 4766413211532667 24305 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Controllerpackage Catalyst::Controller::REST; $Catalyst::Controller::REST::VERSION = '1.21'; use Moose; use namespace::autoclean; =head1 NAME Catalyst::Controller::REST - A RESTful controller =head1 SYNOPSIS package Foo::Controller::Bar; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller::REST' } sub thing : Local : ActionClass('REST') { } # Answer GET requests to "thing" sub thing_GET { my ( $self, $c ) = @_; # Return a 200 OK, with the data in entity # serialized in the body $self->status_ok( $c, entity => { some => 'data', foo => 'is real bar-y', }, ); } # Answer PUT requests to "thing" sub thing_PUT { my ( $self, $c ) = @_; $radiohead = $c->req->data->{radiohead}; $self->status_created( $c, location => $c->req->uri, entity => { radiohead => $radiohead, } ); } =head1 DESCRIPTION Catalyst::Controller::REST implements a mechanism for building RESTful services in Catalyst. It does this by extending the normal Catalyst dispatch mechanism to allow for different subroutines to be called based on the HTTP Method requested, while also transparently handling all the serialization/deserialization for you. This is probably best served by an example. In the above controller, we have declared a Local Catalyst action on "sub thing", and have used the ActionClass('REST'). Below, we have declared "thing_GET" and "thing_PUT". Any GET requests to thing will be dispatched to "thing_GET", while any PUT requests will be dispatched to "thing_PUT". Any unimplemented HTTP methods will be met with a "405 Method Not Allowed" response, automatically containing the proper list of available methods. You can override this behavior through implementing a custom C method. If you do not provide an OPTIONS handler, we will respond to any OPTIONS requests with a "200 OK", populating the Allowed header automatically. Any data included in C<< $c->stash->{'rest'} >> will be serialized for you. The serialization format will be selected based on the content-type of the incoming request. It is probably easier to use the L, which are described below. "The HTTP POST, PUT, and OPTIONS methods will all automatically L the contents of C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on the request's C header. A list of understood serialization formats is L. If we do not have (or cannot run) a serializer for a given content-type, a 415 "Unsupported Media Type" error is generated. To make your Controller RESTful, simply have it BEGIN { extends 'Catalyst::Controller::REST' } =head1 CONFIGURATION See L. Note that the C key has been deprecated. =head1 SERIALIZATION Catalyst::Controller::REST will automatically serialize your responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates which serializer to use by mapping a content-type to a Serialization module. We select the content-type based on: =over =item B If the incoming HTTP Request had a Content-Type header set, we will use it. =item B If this is a GET request, you can supply a content-type query parameter. =item B Finally, if the client provided an Accept header, we will evaluate it and use the best-ranked choice. =back =head1 AVAILABLE SERIALIZERS A given serialization mechanism is only available if you have the underlying modules installed. For example, you can't use XML::Simple if it's not already installed. In addition, each serializer has its quirks in terms of what sorts of data structures it will properly handle. L makes no attempt to save you from yourself in this regard. :) =over 2 =item * C => C Returns YAML generated by L. =item * C => C This uses L and L to generate YAML with all URLs turned to hyperlinks. Only usable for Serialization. =item * C => C Uses L to generate JSON output. It is strongly advised to also have L installed. The C content type is supported but is deprecated and you will receive warnings in your log. You can also add a hash in your controller config to pass options to the json object. There are two options. C are used when decoding incoming JSON, and C is used when encoding JSON for output. For instance, to relax permissions when deserializing input, add: __PACKAGE__->config( json_options => { relaxed => 1 } ) To indent the JSON output so it becomes more human readable, add: __PACKAGE__->config( json_options_encode => { indent => 1 } ) =item * C => C If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON); Note - this is disabled by default as it can be a security risk if you are unaware. The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript', 'application/javascript'. =item * C => C Uses the L module to generate L output. =item * C => C Uses the L module to generate L output. =item * C => C Uses the L module to generate L output. =item * C => C Uses the L module to generate L output. =item * C => C Uses the L module to generate L output. =item * C => C Uses L to generate XML output. This is probably not suitable for any real heavy XML work. Due to Ls requirement that the data you serialize be a HASHREF, we transform outgoing data to be in the form of: { data => $yourdata } =item * L Uses a regular Catalyst view. For example, if you wanted to have your C and C views rendered by TT, set: __PACKAGE__->config( map => { 'text/html' => [ 'View', 'TT' ], 'text/xml' => [ 'View', 'XML' ], } ); Your views should have a C method like this: sub process { my ( $self, $c, $stash_key ) = @_; my $output; eval { $output = $self->serialize( $c->stash->{$stash_key} ); }; return $@ if $@; $c->response->body( $output ); return 1; # important } sub serialize { my ( $self, $data ) = @_; my $serialized = ... process $data here ... return $serialized; } =item * Callback For infinite flexibility, you can provide a callback for the deserialization/serialization steps. __PACKAGE__->config( map => { 'text/xml' => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ], } ); The C callback is passed a string that is the body of the request and is expected to return a scalar value that results from the deserialization. The C callback is passed the data structure that needs to be serialized and must return a string suitable for returning in the HTTP response. In addition to receiving the scalar to act on, both callbacks are passed the controller object and the context (i.e. C<$c>) as the second and third arguments. =back By default, L will return a C<415 Unsupported Media Type> response if an attempt to use an unsupported content-type is made. You can ensure that something is always returned by setting the C config option: __PACKAGE__->config(default => 'text/x-yaml'); would make it always fall back to the serializer plugin defined for C. =head1 CUSTOM SERIALIZERS Implementing new Serialization formats is easy! Contributions are most welcome! If you would like to implement a custom serializer, you should create two new modules in the L and L namespace. Then assign your new class to the content-type's you want, and you're done. See L and L for more information. =head1 STATUS HELPERS Since so much of REST is in using HTTP, we provide these Status Helpers. Using them will ensure that you are responding with the proper codes, headers, and entities. These helpers try and conform to the HTTP 1.1 Specification. You can refer to it at: L. These routines are all implemented as regular subroutines, and as such require you pass the current context ($c) as the first argument. =over =cut BEGIN { extends 'Catalyst::Controller' } use Params::Validate qw(SCALAR OBJECT); __PACKAGE__->mk_accessors(qw(serialize)); __PACKAGE__->config( 'stash_key' => 'rest', 'map' => { 'text/xml' => 'XML::Simple', 'application/json' => 'JSON', 'text/x-json' => 'JSON', }, 'compliance_mode' => 0, ); sub begin : ActionClass('Deserialize') { } sub end : ActionClass('Serialize') { } =item status_ok Returns a "200 OK" response. Takes an "entity" to serialize. Example: $self->status_ok( $c, entity => { radiohead => "Is a good band!", } ); =cut sub status_ok { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { entity => 1, }, ); $c->response->status(200); $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_created Returns a "201 CREATED" response. Takes an "entity" to serialize, and a "location" where the created object can be found. Example: $self->status_created( $c, location => $c->req->uri, entity => { radiohead => "Is a good band!", } ); In the above example, we use the requested URI as our location. This is probably what you want for most PUT requests. =cut sub status_created { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { location => { type => SCALAR | OBJECT }, entity => { optional => 1 }, }, ); $c->response->status(201); $c->response->header( 'Location' => $p{location} ); $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_accepted Returns a "202 ACCEPTED" response. Takes an "entity" to serialize. Also takes optional "location" for queue type scenarios. Example: $self->status_accepted( $c, location => $c->req->uri, entity => { status => "queued", } ); =cut sub status_accepted { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { location => { type => SCALAR | OBJECT, optional => 1 }, entity => 1, }, ); $c->response->status(202); $c->response->header( 'Location' => $p{location} ) if exists $p{location}; $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_no_content Returns a "204 NO CONTENT" response. =cut sub status_no_content { my $self = shift; my $c = shift; $c->response->status(204); $self->_set_entity( $c, undef ); return 1; } =item status_multiple_choices Returns a "300 MULTIPLE CHOICES" response. Takes an "entity" to serialize, which should provide list of possible locations. Also takes optional "location" for preferred choice. =cut sub status_multiple_choices { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { entity => 1, location => { type => SCALAR | OBJECT, optional => 1 }, }, ); $c->response->status(300); $c->response->header( 'Location' => $p{location} ) if exists $p{'location'}; $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_found Returns a "302 FOUND" response. Takes an "entity" to serialize. Also takes optional "location". =cut sub status_found { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { entity => 1, location => { type => SCALAR | OBJECT, optional => 1 }, }, ); $c->response->status(302); $c->response->header( 'Location' => $p{location} ) if exists $p{'location'}; $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_bad_request Returns a "400 BAD REQUEST" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. Example: $self->status_bad_request( $c, message => "Cannot do what you have asked!", ); =cut sub status_bad_request { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(400); $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug; $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } =item status_forbidden Returns a "403 FORBIDDEN" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. Example: $self->status_forbidden( $c, message => "access denied", ); =cut sub status_forbidden { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(403); $c->log->debug( "Status Forbidden: " . $p{'message'} ) if $c->debug; $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } =item status_not_found Returns a "404 NOT FOUND" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. Example: $self->status_not_found( $c, message => "Cannot find what you were looking for!", ); =cut sub status_not_found { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(404); $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug; $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } =item gone Returns a "41O GONE" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. Example: $self->status_gone( $c, message => "The document have been deleted by foo", ); =cut sub status_gone { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(410); $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug; $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } =item status_see_other Returns a "303 See Other" response. Takes an optional "entity" to serialize, and a "location" where the client should redirect to. Example: $self->status_see_other( $c, location => $some_other_url, entity => { radiohead => "Is a good band!", } ); =cut sub status_see_other { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { location => { type => SCALAR | OBJECT }, entity => { optional => 1 }, }, ); $c->response->status(303); $c->response->header( 'Location' => $p{location} ); $self->_set_entity( $c, $p{'entity'} ); return 1; } =item status_moved Returns a "301 MOVED" response. Takes an "entity" to serialize, and a "location" where the created object can be found. Example: $self->status_moved( $c, location => '/somewhere/else', entity => { radiohead => "Is a good band!", }, ); =cut sub status_moved { my $self = shift; my $c = shift; my %p = Params::Validate::validate( @_, { location => { type => SCALAR | OBJECT }, entity => { optional => 1 }, }, ); my $location = ref $p{location} ? $p{location}->as_string : $p{location} ; $c->response->status(301); $c->response->header( Location => $location ); $self->_set_entity($c, $p{entity}); return 1; } sub _set_entity { my $self = shift; my $c = shift; my $entity = shift; if ( defined($entity) ) { $c->stash->{ $self->{'stash_key'} } = $entity; } return 1; } =back =head1 MANUAL RESPONSES If you want to construct your responses yourself, all you need to do is put the object you want serialized in $c->stash->{'rest'}. =head1 IMPLEMENTATION DETAILS This Controller ties together L, L and L. It should be suitable for most applications. You should be aware that it: =over 4 =item Configures the Serialization Actions This class provides a default configuration for Serialization. It is currently: __PACKAGE__->config( 'stash_key' => 'rest', 'map' => { 'text/html' => 'YAML::HTML', 'text/xml' => 'XML::Simple', 'text/x-yaml' => 'YAML', 'application/json' => 'JSON', 'text/x-json' => 'JSON', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ], 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ], 'application/x-storable' => [ 'Data::Serializer', 'Storable' ], 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ], 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ], 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ], }, ); You can read the full set of options for this configuration block in L. =item Sets a C and C method for you The C method uses L. The C method uses L. If you want to override either behavior, simply implement your own C and C actions and forward to another action with the Serialize and/or Deserialize action classes: package Foo::Controller::Monkey; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller::REST' } sub begin : Private { my ($self, $c) = @_; ... do things before Deserializing ... $c->forward('deserialize'); ... do things after Deserializing ... } sub deserialize : ActionClass('Deserialize') {} sub end :Private { my ($self, $c) = @_; ... do things before Serializing ... $c->forward('serialize'); ... do things after Serializing ... } sub serialize : ActionClass('Serialize') {} If you need to deserialize multipart requests (i.e. REST data in one part and file uploads in others) you can do so by using the L action class. =back =head1 A MILD WARNING I have code in production using L. That said, it is still under development, and it's possible that things may change between releases. I promise to not break things unnecessarily. :) =head1 SEE ALSO L, L, L For help with REST in general: The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut __PACKAGE__->meta->make_immutable; 1; catalyst-action-deserialize.t100644000765000024 234413211532667 24504 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Test::Requires qw(YAML::Syck); use YAML::Syck; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; my $t = Test::Rest->new('content_type' => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $url = '/deserialize/test'; my $req = $t->put( url => $url, data => Dump({ kitty => "LouLou" })); my $res = request($req); ok( $res->is_success, 'PUT Deserialize request succeeded' ); is( $res->content, "LouLou", "Request returned deserialized data"); $req->method('GET'); isnt( request($req)->content, 'LouLou', 'GET request with body does not work by default' ); $req->url('/deserialize/test_action_args'); is( request($req)->content, 'LouLou', 'GET request via action_args'); my $nt = Test::Rest->new('content_type' => 'text/broken'); my $bres = request($nt->put( url => $url, data => Dump({ kitty => "LouLou" }))); is( $bres->code, 415, 'PUT on un-useable Deserialize class returns 415'); my $ut = Test::Rest->new('content_type' => 'text/not-happening'); my $ures = request($ut->put( url => $url, data => Dump({ kitty => "LouLou" }))); is ($bres->code, 415, 'GET on unknown Content-Type returns 415'); 1; done_testing; Serialize.pm100644000765000024 1153513211532667 24535 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Actionpackage Catalyst::Action::Serialize; $Catalyst::Action::Serialize::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action::SerializeBase'; use Module::Pluggable::Object; use MRO::Compat; has _encoders => ( is => 'ro', isa => 'HashRef', default => sub { {} }, ); sub execute { my $self = shift; my ( $controller, $c ) = @_; $self->maybe::next::method(@_); return 1 if $c->req->method eq 'HEAD'; return 1 if $c->response->has_body; return 1 if scalar @{ $c->error }; return 1 if $c->response->status =~ /^(?:204)$/; return 1 if defined $c->stash->{current_view}; return 1 if defined $c->stash->{current_view_instance}; # on 3xx responses, serialize if there's something to # serialize, no-op if not my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; return 1 if $c->response->status =~ /^(?:3\d\d)$/ && ! defined $c->stash->{$stash_key}; my ( $sclass, $sarg, $content_type ) = $self->_load_content_plugins( "Catalyst::Action::Serialize", $controller, $c ); unless ( defined($sclass) ) { if ( defined($content_type) ) { $c->log->info("Could not find a serializer for $content_type"); } else { $c->log->info( "Could not find a serializer for an empty content-type"); } return 1; } $c->log->debug( "Serializing with $sclass" . ( $sarg ? " [$sarg]" : '' ) ) if $c->debug; $self->_encoders->{$sclass} ||= $sclass->new; my $sobj = $self->_encoders->{$sclass}; my $rc; eval { if ( defined($sarg) ) { $rc = $sobj->execute( $controller, $c, $sarg ); } else { $rc = $sobj->execute( $controller, $c ); } }; if ($@) { return $self->serialize_bad_request( $c, $content_type, $@ ); } elsif (!$rc) { return $self->unsupported_media_type( $c, $content_type ); } return 1; } __PACKAGE__->meta->make_immutable; 1; =head1 NAME Catalyst::Action::Serialize - Serialize Data in a Response =head1 SYNOPSIS package Foo::Controller::Bar; __PACKAGE__->config( 'default' => 'text/x-yaml', 'stash_key' => 'rest', 'map' => { 'text/html' => [ 'View', 'TT', ], 'text/x-yaml' => 'YAML', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], } ); sub end :ActionClass('Serialize') {} =head1 DESCRIPTION This action will serialize the body of an HTTP Response. The serializer is selected by introspecting the HTTP Requests content-type header. It requires that your Catalyst controller is properly configured to set up the mapping between Content Type's and Serialization classes. The specifics of serializing each content-type is implemented as a plugin to L. Typically, you would use this ActionClass on your C method. However, nothing is stopping you from choosing specific methods to Serialize: sub foo :Local :ActionClass('Serialize') { .. populate stash with data .. } When you use this module, the request class will be changed to L. =head1 CONFIGURATION =head2 map Takes a hashref, mapping Content-Types to a given serializer plugin. =head2 default This is the 'fall-back' Content-Type if none of the requested or acceptable types is found in the L. It must be an entry in the L. =head2 stash_key Specifies the key of the stash entry holding the data that is to be serialized. So if the value is "rest", we will serialize the data under: $c->stash->{'rest'} =head2 content_type_stash_key Specifies the key of the stash entry that optionally holds an overriding Content-Type. If set, and if the specified stash entry has a valid value, then it takes priority over the requested content types. This can be useful if you want to dynamically force a particular content type, perhaps for debugging. =head1 HELPFUL PEOPLE Daisuke Maki pointed out that early versions of this Action did not play well with others, or generally behave in a way that was very consistent with the rest of Catalyst. =head1 CUSTOM ERRORS For building custom error responses when serialization fails, you can create an ActionRole (and use L to apply it to the C action) which overrides C and/or C methods. =head1 SEE ALSO You likely want to look at L, which implements a sensible set of defaults for doing a REST controller. L, L =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut Deserialize.pm100644000765000024 1241613211532667 25045 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Actionpackage Catalyst::Action::Deserialize; $Catalyst::Action::Deserialize::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action::SerializeBase'; use Module::Pluggable::Object; use MRO::Compat; use Moose::Util::TypeConstraints; has plugins => ( is => 'rw' ); has deserialize_http_methods => ( traits => ['Hash'], isa => do { my $tc = subtype as 'HashRef[Str]'; coerce $tc, from 'ArrayRef[Str]', via { +{ map { ($_ => 1) } @$_ } }; $tc; }, coerce => 1, builder => '_build_deserialize_http_methods', handles => { deserialize_http_methods => 'keys', _deserialize_handles_http_method => 'exists', }, ); sub _build_deserialize_http_methods { [qw(POST PUT OPTIONS DELETE)] } sub execute { my $self = shift; my ( $controller, $c ) = @_; if ( !defined($c->req->data) && $self->_deserialize_handles_http_method($c->request->method) ) { my ( $sclass, $sarg, $content_type ) = $self->_load_content_plugins( 'Catalyst::Action::Deserialize', $controller, $c ); return 1 unless defined($sclass); my $rc; if ( defined($sarg) ) { $rc = $sclass->execute( $controller, $c, $sarg ); } else { $rc = $sclass->execute( $controller, $c ); } if ( $rc eq "0" ) { return $self->unsupported_media_type( $c, $content_type ); } elsif ( $rc ne "1" ) { return $self->serialize_bad_request( $c, $content_type, $rc ); } } $self->maybe::next::method(@_); return 1; } __PACKAGE__->meta->make_immutable; =head1 NAME Catalyst::Action::Deserialize - Deserialize Data in a Request =head1 SYNOPSIS package Foo::Controller::Bar; __PACKAGE__->config( 'default' => 'text/x-yaml', 'stash_key' => 'rest', 'map' => { 'text/x-yaml' => 'YAML', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], }, ); sub begin :ActionClass('Deserialize') {} =head1 DESCRIPTION This action will deserialize HTTP POST, PUT, OPTIONS and DELETE requests. It assumes that the body of the HTTP Request is a serialized object. The serializer is selected by introspecting the requests content-type header. If you want deserialize any other HTTP method besides POST, PUT, OPTIONS and DELETE you can do this by setting the C<< deserialize_http_methods >> list via C<< action_args >>. Just modify the config in your controller and define a list of HTTP methods the deserialization should happen for: __PACKAGE__->config( action_args => { '*' => { deserialize_http_methods => [qw(POST PUT OPTIONS DELETE GET)] } } ); See also L. The specifics of deserializing each content-type is implemented as a plugin to L. You can see a list of currently implemented plugins in L. The results of your Deserializing will wind up in $c->req->data. This is done through the magic of L. While it is common for this Action to be called globally as a C method, there is nothing stopping you from using it on a single routine: sub foo :Local :Action('Deserialize') {} Will work just fine. When you use this module, the request class will be changed to L. =head1 RFC 7231 Compliance Mode To maintain backwards compatibility with the module's original functionality, where it was assumed the deserialize and serialize content types are the same, an optional compliance mode can be enabled to break this assumption. __PACKAGE__->config( 'compliance_mode' => 1, 'default' => 'text/x-yaml', 'stash_key' => 'rest', 'map' => { 'text/x-yaml' => 'YAML', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], }, 'deserialize_default => 'application/json', 'deserialize_map' => { 'application/json' => 'JSON', }, ); Three extra keys are added to the controller configuration. compliance_mode, a boolean to enable the mode. And a parallel set of content type mappings 'deserialize_default' and 'deserialize_map' to mirror the default/map configuration keys. The module will use the default/map keys when negotiating the serializing content type specified by the client in the Accept header. And will use the deserialize_default/deserialize_map in conjunction with the Content-Type header where the client is giving the content type being sent in the request. =head1 CUSTOM ERRORS For building custom error responses when de-serialization fails, you can create an ActionRole (and use L to apply it to the C action) which overrides C and/or C methods. =head1 SEE ALSO You likely want to look at L, which implements a sensible set of defaults for a controller doing REST. L, L =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut catalyst-traitfor-request-rest.t100644000765000024 1611413211532667 25244 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ( "$FindBin::Bin/../lib", "$FindBin::Bin/../t/lib" ); use Catalyst::Request::REST; use Catalyst::TraitFor::Request::REST; use Moose::Meta::Class; use HTTP::Headers; use Catalyst::Log; my $anon_class = Moose::Meta::Class->create_anon_class( superclasses => ['Catalyst::Request'], roles => ['Catalyst::TraitFor::Request::REST'], cache => 1, )->name; # We run the tests twice to make sure Catalyst::Request::REST is # 100% back-compatible. for my $class ( $anon_class, 'Catalyst::Request::REST' ) { { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->content_type('text/foobar'); is_deeply( $request->accepted_content_types, [ 'text/foobar' ], 'content-type set in request headers is found' ); is( $request->preferred_content_type, 'text/foobar', 'preferred content type is text/foobar' ); ok( ! $request->accept_only, 'accept_only is false' ); ok( $request->accepts('text/foobar'), 'accepts text/foobar' ); ok( ! $request->accepts('text/html'), 'does not accept text/html' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( { 'content-type' => 'text/fudge' } ); $request->method('GET'); $request->content_type('text/foobar'); is_deeply( $request->accepted_content_types, [ 'text/foobar', 'text/fudge' ], 'content-type set in request headers and type in parameters is found' ); is( $request->preferred_content_type, 'text/foobar', 'preferred content type is text/foobar' ); ok( ! $request->accept_only, 'accept_only is false' ); ok( $request->accepts('text/foobar'), 'accepts text/foobar' ); ok( $request->accepts('text/fudge'), 'accepts text/fudge' ); ok( ! $request->accepts('text/html'), 'does not accept text/html' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( { 'content-type' => 'text/fudge' } ); $request->method('POST'); $request->content_type('text/foobar'); ok( ! $request->accepts('text/fudge'), 'content type in parameters is ignored for POST' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->headers->header( 'Accept' => # From Firefox 2.0 when it requests an html page 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', ); is_deeply( $request->accepted_content_types, [ qw( text/xml application/xml application/xhtml+xml image/png text/html text/plain */* ) ], 'accept header is parsed properly' ); is( $request->preferred_content_type, 'text/xml', 'preferred content type is text/xml' ); ok( $request->accept_only, 'accept_only is true' ); ok( $request->accepts('text/html'), 'accepts text/html' ); ok( $request->accepts('image/png'), 'accepts image/png' ); ok( ! $request->accepts('image/svg'), 'does not accept image/svg' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->content_type('application/json'); $request->headers->header( 'Accept' => # From Firefox 2.0 when it requests an html page 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', ); is_deeply( $request->accepted_content_types, [ qw( application/json text/xml application/xml application/xhtml+xml image/png text/html text/plain */* ) ], 'accept header is parsed properly, and content-type header has precedence over accept' ); ok( ! $request->accept_only, 'accept_only is false' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->content_type('application/json'); $request->headers->header( 'Accept' => # From Firefox 2.0 when it requests an html page 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', ); is_deeply( $request->accepted_content_types, [ qw( application/json text/xml application/xml application/xhtml+xml image/png text/html text/plain */* ) ], 'accept header is parsed properly, and content-type header has precedence over accept' ); ok( ! $request->accept_only, 'accept_only is false' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->content_type('text/x-json'); $request->headers->header( 'Accept' => 'text/plain,text/x-json', ); is_deeply( $request->accepted_content_types, [ qw( text/x-json text/plain ) ], 'each type appears only once' ); } { my $request = $class->new( _log => Catalyst::Log->new ); $request->{_context} = 'MockContext'; $request->headers( HTTP::Headers->new ); $request->parameters( {} ); $request->method('GET'); $request->content_type('application/json'); $request->headers->header( 'Accept' => 'text/plain,application/json', ); is_deeply( $request->accepted_content_types, [ qw( application/json text/plain ) ], 'each type appears only once' ); } } done_testing; package MockContext; sub prepare_body { } Action000755000765000024 013211532667 23245 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/CatalystREST.pm100644000765000024 110213211532667 24512 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Actionpackage Test::Catalyst::Action::REST; use Moose; use namespace::autoclean; use Catalyst::Runtime '5.70'; use Catalyst; use FindBin; use Test::Catalyst::Log; __PACKAGE__->config( name => 'Test::Catalyst::Action::REST', # RT#43840 -- this was ignored in 0.66 and earlier 'Controller::Serialize' => { content_type_stash_key => 'serialize_content_type', }, ); __PACKAGE__->request_class($ENV{CAR_TEST_REQUEST_CLASS}) if $ENV{CAR_TEST_REQUEST_CLASS}; __PACKAGE__->setup; __PACKAGE__->log( Test::Catalyst::Log->new ) unless __PACKAGE__->debug; 1; View000755000765000024 013211532667 23105 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/SerializeAwful.pm100644000765000024 62713211532667 24646 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Serialize/Viewpackage Test::Serialize::View::Awful; use base Catalyst::View; sub render { my ($self, $c, $template) = @_; die "I don't know how to do that!"; } sub process { my ($self, $c) = @_; my $output = eval { $self->render($c, "blah"); }; if ($@) { my $error = qq/Couldn't render template. Error: "$@"/; $c->error($error); return 0; } $c->res->body($output); return 1; } 1; catalyst-action-serialize-query.t100644000765000024 116413211532667 25335 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Test::Requires qw(YAML::Syck); use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; # YAML my $t = Test::Rest->new('content_type' => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $req = $t->get(url => '/serialize/test?content-type=text/x-yaml'); $req->remove_header('Content-Type'); my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); my $data = <content, $data, "Request returned proper data"); 1; done_testing; Simple.pm100644000765000024 31713211532667 25015 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Serialize/Viewpackage Test::Serialize::View::Simple; use Moose; use namespace::autoclean; extends qw/Catalyst::View/; sub process { my ($self, $c) = @_; $c->res->body("I am a simple view"); return 1; } 1; SerializeBase.pm100644000765000024 1530213211532667 25324 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Actionpackage Catalyst::Action::SerializeBase; $Catalyst::Action::SerializeBase::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; use Module::Pluggable::Object; use Catalyst::Request::REST; use Catalyst::Utils (); after BUILDARGS => sub { my $class = shift; my $config = shift; Catalyst::Request::REST->_insert_self_into( $config->{class} ); }; has [qw(_serialize_plugins _loaded_plugins)] => ( is => 'rw' ); sub _load_content_plugins { my $self = shift; my ( $search_path, $controller, $c ) = @_; unless ( defined( $self->_loaded_plugins ) ) { $self->_loaded_plugins( {} ); } # Load the Serialize Classes unless ( defined( $self->_serialize_plugins ) ) { my @plugins; my $mpo = Module::Pluggable::Object->new( 'search_path' => [$search_path], ); @plugins = $mpo->plugins; $self->_serialize_plugins( \@plugins ); } # Finally, we load the class. If you have a default serializer, # and we still don't have a content-type that exists in the map, # we'll use it. my $sclass = $search_path . "::"; my $sarg; my $map; my $compliance_mode; my $default; my $config; if ( exists $controller->{'serialize'} ) { $c->log->info("Catalyst::Action::REST - deprecated use of 'serialize' for configuration."); $c->log->info("Please see 'CONFIGURATION' in Catalyst::Controller::REST."); $config = $controller->{'serialize'}; # if they're using the deprecated config, they may be expecting a # default mapping too. $config->{map} ||= $controller->{map}; } else { $config = $controller; } $map = $config->{'map'}; $default = $config->{'default'} if $config->{'default'}; # If we're in RFC 7231 compliance mode we need to determine if we're # serializing or deserializing, then set the request object to # look at the appropriate set of supported content types. $compliance_mode = $config->{'compliance_mode'}; if($compliance_mode) { my $serialize_mode = (split '::', $search_path)[-1]; if($serialize_mode eq 'Deserialize') { # Tell the request object to only look at the Content-Type header $c->request->set_content_type_only(); # If we're in compliance mode and doing deserializing we want # to use the allowed content types for deserializing, not the # serializer map $map = $config->{'deserialize_map'}; $default = $config->{'deserialize_default'} if $config->{'deserialize_default'}; } elsif($serialize_mode eq 'Serialize') { # Tell the request object to only look at the Accept header $c->request->set_accept_only(); } } # pick preferred content type my @accepted_types; # priority order, best first # give top priority to content type specified by stash, if any my $content_type_stash_key = $config->{content_type_stash_key}; if ($content_type_stash_key and my $stashed = $c->stash->{$content_type_stash_key} ) { # convert to array if not already a ref $stashed = [ $stashed ] if not ref $stashed; push @accepted_types, @$stashed; } # then content types requested by caller push @accepted_types, @{ $c->request->accepted_content_types }; # then the default push @accepted_types, $default if $default; # pick the best match that we have a serializer mapping for my ($content_type) = grep { $map->{$_} } @accepted_types; return $self->unsupported_media_type($c, $content_type) if not $content_type; # carp about old text/x-json if ($content_type eq 'text/x-json') { $c->log->info('Using deprecated text/x-json content-type.'); $c->log->info('Use application/json instead!'); } if ( exists( $map->{$content_type} ) ) { my $mc; if ( ref( $map->{$content_type} ) eq "ARRAY" ) { $mc = $map->{$content_type}->[0]; $sarg = $map->{$content_type}->[1]; } else { $mc = $map->{$content_type}; } # TODO: Handle custom serializers more elegantly.. this is a start, # but how do we determine which is Serialize and Deserialize? #if ($mc =~ /^+/) { # $sclass = $mc; # $sclass =~ s/^+//g; #} else { $sclass .= $mc; #} if ( !grep( /^$sclass$/, @{ $self->_serialize_plugins } ) ) { return $self->unsupported_media_type($c, $content_type); } } else { return $self->unsupported_media_type($c, $content_type); } unless ( exists( $self->_loaded_plugins->{$sclass} ) ) { my $load_class = $sclass; $load_class =~ s/::/\//g; $load_class =~ s/$/.pm/g; eval { require $load_class; }; if ($@) { $c->log->error( "Error loading $sclass for " . $content_type . ": $!" ); return $self->unsupported_media_type($c, $content_type); } else { $self->_loaded_plugins->{$sclass} = 1; } } if ($search_path eq "Catalyst::Action::Serialize") { unless( $c->response->header( 'Vary' ) ) { if ($content_type) { $c->response->header( 'Vary' => 'Content-Type' ); } elsif ($c->request->accept_only) { $c->response->header( 'Vary' => 'Accept' ); } } $c->response->content_type($content_type); } return $sclass, $sarg, $content_type; } sub unsupported_media_type { my ( $self, $c, $content_type ) = @_; $c->res->content_type('text/plain'); $c->res->status(415); if (defined($content_type) && $content_type ne "") { $c->res->body( "Content-Type " . $content_type . " is not supported.\r\n" ); } else { $c->res->body( "Cannot find a Content-Type supported by your client.\r\n" ); } return undef; } sub serialize_bad_request { my ( $self, $c, $content_type, $error ) = @_; $c->res->content_type('text/plain'); $c->res->status(400); $c->res->body( "Content-Type " . $content_type . " had a problem with your request.\r\n***ERROR***\r\n$error" ); return undef; } __PACKAGE__->meta->make_immutable; 1; =head1 NAME Catalyst::Action::SerializeBase - Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize. =head1 DESCRIPTION This module implements the plugin loading and content-type negotiating code for L and L. =head1 SEE ALSO L, L, L, =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut catalyst-action-serialize-accept.t100644000765000024 624213211532667 25431 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use Test::Requires qw(YAML::Syck); use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; use Catalyst::Action::Serialize::YAML; # Should use Data::Dumper, via YAML my $t = Test::Rest->new('content_type' => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; # to avoid whatever serialization bugs YAML::Syck has, # e.g. http://rt.cpan.org/Public/Bug/Display.html?id=46983, # we won't hardcode the expected output my $output_YAML = Catalyst::Action::Serialize::YAML->serialize({lou => 'is my cat'}); { my $req = $t->get(url => '/serialize/test'); $req->remove_header('Content-Type'); $req->header('Accept', 'text/x-yaml'); my $res = request($req); SKIP: { skip "can't test text/x-yaml without YAML support", 3 if ( not $res->is_success and $res->content =~ m#Content-Type text/x-yaml is not supported# ); ok( $res->is_success, 'GET the serialized request succeeded' ); is( $res->content, $output_YAML, "Request returned proper data"); is( $res->content_type, 'text/x-yaml', '... with expected content-type') }; } SKIP: { eval 'use JSON 2.12;'; skip "can't test application/json without JSON support", 3 if $@; my $json = JSON->new; my $at = Test::Rest->new('content_type' => 'text/doesnt-exist'); my $req = $at->get(url => '/serialize/test'); $req->header('Accept', 'application/json'); my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); my $ret = $json->decode($res->content); is( $ret->{lou}, 'is my cat', "Request returned proper data"); is( $res->content_type, 'application/json', 'Accept header used if content-type mapping not found') }; # Make sure we don't get a bogus content-type when using the default # serializer (https://rt.cpan.org/Ticket/Display.html?id=27949) { my $req = $t->get(url => '/serialize/test'); $req->remove_header('Content-Type'); $req->header('Accept', '*/*'); my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); is( $res->content, $output_YAML, "Request returned proper data"); is( $res->content_type, 'text/x-yaml', '... with expected content-type') } # Make sure that when using content_type_stash_key, an invalid value in the stash gets ignored { my $req = $t->get(url => '/serialize/test_second?serialize_content_type=nonesuch'); $req->remove_header('Content-Type'); $req->header('Accept', '*/*'); my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); is( $res->content, $output_YAML, "Request returned proper data"); is( $res->content_type, 'text/x-yaml', '... with expected content-type') } # Make sure that the default content type you specify really gets used. { my $req = $t->get(url => '/override/test'); $req->remove_header('Content-Type'); my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); is( $res->content, "--- \nlou: is my cat\n", "Request returned proper data"); } done_testing; Serialize000755000765000024 013211532667 24012 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/ActionJSON.pm100644000765000024 177713211532667 25275 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serializepackage Catalyst::Action::Serialize::JSON; $Catalyst::Action::Serialize::JSON::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; use JSON::MaybeXS qw(JSON); has encoder => ( is => 'ro', lazy_build => 1, ); sub _build_encoder { my $self = shift; return JSON->new->utf8->convert_blessed; } sub execute { my $self = shift; my ( $controller, $c ) = @_; if (my $options = $controller->{json_options_encode}) { foreach my $opt (keys %$options) { $self->encoder->$opt( $options->{$opt} ); } } my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; my $output = $self->serialize( $c->stash->{$stash_key} ); $c->response->output( $output ); return 1; } sub serialize { my $self = shift; my $data = shift; $self->encoder->encode( $data ); } __PACKAGE__->meta->make_immutable; 1; View.pm100644000765000024 175013211532667 25425 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serializepackage Catalyst::Action::Serialize::View; $Catalyst::Action::Serialize::View::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; sub execute { my $self = shift; my ( $controller, $c, $view ) = @_; # Views don't care / are not going to render an entity for 3XX # responses. return 1 if $c->response->status =~ /^(?:204|3\d\d)$/; my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; if ( !$c->view($view) ) { $c->log->error("Could not load $view, refusing to serialize"); return; } if ($c->view($view)->process($c, $stash_key)) { return 1; } else { # This is stupid. Please improve it. my $error = join("\n", @{ $c->error }) || "Error in $view"; $error .= "\n"; $c->clear_errors; die $error; } } __PACKAGE__->meta->make_immutable; 1; YAML.pm100644000765000024 123713211532667 25255 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serializepackage Catalyst::Action::Serialize::YAML; $Catalyst::Action::Serialize::YAML::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; use YAML::Syck; sub execute { my $self = shift; my ( $controller, $c ) = @_; my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; my $output = $self->serialize($c->stash->{$stash_key}); $c->response->output( $output ); return 1; } sub serialize { my $self = shift; my $data = shift; Dump($data); } __PACKAGE__->meta->make_immutable; 1; Request000755000765000024 013211532667 24030 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/TraitForREST.pm100644000765000024 1467213211532667 25335 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/TraitFor/Requestpackage Catalyst::TraitFor::Request::REST; $Catalyst::TraitFor::Request::REST::VERSION = '1.21'; use Moose::Role; use HTTP::Headers::Util qw(split_header_words); use namespace::autoclean; has [qw/ data accept_only /] => ( is => 'rw' ); has accepted_content_types => ( is => 'ro', isa => 'ArrayRef', lazy => 1, builder => '_build_accepted_content_types', clearer => 'clear_accepted_cache', init_arg => undef, ); has preferred_content_type => ( is => 'ro', isa => 'Str', lazy => 1, builder => '_build_preferred_content_type', init_arg => undef, ); # # By default the module looks at both Content-Type and # Accept and uses the selected content type for both # deserializing received data and serializing the response. # However according to RFC 7231, Content-Type should be # used to specify the payload type of the data sent by # the requester and Accept should be used to negotiate # the content type the requester would like back from # the server. Compliance mode adds support so the method # described in the RFC is more closely model. # # Using a bitmask to represent the the two content type # header schemes. # 0x1 for Accept # 0x2 for Content-Type has 'compliance_mode' => ( is => 'ro', isa => 'Int', lazy => 1, writer => '_set_compliance_mode', default => 0x3, ); # Set request object to only use the Accept header when building # accepted_content_types sub set_accept_only { my $self = shift; # Clear the accepted_content_types cache if we've changed # allowed headers $self->clear_accepted_cache(); $self->_set_compliance_mode(0x1); } # Set request object to only use the Content-Type header when building # accepted_content_types sub set_content_type_only { my $self = shift; $self->clear_accepted_cache(); $self->_set_compliance_mode(0x2); } # Clear serialize/deserialize compliance mode, allow all headers # in both situations sub clear_compliance_mode { my $self = shift; $self->clear_accepted_cache(); $self->_set_compliance_mode(0x3); } # Return true if bit set to examine Accept header sub accept_allowed { my $self = shift; return $self->compliance_mode & 0x1; } # Return true if bit set to examine Content-Type header sub content_type_allowed { my $self = shift; return $self->compliance_mode & 0x2; } # Private writer to set if we're looking at Accept or Content-Type headers sub _set_compliance_mode { my $self = shift; my $mode_bits = shift; $self->compliance_mode($mode_bits); } sub _build_accepted_content_types { my $self = shift; my %types; # First, we use the content type in the HTTP Request. It wins all. # But only examine it if we're not in compliance mode or if we're # in deserializing mode $types{ $self->content_type } = 3 if $self->content_type && $self->content_type_allowed(); # Seems backwards, but users are used to adding &content-type= to the uri to # define what content type they want to recieve back, in the equivalent Accept # header. Let the users do what they're used to, it's outside the RFC # specifications anyhow. if ($self->method eq "GET" && $self->param('content-type') && $self->accept_allowed()) { $types{ $self->param('content-type') } = 2; } # Third, we parse the Accept header, and see if the client # takes a format we understand. # But only examine it if we're not in compliance mode or if we're # in serializing mode # # This is taken from chansen's Apache2::UploadProgress. if ( $self->header('Accept') && $self->accept_allowed() ) { $self->accept_only(1) unless keys %types; my $accept_header = $self->header('Accept'); my $counter = 0; foreach my $pair ( split_header_words($accept_header) ) { my ( $type, $qvalue ) = @{$pair}[ 0, 3 ]; next if $types{$type}; # cope with invalid (missing required q parameter) header like: # application/json; charset="utf-8" # http://tools.ietf.org/html/rfc2616#section-14.1 unless ( defined $pair->[2] && lc $pair->[2] eq 'q' ) { $qvalue = undef; } unless ( defined $qvalue ) { $qvalue = 1 - ( ++$counter / 1000 ); } $types{$type} = sprintf( '%.3f', $qvalue ); } } [ sort { $types{$b} <=> $types{$a} } keys %types ]; } sub _build_preferred_content_type { $_[0]->accepted_content_types->[0] } sub accepts { my $self = shift; my $type = shift; return grep { $_ eq $type } @{ $self->accepted_content_types }; } 1; __END__ =head1 NAME Catalyst::TraitFor::Request::REST - A role to apply to Catalyst::Request giving it REST methods and attributes. =head1 SYNOPSIS if ( $c->request->accepts('application/json') ) { ... } my $types = $c->request->accepted_content_types(); =head1 DESCRIPTION This is a L applied to L that adds a few methods to the request object to facilitate writing REST-y code. Currently, these methods are all related to the content types accepted by the client and the content type sent in the request. =head1 METHODS =over =item data If the request went through the Deserializer action, this method will return the deserialized data structure. =item accepted_content_types Returns an array reference of content types accepted by the client. The list of types is created by looking at the following sources: =over 8 =item * Content-type header If this exists, this will always be the first type in the list. =item * content-type parameter If the request is a GET request and there is a "content-type" parameter in the query string, this will come before any types in the Accept header. =item * Accept header This will be parsed and the types found will be ordered by the relative quality specified for each type. =back If a type appears in more than one of these places, it is ordered based on where it is first found. =item preferred_content_type This returns the first content type found. It is shorthand for: $request->accepted_content_types->[0] =item accepts($type) Given a content type, this returns true if the type is accepted. Note that this does not do any wildcard expansion of types. =back =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut JSONP.pm100644000765000024 150213211532667 25377 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serializepackage Catalyst::Action::Serialize::JSONP; $Catalyst::Action::Serialize::JSONP::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action::Serialize::JSON'; after 'execute' => sub { my $self = shift; my ($controller, $c) = @_; my $callback_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'callback_key'} : $controller->{'callback_key'} ) || 'callback'; my $callback_value = $c->req->param($callback_key); if ($callback_value) { if ($callback_value =~ /^[.\w]+$/) { $c->res->content_type('text/javascript'); $c->res->output($callback_value.'('.$c->res->output().');'); } else { warn 'Callback: '.$callback_value.' will not generate valid Javascript. Falling back to JSON output'; } } }; __PACKAGE__->meta->make_immutable; 1; Deserialize000755000765000024 013211532667 24323 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/ActionJSON.pm100644000765000024 233113211532667 25571 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserializepackage Catalyst::Action::Deserialize::JSON; $Catalyst::Action::Deserialize::JSON::VERSION = '1.21'; use Moose; use namespace::autoclean; use Scalar::Util qw(openhandle); extends 'Catalyst::Action'; use JSON::MaybeXS qw(JSON); sub execute { my $self = shift; my ( $controller, $c, $test ) = @_; my $rbody; # could be a string or a FH if ( my $body = $c->request->body ) { if(openhandle $body) { seek($body, 0, 0); # in case something has already read from it while ( defined( my $line = <$body> ) ) { $rbody .= $line; } } else { $rbody = $body; } } if ( $rbody ) { my $json = JSON->new->utf8; if (my $options = $controller->{json_options}) { foreach my $opt (keys %$options) { $json->$opt( $options->{$opt} ); } } my $rdata = eval { $json->decode( $rbody ) }; if ($@) { return $@; } $c->request->data($rdata); } else { $c->log->debug( 'I would have deserialized, but there was nothing in the body!') if $c->debug; } return 1; } __PACKAGE__->meta->make_immutable; 1; View.pm100644000765000024 35613211532667 25717 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserializepackage Catalyst::Action::Deserialize::View; $Catalyst::Action::Deserialize::View::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; sub execute { return 1; } __PACKAGE__->meta->make_immutable; 1; YAML.pm100644000765000024 175513211532667 25573 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserializepackage Catalyst::Action::Deserialize::YAML; $Catalyst::Action::Deserialize::YAML::VERSION = '1.21'; use Moose; use namespace::autoclean; use Scalar::Util qw(openhandle); extends 'Catalyst::Action'; use YAML::Syck; sub execute { my $self = shift; my ( $controller, $c, $test ) = @_; my $body = $c->request->body; if ($body) { my $rbody = ''; if(openhandle $body) { seek($body, 0, 0); # in case something has already read from it while ( defined( my $line = <$body> ) ) { $rbody .= $line; } } else { $rbody = $body; } my $rdata; eval { $rdata = Load( $rbody ); }; if ($@) { return $@; } $c->request->data($rdata); } else { $c->log->debug( 'I would have deserialized, but there was nothing in the body!') if $c->debug; } return 1; } __PACKAGE__->meta->make_immutable; 1; REST000755000765000024 013211532667 22640 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/ActionForBrowsers.pm100644000765000024 527713211532667 25626 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/RESTpackage Catalyst::Action::REST::ForBrowsers; $Catalyst::Action::REST::ForBrowsers::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action::REST'; use Catalyst::Request::REST::ForBrowsers; sub BUILDARGS { my $class = shift; my $config = shift; Catalyst::Request::REST::ForBrowsers->_insert_self_into( $config->{class} ); return $class->SUPER::BUILDARGS( $config, @_ ); } override dispatch => sub { my $self = shift; my $c = shift; my $req = $c->request(); return super() unless $req->can('looks_like_browser') && $req->looks_like_browser() && uc $c->request()->method() eq 'GET'; my $controller = $c->component( $self->class ); my $rest_method = $self->name() . '_GET_html'; if ( $controller->action_for($rest_method) || $controller->can($rest_method) ) { return $self->_dispatch_rest_method( $c, $rest_method ); } return super(); }; __PACKAGE__->meta->make_immutable; 1; =head1 NAME Catalyst::Action::REST::ForBrowsers - Automated REST Method Dispatching that Accommodates Browsers =head1 SYNOPSIS sub foo :Local :ActionClass('REST::ForBrowsers') { ... do setup for HTTP method specific handlers ... } sub foo_GET : Private { ... do something for GET requests ... } sub foo_GET_html : Private { ... do something for GET requests from browsers ... } sub foo_PUT : Private { ... do something for PUT requests ... } =head1 DESCRIPTION This class subclasses L to add an additional dispatching hook. If the request is a GET request I the request looks like it comes from a browser, it tries to dispatch to a C method before trying to the C method instead. All other HTTP methods are dispatched in the same way. For example, in the synopsis above, calling GET on "/foo" from a browser will end up calling the C method. If the request is I from a browser, it will call C. See L for more details on dispatching details. =head1 METHODS =over 4 =item dispatch This method overrides the default dispatch mechanism to the re-dispatching mechanism described above. =back =head1 SEE ALSO You likely want to look at L, which implements a sensible set of defaults for a controller doing REST. This class automatically adds the L role to your request class. =head1 CONTRIBUTORS Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT Copyright the above named AUTHOR and CONTRIBUTORS =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut Controller000755000765000024 013211532667 24316 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/SerializeJSON.pm100644000765000024 121413211532667 25563 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Serialize/Controllerpackage Test::Serialize::Controller::JSON; use namespace::autoclean; use Moose; BEGIN { extends qw/Catalyst::Controller::REST/ }; __PACKAGE__->config( 'stash_key' => 'rest', 'json_options' => { relaxed => 1, }, 'map' => { 'text/x-json' => 'JSON', }, ); sub monkey_json_put : Path("/monkey_json_put") : ActionClass('Deserialize') { my ( $self, $c ) = @_; if ( ref($c->req->data) eq "HASH" ) { my $out = ($c->req->data->{'sushi'}||'') . ($c->req->data->{'chicken'}||''); utf8::encode($out); $c->res->output( $out ); } else { $c->res->output(1); } } 1; REST.pm100644000765000024 406313211532667 25574 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Serialize/Controllerpackage Test::Serialize::Controller::REST; use namespace::autoclean; use Moose; BEGIN { extends qw/Catalyst::Controller::REST/ }; __PACKAGE__->config( 'namespace' => '', 'stash_key' => 'rest', 'map' => { 'text/html' => 'YAML::HTML', 'text/xml' => 'XML::Simple', 'text/x-yaml' => 'YAML', 'application/json' => 'JSON', 'text/x-json' => 'JSON', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ], 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ], 'application/x-storable' => [ 'Data::Serializer', 'Storable' ], 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ], 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ], 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ], 'text/view' => [ 'View', 'Simple' ], 'text/explodingview' => [ 'View', 'Awful' ], 'text/broken' => 'Broken', 'text/javascript', => 'JSONP', 'application/x-javascript' => 'JSONP', 'application/javascript' => 'JSONP', 'text/my-csv' => [ 'Callback', { deserialize => sub { return {split /,/, shift } }, serialize => sub { my $d = shift; join ',', %$d } } ], }, ); sub monkey_put : Local : ActionClass('Deserialize') { my ( $self, $c ) = @_; if ( ref($c->req->data) eq "HASH" ) { my $out = ($c->req->data->{'sushi'}||'') . ($c->req->data->{'chicken'}||''); utf8::encode($out); $c->res->output( $out ); } else { $c->res->output(1); } } sub monkey_get : Local : ActionClass('Serialize') { my ( $self, $c ) = @_; $c->stash->{'rest'} = { monkey => 'likes chicken!', }; } sub xss_get : Local : ActionClass('Serialize') { my ( $self, $c ) = @_; $c->stash->{'rest'} = { monkey => 'likes chicken > sushi!', }; } 1; JSON000755000765000024 013211532667 24563 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/SerializeXS.pm100644000765000024 50113211532667 25567 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serialize/JSONpackage Catalyst::Action::Serialize::JSON::XS; $Catalyst::Action::Serialize::JSON::XS::VERSION = '1.21'; use Moose; use namespace::autoclean; BEGIN { $ENV{'PERL_JSON_BACKEND'} = 2; # Always use compiled JSON::XS } extends 'Catalyst::Action::Serialize::JSON'; use JSON::XS (); __PACKAGE__->meta->make_immutable; 1; REST000755000765000024 013211532667 23053 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/RequestForBrowsers.pm100644000765000024 246313211532667 26033 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Request/RESTpackage Catalyst::Request::REST::ForBrowsers; $Catalyst::Request::REST::ForBrowsers::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Request::REST'; with 'Catalyst::TraitFor::Request::REST::ForBrowsers'; sub _related_role { 'Catalyst::TraitFor::Request::REST::ForBrowsers' } __PACKAGE__->meta->make_immutable; 1; __END__ =pod =head1 NAME Catalyst::Request::REST::ForBrowsers - A Catalyst::Request::REST subclass for dealing with browsers =head1 SYNOPSIS package MyApp; use Catalyst::Request::REST::ForBrowsers; MyApp->request_class( 'Catalyst::Request::REST::ForBrowsers' ); =head1 DESCRIPTION This class has been deprecated in favor of L. Please see that class for details on methods and attributes. =head1 AUTHOR Dave Rolsky, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 COPYRIGHT & LICENSE Copyright 2008-2009 Dave Rolsky, All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut catalyst-action-rest-action-dispatch.t100644000765000024 352113211532667 26227 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More 0.88; use FindBin; use Data::Dumper; use lib ( "$FindBin::Bin/lib", "$FindBin::Bin/../lib" ); use Test::Rest; # Should use the default serializer, YAML my $t = Test::Rest->new( 'content_type' => 'text/plain' ); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; foreach my $endpoint (qw/ test other_test /) { foreach my $method (qw(GET DELETE POST PUT OPTIONS)) { my $run_method = lc($method); my $res; if ( grep /$method/, qw(GET DELETE OPTIONS) ) { $res = request( $t->$run_method( url => '/actions/' . $endpoint ) ); } else { $res = request( $t->$run_method( url => '/actions/' . $endpoint, data => '', ) ); } ok( $res->is_success, "$method request succeeded" ) or warn Dumper($res); is( $res->content, "$method", "$method request had proper response" ); is( $res->header('X-Was-In-TopLevel'), '1', "went through top level action for dispatching to $method" ); is( $res->header('Using-Action'), 'STATION', "went through action for dispatching to $method" ); } } my $head_res = request( $t->head(url => '/actions/test') ); ok($head_res->is_success, 'HEAD request succeeded') or diag($head_res->code); ok(!$head_res->content, 'HEAD request had proper response'); my $res = request( $t->put( url => '/actions/test', data => '', ) ); is( $res->header('Using-Action'), 'STATION', "went through action for dispatching to PUT" ); is( $res->header('Using-Sub-Action'), 'MOO', "went through sub action for dispatching to PUT" ); done_testing; Callback.pm100644000765000024 115613211532667 26207 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serializepackage Catalyst::Action::Serialize::Callback; $Catalyst::Action::Serialize::Callback::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; sub execute { my $self = shift; my ( $controller, $c, $callbacks ) = @_; my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; my $output = $callbacks->{serialize}->( $c->stash->{$stash_key}, $controller, $c ); $c->response->output( $output ); return 1; } __PACKAGE__->meta->make_immutable; 1; catalyst-action-deserialize-multipart.t100644000765000024 177713211532667 26534 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Test::Requires qw(YAML::Syck); use YAML::Syck; use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; my $t = Test::Rest->new('content_type' => 'multipart/mixed; boundary=----------------------------0b922a55b662'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $url = '/deserializemultipart/test'; my $req = $t->put( url => $url, data => qq(------------------------------0b922a55b662\r\nContent-Disposition: form-data; name="REST"; filename="-"\r\nContent-Type: text/x-yaml\r\n\r\n---\r\nkitty: LouLou\r\n------------------------------0b922a55b662\r\nContent-Disposition: form-data; name="other"; filename="foo.txt"\r\nContent-Type: application/octet-stream\r\n\r\nanother part\r\n------------------------------0b922a55b662--\r\n)); my $res = request($req); ok( $res->is_success, 'PUT Deserialize request succeeded' ); is( $res->content, "LouLou|12", "Request returned deserialized data"); done_testing; JSON000755000765000024 013211532667 25074 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/DeserializeXS.pm100644000765000024 50713211532667 26106 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserialize/JSONpackage Catalyst::Action::Deserialize::JSON::XS; $Catalyst::Action::Deserialize::JSON::XS::VERSION = '1.21'; use Moose; use namespace::autoclean; BEGIN { $ENV{'PERL_JSON_BACKEND'} = 2; # Always use compiled JSON::XS } extends 'Catalyst::Action::Deserialize::JSON'; use JSON::XS (); __PACKAGE__->meta->make_immutable; 1; YAML000755000765000024 013211532667 24554 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/SerializeHTML.pm100644000765000024 275113211532667 26023 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serialize/YAMLpackage Catalyst::Action::Serialize::YAML::HTML; $Catalyst::Action::Serialize::YAML::HTML::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; use YAML::Syck; use URI::Find; sub execute { my $self = shift; my ( $controller, $c ) = @_; my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; my $app = $c->config->{'name'} || ''; my $output = ""; $output .= "" . $app . ""; $output .= "
";
    my $text = HTML::Entities::encode(Dump($c->stash->{$stash_key}));
    # Straight from URI::Find
    my $finder = URI::Find->new(
                              sub {
                                  my($uri, $orig_uri) = @_;
                                  my $newuri;
                                  if ($uri =~ /\?/) {
                                      $newuri = $uri . "&content-type=text/html";
                                  } else {
                                      $newuri = $uri . "?content-type=text/html";
                                  }
                                  return qq|$orig_uri|;
                              });
    $finder->find(\$text);
    $output .= $text;
    $output .= "
"; $output .= ""; $output .= ""; $c->response->output( $output ); return 1; } __PACKAGE__->meta->make_immutable; 1; DeserializeMultiPart.pm100644000765000024 677213211532667 26677 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Actionpackage Catalyst::Action::DeserializeMultiPart; $Catalyst::Action::DeserializeMultiPart::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action::Deserialize'; use HTTP::Body; our $NO_HTTP_BODY_TYPES_INITIALIZATION; $HTTP::Body::TYPES->{'multipart/mixed'} = 'HTTP::Body::MultiPart' unless $NO_HTTP_BODY_TYPES_INITIALIZATION; override execute => sub { my $self = shift; my ( $controller, $c ) = @_; if($c->request->content_type =~ m{^multipart/}i && !defined($c->request->body)){ my $REST_part = $self->attributes->{DeserializePart} || []; my($REST_body) = $c->request->upload($REST_part->[0] || 'REST'); if($REST_body){ $c->request->_body->body( $REST_body->fh ); $c->request->content_type( $REST_body->type ); } } super; }; __PACKAGE__->meta->make_immutable; 1; =head1 NAME Catalyst::Action::DeserializeMultiPart - Deserialize Data in a Multipart Request =head1 SYNOPSIS package Foo::Controller::Bar; __PACKAGE__->config( # see Catalyst::Action::Deserialize for standard config ); sub begin :ActionClass('DeserializeMultiPart') DeserializePart('REST') {} =head1 DESCRIPTION This action will deserialize multipart HTTP POST, PUT, OPTIONS and DELETE requests. It is a simple extension of L with the exception that rather than using the entire request body (which may contain multiple sections), it will look for a single part in the request body named according to the C attribute on that action (defaulting to C). If a part is found under that name, it then proceeds to deserialize the request as normal based on the content-type of that individual part. If no such part is found, the request would be processed as if no data was sent. This module's code will only come into play if the following conditions are met: =over 4 =item * The C of the request is C =item * The request body (as returned by C<$c->request->body> is not defined =item * There is a part of the request body (as returned by C<$c->request->upload($DeserializePart)>) available =back =head1 CONFIGURING HTTP::Body By default, L parses C requests as an L. L does not separate out the individual parts of the request body. In order to make use of the individual parts, L must be told which content types to map to L. This module makes the assumption that you would like to have all C requests parsed by L module. This is done by a package variable inside L: C<$HTTP::Body::Types> (a HASH ref). B As this module modifies the behaviour of HTTP::Body globally, adding it to an application can have unintended consequences as multipart bodies will be parsed differently from before. Feel free to add other content-types to this hash if needed or if you would prefer that C NOT be added to this hash, simply delete it after loading this module. # in your controller use Catalyst::Action::DeserializeMultiPart; delete $HTTP::Body::Types->{'multipart/mixed'}; $HTTP::Body::Types->{'multipart/my-crazy-content-type'} = 'HTTP::Body::MultiPart'; =head1 SEE ALSO This is a simple sub-class of L. =head1 AUTHORS See L for authors. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut Callback.pm100644000765000024 202513211532667 26514 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserializepackage Catalyst::Action::Deserialize::Callback; $Catalyst::Action::Deserialize::Callback::VERSION = '1.21'; use Moose; use namespace::autoclean; use Scalar::Util qw(openhandle); extends 'Catalyst::Action'; sub execute { my $self = shift; my ( $controller, $c, $callbacks ) = @_; my $rbody; # could be a string or a FH if ( my $body = $c->request->body ) { if(openhandle $body) { seek($body, 0, 0); # in case something has already read from it while ( defined( my $line = <$body> ) ) { $rbody .= $line; } } else { $rbody = $body; } } if ( $rbody ) { my $rdata = eval { $callbacks->{deserialize}->( $rbody, $controller, $c ) }; if ($@) { return $@; } $c->request->data($rdata); } else { $c->log->debug( 'I would have deserialized, but there was nothing in the body!') if $c->debug; } return 1; } __PACKAGE__->meta->make_immutable; 1; XML000755000765000024 013211532667 24452 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/SerializeSimple.pm100644000765000024 150613211532667 26403 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Serialize/XMLpackage Catalyst::Action::Serialize::XML::Simple; $Catalyst::Action::Serialize::XML::Simple::VERSION = '1.21'; use Moose; use namespace::autoclean; extends 'Catalyst::Action'; sub execute { my $self = shift; my ( $controller, $c ) = @_; eval { require XML::Simple }; if ($@) { $c->log->debug("Could not load XML::Serializer, refusing to serialize: $@") if $c->debug; return; } my $xs = XML::Simple->new(ForceArray => 0,); my $stash_key = ( $controller->{'serialize'} ? $controller->{'serialize'}->{'stash_key'} : $controller->{'stash_key'} ) || 'rest'; my $output = $xs->XMLout({ data => $c->stash->{$stash_key} }); $c->response->output( $output ); return 1; } __PACKAGE__->meta->make_immutable; 1; Serialize000755000765000024 013211532667 24767 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/broken/Catalyst/ActionBroken.pm100644000765000024 15613211532667 26667 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/broken/Catalyst/Action/Serializepackage Catalyst::Action::Serializer::Broken; use Moose; use namespace::autoclean; use Bilbo::Baggins; 1; XML000755000765000024 013211532667 24763 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/DeserializeSimple.pm100644000765000024 235113211532667 26713 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/Action/Deserialize/XMLpackage Catalyst::Action::Deserialize::XML::Simple; $Catalyst::Action::Deserialize::XML::Simple::VERSION = '1.21'; use Moose; use namespace::autoclean; use Scalar::Util qw(openhandle); extends 'Catalyst::Action'; sub execute { my $self = shift; my ( $controller, $c, $test ) = @_; eval { require XML::Simple; }; if ($@) { $c->log->debug("Could not load XML::Simple, refusing to deserialize: $@") if $c->debug; return 0; } my $body = $c->request->body; if ($body) { my $xs = XML::Simple->new('ForceArray' => 0,); my $rdata; eval { if(openhandle $body){ # make sure we rewind the file handle seek($body, 0, 0); # in case something has already read from it } $rdata = $xs->XMLin( $body ); }; if ($@) { return $@; } if (exists($rdata->{'data'})) { $c->request->data($rdata->{'data'}); } else { $c->request->data($rdata); } } else { $c->log->debug( 'I would have deserialized, but there was nothing in the body!') if $c->debug; } return 1; } __PACKAGE__->meta->make_immutable; 1; catalyst-traitfor-request-rest-forbrowsers.t100644000765000024 1331513211532667 27617 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use Catalyst::Request; use Catalyst::Request::REST::ForBrowsers; use Catalyst::TraitFor::Request::REST::ForBrowsers; use Moose::Meta::Class; use HTTP::Headers; use Catalyst::Log; my $anon_class = Moose::Meta::Class->create_anon_class( superclasses => ['Catalyst::Request'], roles => ['Catalyst::TraitFor::Request::REST::ForBrowsers'], cache => 1, )->name; # We run the tests twice to make sure Catalyst::Request::REST::ForBrowsers is # 100% back-compatible. for my $class ( $anon_class, 'Catalyst::Request::REST::ForBrowsers' ) { { for my $method (qw( GET POST PUT DELETE )) { my $req = $class->new( _log => Catalyst::Log->new, ); $req->method($method); $req->{_context} = 'MockContext'; $req->parameters( {} ); is( $req->method(), $method, "$method - not tunneled" ); } } { for my $method (qw( PUT DELETE )) { my $req = $class->new( _log => Catalyst::Log->new, ); $req->method('POST'); $req->{_context} = 'MockContext'; $req->parameters( { 'x-tunneled-method' => $method } ); is( $req->method(), $method, "$method - tunneled with x-tunneled-method param" ); } } { for my $method (qw( PUT DELETE )) { my $req = $class->new( _log => Catalyst::Log->new, ); $req->method('POST'); $req->{_context} = 'MockContext'; $req->header( 'x-http-method-override' => $method ); is( $req->method(), $method, "$method - tunneled with x-http-method-override header" ); } } { for my $method (qw( PUT DELETE )) { my $req = $class->new( _log => Catalyst::Log->new, ); $req->method('GET'); $req->{_context} = 'MockContext'; $req->parameters( { 'x-tunneled-method' => $method } ); is( $req->method(), 'GET', 'x-tunneled-method is ignore with a GET' ); } } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( {} ); $req->headers( HTTP::Headers->new() ); ok( $req->looks_like_browser(), 'default is a browser' ); } { for my $with (qw( HTTP.Request XMLHttpRequest )) { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->headers( HTTP::Headers->new( 'X-Requested-With' => $with ) ); ok( !$req->looks_like_browser(), "not a browser - X-Request-With = $with" ); } } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( { 'content-type' => 'text/json' } ); $req->headers( HTTP::Headers->new() ); ok( !$req->looks_like_browser(), 'forced non-HTML content-type is not a browser' ); } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( { 'content-type' => 'text/html' } ); $req->headers( HTTP::Headers->new() ); ok( $req->looks_like_browser(), 'forced HTML content-type is not a browser' ); } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( {} ); $req->headers( HTTP::Headers->new( 'Accept' => 'text/xml; q=0.4, */*; q=0.2' ) ); ok( $req->looks_like_browser(), 'if it accepts */* it is a browser' ); } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( {} ); $req->headers( HTTP::Headers->new( 'Accept' => 'text/html; q=0.4, text/xml; q=0.2' ) ); ok( $req->looks_like_browser(), 'if it accepts text/html it is a browser' ); } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( {} ); $req->headers( HTTP::Headers->new( 'Accept' => 'application/xhtml+xml; q=0.4, text/xml; q=0.2' ) ); ok( $req->looks_like_browser(), 'if it accepts application/xhtml+xml it is a browser' ); } { my $req = $class->new( _log => Catalyst::Log->new, ); $req->{_context} = 'MockContext'; $req->method('GET'); $req->parameters( {} ); $req->headers( HTTP::Headers->new( 'Accept' => 'text/json; q=0.4, text/xml; q=0.2' ) ); ok( !$req->looks_like_browser(), 'provided an Accept header but does not accept html, is not a browser' ); } } done_testing; package MockContext; sub prepare_body { } Deserialize000755000765000024 013211532667 25300 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/broken/Catalyst/ActionBroken.pm100644000765000024 15613211532667 27200 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/broken/Catalyst/Action/Deserializepackage Catalyst::Action::Serializer::Broken; use Moose; use namespace::autoclean; use Bilbo::Baggins; 1; REST000755000765000024 013211532667 24605 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/TraitFor/RequestForBrowsers.pm100644000765000024 1303113211532667 27576 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/lib/Catalyst/TraitFor/Request/RESTpackage Catalyst::TraitFor::Request::REST::ForBrowsers; $Catalyst::TraitFor::Request::REST::ForBrowsers::VERSION = '1.21'; use Moose::Role; use namespace::autoclean; with 'Catalyst::TraitFor::Request::REST'; has _determined_real_method => ( is => 'rw', isa => 'Bool', ); has looks_like_browser => ( is => 'rw', isa => 'Bool', lazy => 1, builder => '_build_looks_like_browser', init_arg => undef, ); # All this would be much less gross if Catalyst::Request used a builder to # determine the method. Then we could just wrap the builder. around method => sub { my $orig = shift; my $self = shift; return $self->$orig(@_) if @_ || $self->_determined_real_method; my $method = $self->$orig(); my $tunneled; if ( defined $method && uc $method eq 'POST' ) { $tunneled = $self->param('x-tunneled-method') || $self->header('x-http-method-override'); } $self->$orig( defined $tunneled ? uc $tunneled : $method ); $self->_determined_real_method(1); return $self->$orig(); }; { my %HTMLTypes = map { $_ => 1 } qw( text/html application/xhtml+xml ); sub _build_looks_like_browser { my $self = shift; my $with = $self->header('x-requested-with'); return 0 if $with && grep { $with eq $_ } qw( HTTP.Request XMLHttpRequest ); if ( uc $self->method eq 'GET' ) { my $forced_type = $self->param('content-type'); return 0 if $forced_type && !$HTMLTypes{$forced_type}; } # IE7 does not say it accepts any form of html, but _does_ # accept */* (helpful ;) return 1 if $self->accepts('*/*'); return 1 if grep { $self->accepts($_) } keys %HTMLTypes; return 0 if @{ $self->accepted_content_types() }; # If the client did not specify any content types at all, # assume they are a browser. return 1; } } 1; __END__ =pod =head1 NAME Catalyst::TraitFor::Request::REST::ForBrowsers - A request trait for REST and browsers =head1 SYNOPSIS package MyApp; use Moose; use namespace::autoclean; use Catalyst; use CatalystX::RoleApplicator; extends 'Catalyst'; __PACKAGE__->apply_request_class_roles(qw[ Catalyst::TraitFor::Request::REST::ForBrowsers ]); =head1 DESCRIPTION Writing REST-y apps is a good thing, but if you're also trying to support web browsers, you're probably going to need some hackish workarounds. This module provides those workarounds for you. Specifically, it lets you do two things. First, it lets you "tunnel" PUT and DELETE requests across a POST, since most browsers do not support PUT or DELETE actions (as of early 2009, at least). Second, it provides a heuristic to check if the client is a web browser, regardless of what content types it claims to accept. The reason for this is that while a browser might claim to accept the "application/xml" content type, it's really not going to do anything useful with it, and you're best off giving it HTML. =head1 METHODS This class provides the following methods: =head2 $request->method This method works just like C<< Catalyst::Request->method() >> except it allows for tunneling of PUT and DELETE requests via a POST. Specifically, you can provide a form element named "x-tunneled-method" which can override the request method for a POST. This I works for a POST, not a GET. You can also use a header named "x-http-method-override" instead (Google uses this header for its APIs). =head2 $request->looks_like_browser This attribute provides a heuristic to determine whether or not the request I to come from a browser. You can use this however you want. I usually use it to determine whether or not to give the client a full HTML page or some sort of serialized data. This is a heuristic, and like any heuristic, it is probably wrong sometimes. Here is how it works: =over 4 =item * If the request includes a header "X-Request-With" set to either "HTTP.Request" or "XMLHttpRequest", this returns false. The assumption is that if you're doing XHR, you don't want the request treated as if it comes from a browser. =item * If the client makes a GET request with a query string parameter "content-type", and that type is I an HTML type, it is I a browser. =item * If the client provides an Accept header which includes "*/*" as an accepted content type, the client is a browser. Specifically, it is IE7, which submits an Accept header of "*/*". IE7's Accept header does not include any html types like "text/html". =item * If the client provides an Accept header and accepts either "text/html" or "application/xhtml+xml" it is a browser. =item * If it provides an Accept header of any sort that doesn't match one of the above criteria, it is I a browser. =item * The default is that the client is a browser. =back This all works well for my apps, but read it carefully to make sure it meets your expectations before using it. =head1 AUTHOR Dave Rolsky, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. We will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 COPYRIGHT & LICENSE Copyright 2008-2010 Dave Rolsky, All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Controller000755000765000024 013211532667 26145 5ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/RESTREST.pm100644000765000024 453413211532667 27426 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::REST; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller::REST' } __PACKAGE__->config( 'map' => { 'text/html' => 'YAML::HTML', 'text/x-yaml' => 'YAML', }); sub test : Local { my ( $self, $c ) = @_; $self->status_ok( $c, entity => { test => 'worked', data => $c->req->data } ); } sub test_status_created : Local { my ( $self, $c ) = @_; $self->status_created( $c, location => '/rest', entity => { status => 'created' } ); } sub test_status_multiple_choices : Local { my ( $self, $c ) = @_; $self->status_multiple_choices( $c, location => '/rest/choice1', entity => { choices => [qw(/rest/choice1 /rest/choice2)] } ); } sub test_status_found : Local { my ( $self, $c ) = @_; $self->status_found( $c, location => '/rest', entity => { status => 'found' }, ); } sub test_status_accepted : Local { my ( $self, $c ) = @_; $self->status_accepted( $c, location => '/rest', entity => { status => "queued", } ); } sub test_status_no_content : Local { my ( $self, $c ) = @_; $self->status_no_content($c); } sub test_status_bad_request : Local { my ( $self, $c ) = @_; $self->status_bad_request( $c, message => "Cannot do what you have asked!", ); } sub test_status_forbidden : Local { my ( $self, $c ) = @_; $self->status_forbidden ( $c, message => "access denied", ); } sub test_status_not_found : Local { my ( $self, $c ) = @_; $self->status_not_found( $c, message => "Cannot find what you were looking for!", ); } sub test_status_gone : Local { my ( $self, $c ) = @_; $self->status_gone( $c, message => "Document have been deleted by foo", ); } sub test_status_see_other : Local { my ( $self, $c ) = @_; $self->status_see_other( $c, location => '/rest', entity => { somethin => 'happenin' } ); } sub opts : Local ActionClass('REST') {} sub opts_GET { my ( $self, $c ) = @_; $self->status_ok( $c, entity => { opts => 'worked' } ); } sub opts_not_implemented { my ( $self, $c ) = @_; $c->res->status(405); $c->res->header('Allow' => [qw(GET HEAD)]); $c->res->body('Not implemented'); } 1; Root.pm100644000765000024 327313211532667 27573 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::Root; use Moose; use namespace::autoclean; BEGIN { extends qw/Catalyst::Controller::REST/ } __PACKAGE__->config( namespace => '' ); sub begin {} # Don't need serialization.. sub test : Local : ActionClass('REST') { my ( $self, $c ) = @_; $c->stash->{'entity'} = 'something'; } sub test_GET { my ( $self, $c ) = @_; $c->stash->{'entity'} .= " GET"; $c->forward('ok'); } sub test_POST { my ( $self, $c ) = @_; $c->stash->{'entity'} .= " POST"; $c->forward('ok'); } sub test_PUT { my ( $self, $c ) = @_; $c->stash->{'entity'} .= " PUT"; $c->forward('ok'); } sub test_DELETE { my ( $self, $c ) = @_; $c->stash->{'entity'} .= " DELETE"; $c->forward('ok'); } sub test_OPTIONS { my ( $self, $c ) = @_; $c->stash->{'entity'} .= " OPTIONS"; $c->forward('ok'); } sub notreally : Local : ActionClass('REST') { } sub notreally_GET { my ( $self, $c ) = @_; $c->stash->{'entity'} = "notreally GET"; $c->forward('ok'); } sub not_implemented : Local : ActionClass('REST') { } sub not_implemented_GET { my ( $self, $c ) = @_; $c->stash->{'entity'} = "not_implemented GET"; $c->forward('ok'); } sub not_implemented_not_implemented { my ( $self, $c ) = @_; $c->stash->{'entity'} = "Not Implemented Handler"; $c->forward('ok'); } sub not_modified : Local : ActionClass('REST') { } sub not_modified_GET { my ( $self, $c ) = @_; $c->res->status(304); return 1; } sub ok : Private { my ( $self, $c ) = @_; $c->res->content_type('text/plain'); $c->res->body( $c->stash->{'entity'} ); } sub end : Private {} # Don't need serialization.. 1; catalyst-request-rest-custom-rest-request-class.t100644000765000024 127513211532667 30452 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use Moose (); use lib ( "$FindBin::Bin/lib" ); my $test = 'Test::Catalyst::Action::REST'; my $meta = Moose::Meta::Class->create_anon_class( # The test app has ForBrowsers actions, so we need that to not have # the request class replaced superclasses => ['Catalyst::Request::REST::ForBrowsers'], ); $ENV{CAR_TEST_REQUEST_CLASS} = $meta->name; use_ok $test; ok($test->request_class->does('Catalyst::TraitFor::Request::REST'), 'Request class does Catalyst::TraitFor::Request::REST'); is $test->request_class, $meta->name, 'Request class kept'; ok $test->request_class->can('data'), 'Also smells like REST subclass'; done_testing; catalyst-action-rest-action-dispatch-for-browsers.t100644000765000024 216413211532667 30661 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use lib ( "$FindBin::Bin/lib", "$FindBin::Bin/../lib" ); use Test::Rest; my $t = Test::Rest->new( 'content_type' => 'text/plain' ); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; my $url = '/actionsforbrowsers/for_browsers'; foreach my $method (qw(GET POST)) { my $run_method = lc($method); my $result = "something $method"; my $res; if ( $method eq 'GET' ) { $res = request( $t->$run_method( url => $url ) ); } else { $res = request( $t->$run_method( url => $url, data => '', ) ); } ok( $res->is_success, "$method request succeeded" ); is( $res->content, "$method", "$method request had proper response" ); } my $res = request( $t->get( url => $url, headers => { Accept => 'text/html' }, ) ); ok( $res->is_success, "GET request succeeded (client looks like browser)" ); is( $res->content, "GET_html", "GET request had proper response (client looks like browser)" ); done_testing; Actions.pm100644000765000024 313113211532667 30241 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::Actions; use Moose; use namespace::autoclean; BEGIN { extends qw/Catalyst::Controller::REST/ } __PACKAGE__->_action_class('Test::Action::Class'); sub begin {} # Don't need serialization.. sub test : Local : ActionClass('+Catalyst::Action::REST') { my ( $self, $c ) = @_; $c->res->header('X-Was-In-TopLevel', 1); } sub test_GET : Private { my ( $self, $c ) = @_; $c->res->body('GET'); } sub test_POST : Action { my ( $self, $c ) = @_; $c->res->body('POST'); } sub test_PUT :ActionClass('+Test::Action::Class::Sub') { my ( $self, $c ) = @_; $c->res->body('PUT'); } sub test_DELETE : Local { my ( $self, $c ) = @_; $c->res->body('DELETE'); } sub test_OPTIONS : Path('foobar') { my ( $self, $c ) = @_; $c->res->body('OPTIONS'); } sub other_test :Local :ActionClass('+Catalyst::Action::REST') { my ( $self, $c ) = @_; $c->res->header('X-Was-In-TopLevel', 1); } sub other_test_GET { my ( $self, $c ) = @_; $c->res->body('GET'); } sub other_test_POST { my ( $self, $c ) = @_; $c->res->body('POST'); } sub other_test_PUT :ActionClass('+Test::Action::Class::Sub') { my ( $self, $c ) = @_; $c->res->body('PUT'); } sub other_test_DELETE { my ( $self, $c ) = @_; $c->res->body('DELETE'); } sub other_test_OPTIONS { my ( $self, $c ) = @_; $c->res->body('OPTIONS'); } sub yet_other_test : Local : ActionClass('+Catalyst::Action::REST') {} sub yet_other_test_POST { my ( $self, $c ) = @_; $c->res->body('POST'); } sub end : Private {} # Don't need serialization.. 1; catalyst-request-rest-custom-nonrest-request-class.t100644000765000024 127713211532667 31167 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/tuse strict; use warnings; use Test::More; use FindBin; use Moose (); use lib ( "$FindBin::Bin/lib" ); my $test = 'Test::Catalyst::Action::REST'; my $meta = Moose::Meta::Class->create_anon_class( superclasses => ['Catalyst::Request'], ); $meta->add_method('__random_method' => sub { 42 }); $ENV{CAR_TEST_REQUEST_CLASS} = $meta->name; use_ok $test; ok($test->request_class->does('Catalyst::TraitFor::Request::REST'), 'Request class does Catalyst::TraitFor::Request::REST'); isnt $test->request_class, $meta->name, 'Different request class'; ok $test->request_class->can('__random_method'), 'Is right class'; ok $test->request_class->can('data'), 'Also smells like REST subclass'; done_testing; Override.pm100644000765000024 67013211532667 30405 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::Override; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } __PACKAGE__->config( 'default' => 'application/json', 'map' => { 'application/json' => 'YAML', # Yes, this is deliberate! }, ); sub test :Local :ActionClass('Serialize') { my ( $self, $c ) = @_; $c->stash->{'rest'} = { lou => 'is my cat', }; } 1; Serialize.pm100644000765000024 274013211532667 30575 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::Serialize; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } __PACKAGE__->config( 'default' => 'text/x-yaml', 'stash_key' => 'rest', 'map' => { 'text/x-yaml' => 'YAML', 'application/json' => 'JSON', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/broken' => 'Broken', }, ); sub test :Local :ActionClass('Serialize') { my ( $self, $c ) = @_; $c->stash->{'rest'} = { lou => 'is my cat', }; } sub test_second :Local :ActionClass('Serialize') { my ( $self, $c ) = @_; # 'serialize_content_type' is configured in the test config in t/conf $c->stash->{'serialize_content_type'} = $c->req->params->{'serialize_content_type'}; $c->stash->{'rest'} = { lou => 'is my cat', }; } # For testing saying 'here is an explicitly empty body, do not serialize' sub empty : Chained('/') PathPart('serialize') CaptureArgs(0) { my ($self, $c) = @_; $c->stash( rest => { foo => 'bar' } ); } # Normal case sub empty_serialized :Chained('empty') Args(0) ActionClass('Serialize') { } # Blank body sub empty_not_serialized_blank :Chained('empty') Args(0) ActionClass('Serialize') { my ($self, $c) = @_; $c->res->body(''); } # Explicitly set a view sub explicit_view :Chained('empty') Args(0) ActionClass('Serialize') { my ($self, $c) = @_; $c->stash->{current_view} = ''; } 1; Deserialize.pm100644000765000024 142313211532667 31103 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::Deserialize; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } __PACKAGE__->config( 'action_args' => { 'test_action_args' => { 'deserialize_http_methods' => [qw(POST PUT OPTIONS DELETE GET)] } }, 'stash_key' => 'rest', 'map' => { 'text/x-yaml' => 'YAML', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/broken' => 'Broken', }, ); sub test :Local :ActionClass('Deserialize') { my ( $self, $c ) = @_; $c->res->output($c->req->data->{'kitty'}); } sub test_action_args :Local :ActionClass('Deserialize') { my ( $self, $c ) = @_; $c->res->output($c->req->data->{'kitty'}); } 1; ActionsForBrowsers.pm100644000765000024 124113211532667 32437 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::ActionsForBrowsers; use Moose; use namespace::autoclean; BEGIN { extends qw/Catalyst::Controller::REST/ } sub begin {} # Don't need serialization.. sub for_browsers : Local : ActionClass('REST::ForBrowsers') { my ( $self, $c ) = @_; $c->res->header('X-Was-In-TopLevel', 1); } sub for_browsers_GET : Private { my ( $self, $c ) = @_; $c->res->body('GET'); } sub for_browsers_GET_html : Private { my ( $self, $c ) = @_; $c->res->body('GET_html'); } sub for_browsers_POST : Private { my ( $self, $c ) = @_; $c->res->body('POST'); } sub end : Private {} # Don't need serialization.. 1; DeserializeMultiPart.pm100644000765000024 113313211532667 32743 0ustar00jnapiorkowskistaff000000000000Catalyst-Action-REST-1.21/t/lib/Test/Catalyst/Action/REST/Controllerpackage Test::Catalyst::Action::REST::Controller::DeserializeMultiPart; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } __PACKAGE__->config( 'stash_key' => 'rest', 'map' => { 'text/x-yaml' => 'YAML', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], 'text/broken' => 'Broken', }, ); sub test :Local ActionClass('DeserializeMultiPart') DeserializePart('REST') { my ( $self, $c ) = @_; $DB::single=1; $c->res->output($c->req->data->{'kitty'} . '|' . $c->req->uploads->{other}->size); } 1;