Skip to content

Commit

Permalink
add router docs, add Slick::Error
Browse files Browse the repository at this point in the history
  • Loading branch information
rawleyfowler committed Aug 2, 2023
1 parent 1091969 commit d61c01b
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
0.004 2023-08-02
- Alpha release #4
- Add error handling for routes and events
- Add the Slick::Error class
- Various POD changes

0.003 2023-08-01
- Alpha release #3
- Add routers allowing for easier use in multi-file projects
Expand Down
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,28 @@ you can also take advantage of swappable backends and Plack middlewares extremel

Currently, Slick supports `MySQL` and `Postgres` but there are plans to implement `Oracle` and `MS SQL Server`.

## Philosophy

Slick is aiming to become a "Batteries Included" framework for building REST API's and Micro-Services in
Perl. This will include tooling for all sorts of Micro-Service concerns like Databases, Caching, Queues,
User-Agents, and much more.

### Goals

- [x] Database management (auto-enabled)
- [x] Migrations (auto-enabled)
- [ ] CLI
- [ ] Caching via Redis (optional)
- [ ] RabbitMQ built-ins (optional)
- [ ] AWS S3 support (optional)
- [ ] User-Agents, including Client API exports
- [ ] AWS SQS support (optional)

*Note*: All of these features excluding database stuff will be enabled optionally at run-time.

## Examples

### Single File App
```perl
use 5.036;

Expand Down Expand Up @@ -58,6 +78,62 @@ $s->post('/users' => sub {
$s->run; # Run the application.
```

See the examples directory for this example.

### Multi-file Router App

```perl
### INSIDE lib/MyApp/ItemRouter.pm

package MyApp::ItemRouter;

use base qw(Slick::Router);

my $router = __PACKAGE__->new(base => '/items');

$router->get('/{id}' => sub {
my ($app, $context) = @_;
my $item = $app->database('items')->select_one({ id => $context->param('id') });
$context->json($item);
});

$router->post('' => sub {
my ($app, $context) = @_;
my $new_item = $context->content;

# Do some sort of validation
if (not $app->helper('item_validator')->validate($new_item)) {
$context->status(400)->json({ error => 'Bad Request' });
}
else {
$app->database('items')->insert('items', $new_item);
$context->json($new_item);
}
});

sub router {
return $router;
}

1;

### INSIDE OF YOUR RUN SCRIPT

use 5.036;
use lib 'lib';

use Slick;
use MyApp::ItemRouter;

my $slick = Slick->new;

$slick->register(MyApp::ItemRouter->router);

$slick->run;
```

See the examples directory for this example.

### Running with `plackup`

If you wish to use `plackup` you can change the final call to `run` to a call to `app`
Expand Down
13 changes: 13 additions & 0 deletions examples/multi-file/app.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use 5.036;
use lib 'lib';

use Slick;
use MyApp::ItemRouter;

my $slick = Slick->new;

$slick->helper(item_validator => sub { return exists shift->{name} });

$slick->register(MyApp::ItemRouter->router);

$slick->run;
31 changes: 31 additions & 0 deletions examples/multi-file/lib/MyApp/ItemRouter.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package MyApp::ItemRouter;

use base qw(Slick::Router);

my $router = __PACKAGE__->new(base => '/items');

$router->get('/{id}' => sub {
my ($app, $context) = @_;
my $item = $app->database('items')->select_one({ id => $context->param('id') });
$context->json($item);
});

$router->post('' => sub {
my ($app, $context) = @_;
my $new_item = $context->content;

# Do some sort of validation
if (not $app->helper('item_validator')->($new_item)) {
$context->status(400)->json({ error => 'Bad Request' });
}
else {
$app->database('items')->insert('items', $new_item);
$context->json($new_item);
}
});

sub router {
return $router;
}

1;
111 changes: 97 additions & 14 deletions lib/Slick.pm
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ use Plack::Builder qw(builder enable);
use Plack::Request;
use Slick::Context;
use Slick::Events qw(EVENTS BEFORE_DISPATCH AFTER_DISPATCH);
use Slick::Error;
use Slick::Database;
use Slick::Methods qw(METHODS);
use Slick::Route;
use Slick::Router;
use Slick::RouteMap;
use Slick::Util;
use experimental qw(try);

our $VERSION = '0.003';
no warnings qw(experimental::try);

our $VERSION = '0.004';

with 'Slick::EventHandler';
with 'Slick::RouteManager';
Expand All @@ -45,11 +49,10 @@ has timeout => (
);

has env => (
is => 'ro',
lazy => 1,
is => 'rw',
isa => Str,
default => sub {
return $ENV{SLICK_ENV} || $ENV{PLACK_ENV} || 'dev';
return $ENV{SLICK_ENV} // $ENV{PLACK_ENV} // 'dev';
}
);

Expand Down Expand Up @@ -95,37 +98,56 @@ sub _dispatch {
my $request = Plack::Request->new(shift);

my $context = Slick::Context->new( request => $request, );

my $method = lc( $request->method );
my $method = lc( $request->method );

for ( @{ $self->event_handlers->{ BEFORE_DISPATCH() } } ) {
if ( !$_->( $self, $context ) ) {
goto DONE;
}
try { $_->( $self, $context ) // goto DONE; }
catch ($e) {
push $context->stash->{'slick.errors'}->@*, Slick::Error->new($e)
};
}

my $route =
$self->handlers->get( $context->request->request_uri, $method, $context );

if ( defined $route ) {
$route->dispatch( $self, $context );
try { $route->dispatch( $self, $context ); }
catch ($e) {
push $context->stash->{'slick.errors'}->@*, Slick::Error->new($e)
}
}
else {
$context->status(405);
$context->body('405 Method Not Supported');
goto DONE;
}

$_->( $self, $context )
for ( @{ $self->event_handlers->{ AFTER_DISPATCH() } } );
for ( @{ $self->event_handlers->{ AFTER_DISPATCH() } } ) {
try { $_->( $self, $context ) // goto DONE; }
catch ($e) {
push $context->stash->{'slick.errors'}->@*, Slick::Error->new($e);
}
}

DONE:

if ( $context->stash->{'slick.errors'}->@* && ( $self->env eq 'dev' ) ) {
say $_->error for $context->stash->{'slick.errors'}->@*;
$context->json(
[ map { $_->to_hash } $context->stash->{'slick.errors'}->@* ] );
}

# HEAD requests only want headers.
$context->body = [] if $method eq 'head';

return $context->to_psgi;
}

sub BUILD {
my $self = shift;

return $self;
}

sub helper {
my ( $self, $name, $helper ) = @_;

Expand Down Expand Up @@ -206,7 +228,9 @@ sub app {
return builder {
enable 'Plack::Middleware::AccessLog' => format => 'combined';
enable $_->@* for $self->middlewares->@*;
sub { return $self->_dispatch(@_); }
sub {
return $self->_dispatch(@_);
}
};
}

Expand Down Expand Up @@ -280,6 +304,63 @@ has a migration, and also serves some JSON.
$s->run; # Run the application.
=head3 A Multi-file application
This is a multi-file application that uses L<Slick::Router>.
### INSIDE OF YOUR ROUTER MODULE lib/MyApp/ItemRouter.pm
package MyApp::ItemRouter;
use base qw(Slick::Router);
my $router = __PACKAGE__->new(base => '/items');
$router->get('/{id}' => sub {
my ($app, $context) = @_;
my $item = $app->database('items')->select_one({ id => $context->param('id') });
$context->json($item);
});
$router->post('' => sub {
my ($app, $context) = @_;
my $new_item = $context->content;
# Do some sort of validation
if (not $app->helper('item_validator')->($new_item)) {
$context->status(400)->json({ error => 'Bad Request' });
}
else {
$app->database('items')->insert('items', $new_item);
$context->json($new_item);
}
});
sub router {
return $router;
}
1;
### INSIDE OF YOUR RUN SCRIPT 'app.pl'
use 5.036;
use lib 'lib';
use Slick;
use MyApp::ItemRouter;
my $slick = Slick->new;
$slick->helper(item_validator => sub {
return exists shift->{name};
});
# Register as many routers as you want!
$slick->register(MyApp::ItemRouter->router);
$slick->run;
=head3 Running with plackup
If you wish to use C<plackup> you can change the final call to C<run> to a call to C<app>:
Expand Down Expand Up @@ -478,6 +559,8 @@ Inherited from L<Slick::EventHandler>.
=item * L<Slick::Util>
=item * L<Slick::Error>
=back
=cut
9 changes: 7 additions & 2 deletions lib/Slick/Context.pm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ has id => (
has stash => (
is => 'rw',
isa => HashRef,
default => sub { return {}; }
default => sub { return { 'slick.errors' => [] }; }
);

has request => (
Expand Down Expand Up @@ -117,7 +117,12 @@ sub query {
}

sub to_psgi {
my $response = shift->response;
my $self = shift;
my $response = $self->response;

$response->{status} = 500
if $self->stash->{'slick.errors'}->@*;

return [ $response->{status}, $response->{headers}, $response->{body} ];
}

Expand Down
Loading

0 comments on commit d61c01b

Please sign in to comment.