File Coverage: lib/Yukki/Error.pm

# File Coverage

File: | lib/Yukki/Error.pm
Coverage: | 91.3%

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Yukki::Error;
2 |
3 | 7 7 |  |  | 7 |  | 55 18 | use v5.24;
4 | 7 7 7 |  |  | 7 |  | 21 7 38 | use utf8;
5 | 7 7 7 |  |  | 7 |  | 328 5186 27 | use Moo;
6 |
7 |  |  |  |  |  |  | extends qw( HTTP::Throwable::Factory );
8 |
9 | 7 7 7 |  |  | 7 |  | 3803 31 145 | use Yukki::Web::View;
10 |
11 | 7 7 7 |  |  | 7 |  | 30 9 33 | use namespace::clean;
12 |
13 | 7 |  |  | 7 |  | 2212 | use Sub::Exporter -setup => {
14 |  |  |  |  |  |  | exports => [ qw< http_throw http_exception > ],
15 | 7 7 |  |  |  |  | 14057 68 | };
16 |
17 |  |  |  |  |  |  | # ABSTRACT: Yukki's exception class
18 |
19 - 59 |  |
    =head1 SYNOPSIS

      Yukki::Error->throw("Something really bad.", { ... });

    =head1 DESCRIPTION

    If you are familiar with L<HTTP::Throwable::Factory>, this is similar to that (and is based on that).

    However, there are two differences. First, the error message is given primacy rather than exception type, so you can just use this to throw an exception:

        use Yukki::Error qw( http_throw );
        http_throw('something went wrong');

    Since you almost always want your exception to be an internal server error of some kind, this makes more sense to me than having to write:

        use HTTP::Throwable::Factory qw( http_throw );
        http_throw(InternalServerError => {
            message => 'something went wrong',
        });

    To specify the type of exception, us C<status>:

        use Yukki::Error qw( http_throw );
        http_throw('something was not found', {
            status => 'NotFound',
        });

    The second difference is that all exceptions thrown by this factory inherit from L<Yukki::Error>, so this works:

        use Scalar::Util qw( blessed );
        use Try::Tiny;
        try { ... }
        catch {
            if (blassed $_ && $_->isa("Yukki::Error") {
                # we now this is an application error from Yukki
            }
        };

    This makes it easy to know whether Yukki generated the exception or something else did.

    =cut
60 |
61 | 2 |  |  | 2 | 1 | 21241 | sub base_class { 'Yukki::Error' }
62 | 2 |  |  | 2 | 1 | 18 | sub extra_roles { 'Yukki::Error::Body' }
63 |
64 - 75 |  |
    =head1 EXPORTS

    =head2 http_exception

      my $error = http_exception('message', {
          status           => 'InternalServerError',
          show_stask_trace => 0,
      });

    Creates a new exception object. Calls the constructor for L<Yukki:Error> and applied the L<HTTP::Throwable> status role needed (prior to construction actually).

    =cut
76 |
77 |  |  |  |  |  |  | sub http_exception {
78 | 2 |  |  | 2 | 1 | 5 | my ($name, $args) = @_;
79 |
80 | 2 2 |  | 50 |  |  | 3 17 | my %args = %{ $args // {} };
81 | 2 |  | 50 |  |  | 6 | my $status = delete $args{status} // 'InternalServerError';
82 |
83 | 2 |  |  |  |  | 23 | Yukki::Error->new_exception($status => {
84 |  |  |  |  |  |  | %args,
85 |  |  |  |  |  |  | message => "$name",
86 |  |  |  |  |  |  | });
87 |  |  |  |  |  |  | }
88 |
89 - 98 |  |
    =head2 http_throw

      http_throw('message', {
          status           => 'InternalServerError',
          show_stask_trace => 0,
      });

    Constructs the exception (via L</http_exception>) and throws it.

    =cut
99 |
100 |  |  |  |  |  |  | sub http_throw {
101 | 2 |  |  | 2 | 1 | 4 | my ($name, $args) = @_;
102 |
103 | 2 |  |  |  |  | 6 | http_exception($name, $args)->throw;
104 |  |  |  |  |  |  | }
105 |
106 |  |  |  |  |  |  | sub BUILDARGS {
107 | 2 |  |  | 2 | 0 | 21852 | my ($class, $args) = @_;
108 | 2 |  |  |  |  | 26 | $args;
109 |  |  |  |  |  |  | }
110 |
111 - 119 |  |
    =begin Pod::Coverage

        BUILDARGS
        base_class
        extra_roles

    =end Pod::Coverage

    =cut
120 |
121 |  |  |  |  |  |  | {
122 |  |  |  |  |  |  | package Yukki::Error::Body;
123 |
124 | 7 7 7 |  |  | 7 |  | 3344 9 25 | use Moo::Role;
125 |
126 - 132 |  |
    =head1 METHODS

    =head2 body

    Renders the HTML body for the error.

    =cut
133 |
134 |  |  |  |  |  |  | sub body {
135 | 1 |  |  | 1 | 0 | 2 | my ($self, $env) = @_;
136 |
137 | 1 |  |  |  |  | 3 | my $app = $env->{'yukki.app'};
138 | 1 |  |  |  |  | 7 | my $view = Yukki::Web::View->new(app => $app);
139 | 1 |  |  |  |  | 141 | my $ctx = Yukki::Web::Context->new(env => $env);
140 |
141 | 1 |  |  |  |  | 25 | my $template = $view->prepare_template(
142 |  |  |  |  |  |  | template => 'error.html',
143 |  |  |  |  |  |  | directives => [
144 |  |  |  |  |  |  | '#error-page' => 'error_message',
145 |  |  |  |  |  |  | ],
146 |  |  |  |  |  |  | );
147 |
148 | 1 |  |  |  |  | 603 | $ctx->response->page_title($self->reason);
149 |
150 | 1 |  |  |  |  | 100 | return $view->render_page(
151 |  |  |  |  |  |  | template => $template,
152 |  |  |  |  |  |  | context => $ctx,
153 |  |  |  |  |  |  | vars => {
154 |  |  |  |  |  |  | 'error_message' => $self->message,
155 |  |  |  |  |  |  | },
156 |  |  |  |  |  |  | );
157 |  |  |  |  |  |  | }
158 |
159 - 163 |  |
    =head2 body_headers

    Setup the HTTP headers.

    =cut
164 |
165 |  |  |  |  |  |  | sub body_headers {
166 | 1 |  |  | 1 | 0 | 30 | my ($self, $body) = @_;
167 |
168 |  |  |  |  |  |  | return [
169 | 1 |  |  |  |  | 4 | 'Content-type' => 'text/html',
170 |  |  |  |  |  |  | 'Content-length' => length $body,
171 |  |  |  |  |  |  | ];
172 |  |  |  |  |  |  | }
173 |
174 - 178 |  |
    =head2 as_string

    Returns the message.

    =cut
179 |
180 |  |  |  |  |  |  | sub as_string {
181 | 7 |  |  | 7 | 0 | 396 | my $self = shift;
182 | 7 |  |  |  |  | 15 | return $self->message;
183 |  |  |  |  |  |  | }
184 |
185 |  |  |  |  |  |  | around as_psgi => sub {
186 |  |  |  |  |  |  | shift; # original method is ignored
187 |  |  |  |  |  |  | my ($self, $env) = @_;
188 |  |  |  |  |  |  | my $body = $self->body( $env );
189 |  |  |  |  |  |  | my $headers = $self->build_headers( $body );
190 |  |  |  |  |  |  | [ $self->status_code, $headers, [ defined $body ? $body : () ] ];
191 |  |  |  |  |  |  | };
192 |
193 |  |  |  |  |  |  | }
194 |
195 |  |  |  |  |  |  | 1;