{"id":16307854,"url":"https://github.com/ap/router-resource","last_synced_at":"2025-03-22T20:33:33.204Z","repository":{"id":1115633,"uuid":"985699","full_name":"ap/Router-Resource","owner":"ap","description":"Build REST-inspired routing tables","archived":false,"fork":false,"pushed_at":"2022-09-04T01:57:57.000Z","size":34,"stargazers_count":9,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T14:23:00.148Z","etag":null,"topics":["http","perl","rest"],"latest_commit_sha":null,"homepage":"https://metacpan.org/release/Router-Resource","language":"Perl","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ap.png","metadata":{"files":{"readme":"README.pod","changelog":"Changes","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-10-14T01:27:18.000Z","updated_at":"2024-03-14T16:04:48.000Z","dependencies_parsed_at":"2022-08-16T12:05:13.497Z","dependency_job_id":null,"html_url":"https://github.com/ap/Router-Resource","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ap%2FRouter-Resource","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ap%2FRouter-Resource/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ap%2FRouter-Resource/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ap%2FRouter-Resource/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ap","download_url":"https://codeload.github.com/ap/Router-Resource/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245020147,"owners_count":20548154,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","perl","rest"],"created_at":"2024-10-10T21:15:30.351Z","updated_at":"2025-03-22T20:33:32.905Z","avatar_url":"https://github.com/ap.png","language":"Perl","readme":"use 5.008001; use strict; use warnings;\n\npackage Router::Resource;\n\nour $VERSION = '0.22';\n\nuse Router::Simple::Route;\nuse Sub::Exporter -setup =\u003e {\n    exports =\u003e [ qw(router resource missing GET POST PUT DELETE HEAD OPTIONS TRACE CONNECT PATCH)],\n    groups  =\u003e { default =\u003e [ qw(resource router missing GET POST PUT DELETE HEAD OPTIONS TRACE CONNECT PATCH) ] }\n};\n\nsub new {\n    my $class = shift;\n    bless { @_, routes =\u003e [] };\n}\n\nour (%METHS, $ROUTER);\n\nsub router(\u0026;@) {\n    my ($block, @settings) = @_;\n    local $ROUTER = __PACKAGE__-\u003enew(@settings);\n    $block-\u003e();\n    return $ROUTER;\n}\n\nsub resource ($\u0026) {\n    my ($path, $code) = @_;\n    local %METHS = ();\n    $code-\u003e();\n\n    # Let HEAD use GET if not specified.\n    $METHS{HEAD} ||= $METHS{GET};\n\n    # Add OPTIONS if requested.\n    if ($ROUTER-\u003e{auto_options} \u0026\u0026 !$METHS{OPTIONS}) {\n        my $methods = join(', ' =\u003e 'OPTIONS', keys %METHS);\n        $METHS{OPTIONS} = sub { [200, ['Allow', $methods], []] };\n    }\n\n    # Add the route.\n    push @{ $ROUTER-\u003e{routes} }, Router::Simple::Route-\u003enew(\n        $path, { meths =\u003e { %METHS } }\n    );\n}\n\nsub missing(\u0026) { $ROUTER-\u003e{missing} = shift }\nsub GET(\u0026)     { $METHS{GET}     = shift }\nsub HEAD(\u0026)    { $METHS{HEAD}    = shift }\nsub POST(\u0026)    { $METHS{POST}    = shift }\nsub PUT(\u0026)     { $METHS{PUT}     = shift }\nsub DELETE(\u0026)  { $METHS{DELETE}  = shift }\nsub OPTIONS(\u0026) { $METHS{OPTIONS} = shift }\nsub TRACE(\u0026)   { $METHS{TRACE}   = shift }\nsub CONNECT(\u0026) { $METHS{CONNECT} = shift }\nsub PATCH(\u0026)   { $METHS{PATCH}   = shift }\n\nsub dispatch {\n    my ($self, $env) = @_;\n    my $match = $self-\u003ematch($env);\n    if (my $meth = $match-\u003e{meth}) {\n        return $meth-\u003e($env, $match-\u003e{data});\n    }\n    my $missing = $self-\u003e{missing} or return [\n        $match-\u003e{code}, $match-\u003e{headers}, [$match-\u003e{message}]\n    ];\n    return $missing-\u003e($env, $match);\n}\n\nsub match {\n    my ($self, $env) = @_;\n    my $meth = uc($env-\u003e{REQUEST_METHOD} || '') or return;\n\n    for my $route (@{ $self-\u003e{routes} }) {\n        my $match = $route-\u003ematch($env) or next;\n        my $meths = delete $match-\u003e{meths};\n        my $code = $meths-\u003e{$meth} or return {\n            code    =\u003e 405,\n            message =\u003e 'not allowed',\n            headers =\u003e [Allow =\u003e join ', ', sort keys %{ $meths } ],\n        };\n        return { meth =\u003e $code, code =\u003e 200, data =\u003e $match };\n    }\n    return { code =\u003e 404, message =\u003e 'not found', headers =\u003e [] };\n}\n\n1;\n\n__END__\n\n=pod\n\n=encoding UTF-8\n\n=head1 NAME\n\nRouter::Resource - Build REST-inspired routing tables\n\n=head1 SYNOPSIS\n\n  use Router::Resource;\n  use Plack::Builder;\n  use namespace::autoclean;\n\n  sub app {\n      # Create a routing table.\n      my $router = router {\n          resource '/' =\u003e sub {\n              GET  { $template-\u003erender('home') };\n          };\n\n          resource '/blog/{year}/{month}' =\u003e sub {\n              GET  { [200, [], [ $template-\u003erender({ posts =\u003e \\@posts }) ] };\n              POST { push @posts, new_post(shift); [200, [], ['ok']] };\n          };\n      };\n\n      # Build the Plack app to use it.\n      builder {\n          sub { $router-\u003edispatch(shift) };\n      };\n  }\n\n=head1 DESCRIPTION\n\nThere are a bunch of path routers on CPAN, but they tend not to be very RESTy.\nA basic idea of a RESTful API is that URIs point to resources and the standard\nHTTP methods indicate the actions to be taken on those resources. So to\nencourage you to think about it that way, Router::Resource requires that you\ndeclare resources and then the HTTP methods that are implemented for those\nresources.\n\nThe rules for matching paths are defined by\nL\u003cRouter::Simple's routing rules|Router::Simple/HOW TO WRITE A ROUTING RULE\u003e,\nwhich offer quite a lot of flexibility.\n\n=head1 INTERFACE\n\nYou create a router in a C\u003crouter\u003e block. Within that block, define resources\nunderstood by the router with the C\u003cresource\u003e keyword, which takes a resource\npath and a block defining its interface:\n\n  my $router = {\n      resource '/'    =\u003e sub { [[200, [], ['ok']] };\n      resource '/foo' =\u003e sub { [[200, [], ['ok']] };\n  };\n\nWithin a resource block, declare the HTTP methods that the resource responds\nto by using one or more of the following keywords:\n\n=over\n\n=item C\u003cGET\u003e\n\n=item C\u003cHEAD\u003e\n\n=item C\u003cPOST\u003e\n\n=item C\u003cPUT\u003e\n\n=item C\u003cDELETE\u003e\n\n=item C\u003cOPTIONS\u003e\n\n=item C\u003cTRACE\u003e\n\n=item C\u003cCONNECT\u003e\n\n=item C\u003cPATCH\u003e\n\n=back\n\nNote that if you define a C\u003cGET\u003e method but not a C\u003cHEAD\u003e method, the C\u003cGET\u003e\nmethod will respond to C\u003cHEAD\u003e requests.\n\nThese methods should expect two arguments: the matched request (generally a\nL\u003cPSGI\u003e C\u003c$env\u003e hash) and a hash of the matched data as created by\nRouter::Simple. For example, in a L\u003cPlack\u003e-powered Wiki app you might do\nsomething like this:\n\n  resource '/wiki/{name}' =\u003e sub {\n      GET {\n          my $req    = Plack::Request-\u003enew(shift);\n          my $params = shift;\n          my $wiki   = Wiki-\u003elookup( $params-\u003e{name} );\n          my $res    = $req-\u003enew_response;\n          $res-\u003econtent_type('text/html; charset=UTF-8');\n          $res-\u003ebody($wiki);\n          return $res-\u003efinalize;\n      };\n  };\n\nBut of course you can abstract that into a controller or other code that the\nHTTP method simply dispatches to.\n\nIf you wish the router to create an C\u003cOPTIONS\u003e handler for you, pass the\nC\u003cauto_options\u003e parameter to C\u003crouter\u003e:\n\n    $router = router {\n        resource '/blog/{year}/{month}' =\u003e sub {\n            GET  { [200, [], [ $template-\u003erender({ posts =\u003e \\@posts }) ] };\n            POST { push @posts, new_post(shift); [200, [], ['ok']] };\n        };\n    } auto_options =\u003e 1;\n\nWith C\u003cauto_options\u003e enabled, Router::Resource will look at the methods\ndefined for a resource to define the C\u003cOPTIONS\u003e handler. In this example,\nC\u003c$router\u003e's C\u003cOPTIONS\u003e method will specify that C\u003cGET\u003e, C\u003cHEAD\u003e, and\nC\u003cOPTIONS\u003e are valid for C\u003c/blog/{year}/{month}\u003e.\n\n=head2 Dispatching\n\nUse the C\u003cdispatch\u003e method to have the router dispatch HTTP requests. For a\n Plack app, it looks something like this:\n\n  sub { $router-\u003edispatch(shift) };\n\nThe assumption is that the methods you've defined will return a\nL\u003cPSGI\u003e-compatible array reference. When the router finds no matching resource\nor method, such an array is precisely what I\u003cit\u003e will return. When a resource\ncannot be found, it will return\n\n  [404, [], ['not found']]\n\nIf the resource is found but the requested method is not defined, it returns\nsomething like:\n\n  [405, [Allow =\u003e 'GET, HEAD'], ['not allowed']]\n\nThe \"Allow\" header will list the methods that the requested resource I\u003cdoes\u003e\nrespond to.\n\nOf course you may not want something so simple for your app. So use the\nC\u003cmissing\u003e keyword to specify a code block to handle this situation. The code\nblock should expect two arguments: the unmatched request C\u003c$env\u003e hash and a\nhash describing the failure. For an unfound resource, that hash will contain:\n\n  { code =\u003e 404, message =\u003e 'not found', headers =\u003e [] }\n\nIf a resource was found but it does not define the requested method, the hash\nwill look something like this:\n\n  { code =\u003e 405, message =\u003e 'not allowed', headers =\u003e [Allow =\u003e 'GET, HEAD'] }\n\nThis is designed to make it relatively easy to create a custom response to\nunfound resources and missing methods. Something like:\n\n  missing {\n      my $req    = Plack::Request-\u003enew(shift);\n      my $params = shift;\n      my $res    = $req-\u003enew_response($params-\u003e{code});\n      $res-\u003eheaders(@{ $params-\u003e{headers} });\n      $res-\u003econtent_type('text/html; charset=UTF-8');\n      $res-\u003ebody($template-\u003eshow('not_found', $params));\n      return $res-\u003efinalize;\n  };\n\n=begin private\n\nXXX Document C\u003cmatch\u003e or not?\n\n=head2 Matches\n\nThe C\u003cdistpatch\u003e method relies on the C\u003cmatch\u003e method to find the requested\nresources and the methods to execute. If you find that C\u003cdispatch\u003e isn't quite\nwhat you need, you can use C\u003cmatch\u003e and do the work yourself. The C\u003cmatch\u003e\nmethod returns a hash describing the match (or lack of match). The keys that\nmay be found in that hash are:\n\n=over\n\n=item C\u003ccode\u003e\n\nAn HTTP status code. Possible values are 200 for a successful match, 404 when\nthe resource cannot be found, and 405 when the resource does not support the\nrequested method.\n\n=item C\u003cmeth\u003e\n\nThe code reference that defines the method that was found for the resource.\nAlways set when C\u003ccode\u003e is 200.\n\n=item C\u003cdata\u003e\n\nThe data matched by Router::Simple. Undefined unless the C\u003ccode\u003e is 200.\n\n=item C\u003cheaders\u003e\n\nAn array reference of headers to be used in the response. Undefined when\nC\u003ccode\u003e is 200, an empty array for 404, and containing the \"Allow\" header\nfor 405.\n\n=back\n\nUse the result hash to determine how to respond. An example:\n\n  sub {\n      my $env = shift;\n      my $match = $router-\u003ematch($env);\n      if (my $meth = $match-\u003e{meth}) {\n          # We have a match!\n          return $meth-\u003e($env, $match-\u003e{data});\n      }\n\n      if ($match-\u003e{code} == 404) {\n          return [404, $match-\u003e{headers}, ['Nothing found, look elsewhere']];\n      } else {\n          return [405, $match-\u003e{headers}, [\n              \"Sorry, but $env-\u003e{PATH_INFO}\" does't respond to \"\n            . \"$env-\u003e{REQUEST_METHOD}. Try any of these: \"\n            . $match-\u003e{headers}[0][1]\n          ]];\n      }\n  };\n\n\nLikely you won't need this, though, as C\u003cdispatch\u003e should cover the vast\nmajority of needs.\n\n=end private\n\n=head1 SEE ALSO\n\n=over\n\n=item *\n\nL\u003cRouter::Simple\u003e provides the rule syntax for Router::Resource resource paths.\n\n=item *\n\nL\u003cRouter::Simple::Sinatraish\u003e provides a\nL\u003cSinatraish|http://www.sinatrarb.com/\u003e routing table interface. It's nice,\nthough perhaps a bit too magical.\n\n=item *\n\nL\u003cSinatra::Resources|http://github.com/nateware/sinatra-resources\u003e - The Ruby\nmodule that inspired this module.\n\n=item *\n\nL\u003cPlack\u003e is B\u003cthe\u003e way to write your Perl web apps. Router::Resource is fully\nPlack-aware.\n\n=back\n\n=head1 ACKNOWLEDGEMENTS\n\nThanks to the denizens of #plack for their feedback and advice on this module,\nincluding:\n\n=over\n\n=item * Hans Dieter Pearcey (confound)\n\n=item * Florian Ragwitz (rafl)\n\n=item * Paul Evans (LeoNerd)\n\n=item * Matt S Trout (mst)\n\n=item * Tatsuhiko Miyagawa (miyagawa)\n\n=item * Pedro Melo (melo)\n\n=back\n\n=cut\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fap%2Frouter-resource","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fap%2Frouter-resource","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fap%2Frouter-resource/lists"}