diff --git a/lib/Dancer2/Manual.pod b/lib/Dancer2/Manual.pod index ed06348db..1d4f43db3 100644 --- a/lib/Dancer2/Manual.pod +++ b/lib/Dancer2/Manual.pod @@ -201,117 +201,71 @@ The following will match GET or POST requests to C: # Write code to do something at the visitor center! }; -=head2 Organizing routes using prefix +=head2 URI Generation -To organize your routes more effectively in Danceyland, you can use the -C DSL keyword to group related routes under specific sections of -the park. +TODO: describe why -=head3 Example: Using C in Danceyland +=head3 uri_for -Imagine Danceyland has different sections, like the I and -I. You can use C to organize these areas: +The C keyword is used to generate a URI for a given path within +your application, including query parameters. It's especially useful +when you want to construct URLs dynamically inside your routes. - package MyApp; - use Dancer2; +=head4 Example: Generating a URI in Danceyland - # Prefix for Zoo Kingdom - prefix '/zoo-kingdom' => sub { - get '/animals' => sub { - return "Welcome to the Zoo Kingdom! Here you can find all the animals."; - }; - - get '/feed-schedule' => sub { - return "Feeding Schedule: 10 AM and 4 PM."; - }; - }; - - # Prefix for Actionland - prefix '/actionland' => sub { - get '/thrill-rides' => sub { - return "Welcome to Actionland! Enjoy the thrill rides."; - }; - - get '/roller-coaster' => sub { - return "The best roller coaster in the park!"; - }; + get '/ride/:name' => sub { + my $ride_name = route_parameters->get('name'); + return 'Enjoy the ride at ' . uri_for("/ride/$ride_name"); }; - MyApp->to_app(); +In this example, C generates a full URI for the ride name in +Danceyland. -This example organizes routes by grouping all animal-related activities -under C and all rides under C. +=head3 uri_for_route -=head2 Controlling the flow with forward, redirect, and pass +The C keyword creates a URL for a named route. In +Danceyland, it can be used to generate a URL to any part of the park +that has a named route. -These DSL keywords in Dancer2 allow you to manage the flow of actions -within your routes. Here’s how they work in Danceyland. +For more information, see L. -=head3 forward - -C allows you to forward the current request to another route -handler, as if it were redirected, but without sending a new HTTP request. -Put another way, a different route than the one requested is processed, -but the address in the user's browser's bar stays the same. - -=head4 Example +=head3 Example 1: Basic Usage - get '/guest-services' => sub { - forward '/info'; # Forwards to /info + get 'animals' => '/zoo-kingdom/animals' => sub { + return "Visit the animals at " . uri_for_route('animals'); }; - get '/info' => sub { - return "Welcome to Guest Services. How can we help you today?"; - }; - -In this example, when a visitor goes to C, they are -forwarded to the C route internally, however, the vistor's browser -still shows they are in C. +In this example, the route is named C, and C +generates a URL pointing to it. -=head3 redirect +=head3 Example 2: Using Route Parameters -C sends an HTTP redirect to the client, instructing their -browser to make a new request to a different URL. At the end of the -request, the user's browser will show a different URL than the one -they originally navigated to. - -=head4 Example - - get '/old-roller-coaster' => sub { - redirect '/new-roller-coaster'; - }; - - get '/new-roller-coaster' => sub { - return "Welcome to the new and improved roller coaster!"; + get 'ride' => '/ride/:name' => sub { + my $ride_name = route_parameters->get('name'); + return "Ride details: " . uri_for_route('ride', { name => $ride_name }); }; -When a visitor requests C, they are redirected to -C. The browser's URL will now show C. +This example uses C to generate a URL that includes the +named route parameter C. -=head3 pass +=head3 Example 3: Including Query Parameters -C tells Dancer2 to skip the current route and continue looking for -the next matching route. - -=head4 Example - - get '/vip-area' => sub { - if (!session('is_vip')) { - pass; # Skip to the next matching route if the user is not VIP - } - return "Welcome to the VIP area!"; + get 'search' => '/search' => sub { + return uri_for_route('search', { q => 'roller coaster' }); }; - get '/vip-area' => sub { - return "Access Denied. This area is for VIPs only."; + get '/search' => sub { + my $query = query_parameters->get('q'); + return "Search results for: $query"; }; -In this example, if the session doesn’t indicate the user is a VIP, the -request will skip to the next matching route, which denies access. +In this example, C generates a URL for the named route +C with the query parameter C set to "roller coaster". =head2 Parameter Handling TODO: need an explanation here +Parameters are how visitors of Danceyland communicate information to park staff. =head3 Keywords for working with parameters @@ -353,9 +307,8 @@ Example of C with multiple values: =head4 query_parameters -This keyword retrieves parameters from the query string of the request URL. - -Example of C with multiple values: +This keyword retrieves parameters from the query string of the request +URL: # Matches URL: /search?q=rides&cat=thrill get '/search' => sub { @@ -368,9 +321,7 @@ The above route would match the URL C. =head4 route_parameters -This keyword retrieves parameters from the route pattern. - -Example of C: +This keyword retrieves named parameters from the route declaration: # Matches URL: /user/123 get '/user/:id' => sub { @@ -396,9 +347,7 @@ Example of C: =head3 Named Route Parameters Named route parameters allow you to capture specific segments of the URL -and use them in your route handler. - -Example of named route parameters: +and use them in your route handler: # Matches URL: /ride/roller-coaster get '/ride/:name' => sub { @@ -406,6 +355,9 @@ Example of named route parameters: return "Welcome to the $ride_name ride!"; }; +Named route parameters are retrieved with the L +keyword. + =head3 Wildcard Route Parameters Wildcard route parameters allow you to capture arbitrary parts of the URL. @@ -449,27 +401,221 @@ Example combining named and wildcard parameters: =head3 Named parameters with type constraints -TODO/re-do +TODO: more examples from existing manual + +Type constraints allow you to enforce specific types for named +parameters, ensuring that the parameters meet certain criteria. + +Dancer2 natively supports named parameters with type constraints +without needing to rely on external plugins. Here’s how to declare +and use type constraints for named route parameters in Dancer2: + + use Dancer2; + + get '/ride/:id[Int]' => sub { + my $id = route_parameters->get('id'); + return "Ride ID: $id"; + }; + + get '/guest/:name[Str]' => sub { + my $name = route_parameters->get('name'); + return "Guest Name: $name"; + }; + + MyApp->to_app(); + +=over 4 + +=item * + +B: This constraint ensures that the C parameter must be an integer. + +=item * + +B: This ensures that the C parameter must be a string. + +=back + +Note: For more complex parameter types, the C +module provides additional constraints and validation for all parameter +types. =head3 Wildcard Parameters with Type Constraints -TODO/re-do +TODO: examples seem broken + +You can also enforce type constraints on wildcard parameters: + + # Matches URL: /images/photo.jpg + get '/images/*.*' => sub { + my ($filename, $extension) = splat; + return "Filename: $filename, Extension: $extension"; + } => sub { + params({ + splat => ArrayRef[Str], + }); + }; + + # Matches URL: /documents/folder/subfolder/file.txt + get '/documents/**' => sub { + my @path = splat; + return "Document path: " . join('/', @path); + } => sub { + params({ + splat => ArrayRef[Str], + }); + }; =head3 Regex Route Matching -TODO/re-do +Regex route matching allows you to define routes using regular +expressions, providing more flexibility in matching URLs: + + # Matches URL: /product/12345 + get qr{/product/(\d+)} => sub { + my ($product_id) = splat; + return "Product ID: $product_id"; + }; + + # Matches URL: /category/electronics + get qr{/category/(\w+)} => sub { + my ($category_name) = splat; + return "Category: $category_name"; + }; =head3 Combining Examples -TODO/re-do +TODO: fix examples -=head2 Simplifying Route Declarations with prefix + # Matches URL: /item/42/specifications + get '/item/:id/*' => sub { + my $item_id = route_parameters->get('id'); + my ($detail) = splat; + return "Item ID: $item_id, Detail: $detail"; + } => sub { + params({ + id => Int, + splat => ArrayRef[Str], + }); + }; -TODO + # Matches URL: /archive/2023/07/10 + get qr{/archive/(\d{4})/(\d{2})/(\d{2})} => sub { + my ($year, $month, $day) = splat; + return "Archive for: $year-$month-$day"; + }; -=head2 Skipping Routes and Actions +=head2 Organizing routes and growing your app using prefix -TODO +To organize your routes more effectively in Danceyland, you can use the +C DSL keyword to group related routes under specific sections of +the park. + +C helps you to grow Danceyland as well. You can create additional +application modules that represent different areas of the park, each of +which is declared with its own C. + +=head3 Example: Using C in Danceyland + +Imagine Danceyland has different sections, like the I and +I. You can use C to organize these areas: + + package MyApp; + use Dancer2; + + # Prefix for Zoo Kingdom + prefix '/zoo-kingdom' => sub { + get '/animals' => sub { + return "Welcome to the Zoo Kingdom! Here you can find all the animals."; + }; + + get '/feed-schedule' => sub { + return "Feeding Schedule: 10 AM and 4 PM."; + }; + }; + + # Prefix for Actionland + prefix '/actionland' => sub { + get '/thrill-rides' => sub { + return "Welcome to Actionland! Enjoy the thrill rides."; + }; + + get '/roller-coaster' => sub { + return "The best roller coaster in the park!"; + }; + }; + + MyApp->to_app(); + +This example organizes routes by grouping all animal-related activities +under C and all rides under C. + +=head2 Controlling the flow with forward, redirect, and pass + +These DSL keywords in Dancer2 allow you to manage the flow of actions +within your routes. Here’s how they work in Danceyland. + +=head3 forward + +C allows you to forward the current request to another route +handler, as if it were redirected, but without sending a new HTTP request. +Put another way, a different route than the one requested is processed, +but the address in the user's browser's bar stays the same. + +=head4 Example + + get '/guest-services' => sub { + forward '/info'; # Forwards to /info + }; + + get '/info' => sub { + return "Welcome to Guest Services. How can we help you today?"; + }; + +In this example, when a visitor goes to C, they are +forwarded to the C route internally, however, the vistor's browser +still shows they are in C. + +=head3 redirect + +C sends an HTTP redirect to the client, instructing their +browser to make a new request to a different URL. At the end of the +request, the user's browser will show a different URL than the one +they originally navigated to. + +=head4 Example + + get '/old-roller-coaster' => sub { + redirect '/new-roller-coaster'; + }; + + get '/new-roller-coaster' => sub { + return "Welcome to the new and improved roller coaster!"; + }; + +When a visitor requests C, they are redirected to +C. The browser's URL will now show C. + +=head3 pass + +C tells Dancer2 to skip the current route and continue looking for +the next matching route. + +=head4 Example + + get '/vip-area' => sub { + if (!session('is_vip')) { + pass; # Skip to the next matching route if the user is not VIP + } + return "Welcome to the VIP area!"; + }; + + get '/vip-area' => sub { + return "Access Denied. This area is for VIPs only."; + }; + +In this example, if the session doesn’t indicate the user is a VIP, the +request will skip to the next matching route, which denies access. =head1 Templates: Displaying Information, and More! @@ -674,7 +820,6 @@ coderef to execute, which returns the response. The above route specifies that, for GET requests to C, the code block provided should be executed. - You can also provide routes with a name: get 'hi_to' => '/hello/:name' => sub {...};