File Coverage: lib/Yukki/Web/Context.pm

# File Coverage

File: | lib/Yukki/Web/Context.pm
Coverage: | 73.6%

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Yukki::Web::Context;
2 |
3 | 3 3 |  |  | 3 |  | 27 13 | use v5.24;
4 | 3 3 3 |  |  | 3 |  | 10 3 41 | use utf8;
5 | 3 3 3 |  |  | 3 |  | 603 10809 9 | use Moo;
6 |
7 | 3 3 3 |  |  | 3 |  | 2107 752 101 | use Sub::Name qw( subname );
8 | 3 3 3 |  |  | 3 |  | 487 27070 10 | use Type::Utils;
9 | 3 3 3 |  |  | 3 |  | 3132 54548 20 | use Types::Standard qw( ArrayRef HashRef Str );
10 | 3 3 3 |  |  | 3 |  | 2097 106020 17 | use Types::URI qw( Uri );
11 | 3 3 3 |  |  | 3 |  | 1119 6 52 | use Yukki::Web::Request;
12 | 3 3 3 |  |  | 3 |  | 665 6 50 | use Yukki::Web::Response;
13 |
14 | 3 3 3 |  |  | 3 |  | 10 4 11 | use namespace::clean;
15 |
16 |  |  |  |  |  |  | # ABSTRACT: request-response context descriptor
17 |
18 - 43 |  |
    =head1 SYNOPSIS

      # Many components are handed a Context in $ctx...

      my $request = $ctx->request;
      my $session = $ctx->session;
      my $session_options = $ctx->session_options;
      my $response = $ctx->response;
      my $stash = $ctx->stash;

      $ctx->add_errors('bad stuff');
      $ctx->add_warnings('not so good stuff');
      $ctx->add_info('some stuff');

    =head1 DESCRIPTION

    This describes information about a single request-repsonse to be handled by the server.

    =head1 ATTRIBUTES

    =head2 env

    This is the L<PSGI> environment. Do not use directly. This will probably be
    renamed to make it more difficult to use directly in the future.

    =cut
44 |
45 |  |  |  |  |  |  | has env => (
46 |  |  |  |  |  |  | is => 'ro',
47 |  |  |  |  |  |  | isa => HashRef,
48 |  |  |  |  |  |  | required => 1,
49 |  |  |  |  |  |  | );
50 |
51 - 55 |  |
    =head2 request

    This is the L<Yukki::Web::Request> object representing the incoming request.

    =cut
56 |
57 |  |  |  |  |  |  | has request => (
58 |  |  |  |  |  |  | is => 'ro',
59 |  |  |  |  |  |  | isa => class_type('Yukki::Web::Request'),
60 |  |  |  |  |  |  | required => 1,
61 |  |  |  |  |  |  | lazy => 1,
62 |  |  |  |  |  |  | default => sub { Yukki::Web::Request->new(env => shift->env) },
63 |  |  |  |  |  |  | handles => [ qw( session session_options ) ],
64 |  |  |  |  |  |  | );
65 |
66 - 71 |  |
    =head2 response

    This is the L<Yukki::Web::Response> object representing the response to send
    back to the client.

    =cut
72 |
73 |  |  |  |  |  |  | has response => (
74 |  |  |  |  |  |  | is => 'ro',
75 |  |  |  |  |  |  | isa => class_type('Yukki::Web::Response'),
76 |  |  |  |  |  |  | required => 1,
77 |  |  |  |  |  |  | lazy => 1,
78 |  |  |  |  |  |  | default => sub { Yukki::Web::Response->new },
79 |  |  |  |  |  |  | );
80 |
81 - 87 |  |
    =head2 stash

    This is a temporary stash of information. Use of this should be avoided when
    possible. Global state like this (even if it only lasts for one request) should
    only be used as a last resort.

    =cut
88 |
89 |  |  |  |  |  |  | has stash => (
90 |  |  |  |  |  |  | is => 'ro',
91 |  |  |  |  |  |  | isa => HashRef,
92 |  |  |  |  |  |  | required => 1,
93 |  |  |  |  |  |  | default => sub { +{} },
94 |  |  |  |  |  |  | );
95 |
96 - 127 |  |
    =head2 base_url

    This is a L<URI> describing the base path to get to this Yukki wiki site. It is configured from the L<Yukki::Web::Settings/base_url> setting. The value of the setting will determine how this value is calculated or may set it explicitly.

    =over

    =item *

    C<SCRIPT_NAME>. When C<base_url> is set to C<SCRIPT_NAME>, then the full path to the script name will be used as the base URL. This is the default and, generally, the safest option.

    =item *

    C<REWRITE>. The C<REWRITE> option takes a slightly different approach to building the base URL. It looks at the C<REQUEST_URI> and compares that to the C<PATH_INFO> and finds the common components. For example:

      PATH_INFO=/page/view/main
      REQUEST_URI=/yukki-site/page/view/main

    this leads to a base URL of:

      /yukki-site

    If C<PATH_INFO> is not a sub-path of C<REQUEST_URI>, this will fall back to the same solution as C<SCRIPT_NAME> above.

    =item *

    Anything else will be considered an absolute URL and used as the base URL.

    =back

    This may be used to construct redirects or URLs for links and form actions.

    =cut
128 |
129 |  |  |  |  |  |  | has base_url => (
130 |  |  |  |  |  |  | is => 'rw',
131 |  |  |  |  |  |  | isa => Uri,
132 |  |  |  |  |  |  | required => 1,
133 |  |  |  |  |  |  | coerce => 1,
134 |  |  |  |  |  |  | lazy => 1,
135 |  |  |  |  |  |  | builder => '_build_base_url',
136 |  |  |  |  |  |  | );
137 |
138 |  |  |  |  |  |  | sub _build_base_url {
139 | 3 |  |  | 3 |  | 260 | my $self = shift;
140 |
141 | 3 |  |  |  |  | 15 | my $base_url = $self->env->{'yukki.settings'}->base_url;
142 | 3 | 50 0 |  |  |  | 7 | if ($base_url eq 'SCRIPT_NAME') {
143 | 3 |  |  |  |  | 41 | return $self->request->base;
144 |  |  |  |  |  |  | }
145 |
146 |  |  |  |  |  |  | elsif ($base_url eq 'REWRITE') {
147 | 0 |  |  |  |  | 0 | my $path_info = $self->env->{PATH_INFO};
148 | 0 |  |  |  |  | 0 | my $request_uri = $self->env->{REQUEST_URI};
149 |
150 | 0 | 0 |  |  |  | 0 | if ($request_uri =~ s/$path_info$//) {
151 | 0 |  |  |  |  | 0 | my $base_url = $self->request->uri;
152 | 0 |  |  |  |  | 0 | $base_url->path($request_uri);
153 | 0 |  |  |  |  | 0 | return $base_url->canonical;
154 |  |  |  |  |  |  | }
155 |
156 | 0 |  |  |  |  | 0 | return $self->request->base;
157 |  |  |  |  |  |  | }
158 |
159 |  |  |  |  |  |  | else {
160 | 0 |  |  |  |  | 0 | return $_;
161 |  |  |  |  |  |  | }
162 |  |  |  |  |  |  | }
163 |
164 - 181 |  |
    =head2 errors

    =head2 warnings

    =head2 info

    These each contain an array of errors.

    The C<list_errors>, C<list_warnings>, and C<list_info> methods are provided to
    return the values as a list.

    The C<add_errors>, C<add_warnings>, and C<add_info> methods are provided to
    append new messages.

    The C<has_errors>, C<has_warnings>, and C<has_info> methods are provided to tell
    you if there are any messages.

    =cut
182 |
183 |  |  |  |  |  |  | # TODO Store these in a flash stash
184 |  |  |  |  |  |  | for my $message_type (qw( errors warnings info )) {
185 |  |  |  |  |  |  | has $message_type => (
186 |  |  |  |  |  |  | is => 'ro',
187 |  |  |  |  |  |  | isa => ArrayRef[Str],
188 |  |  |  |  |  |  | required => 1,
189 |  |  |  |  |  |  | default => sub { [] },
190 |  |  |  |  |  |  | );
191 |
192 | 3 3 3 |  |  | 3 |  | 1405 4 393 | no strict 'refs';
193 |
194 |  |  |  |  |  |  | *{__PACKAGE__ . "::list_$message_type"} = subname "list_$message_type", sub {
195 | 0 |  |  | 0 0 0 | 1 1 1 | 0 | my $self = shift;
196 | 0 0 |  |  |  |  | 0 0 | map { ucfirst "$_." } $self->$message_type->@*
197 |  |  |  |  |  |  | };
198 |
199 |  |  |  |  |  |  | *{__PACKAGE__ . "::add_$message_type"} = subname "add_$message_type", sub {
200 | 0 |  |  | 0 0 0 | 1 1 1 | 0 | my $self = shift;
201 | 0 |  |  |  |  | 0 | push $self->$message_type->@*, @_;
202 |  |  |  |  |  |  | };
203 |
204 |  |  |  |  |  |  | *{__PACKAGE__ . "::has_$message_type"} = subname "add_$message_type", sub {
205 | 7 |  |  | 7 7 7 | 1 1 1 | 2570 | my $self = shift;
206 | 7 |  |  |  |  | 48 | scalar $self->$message_type->@*;
207 |  |  |  |  |  |  | };
208 |  |  |  |  |  |  | }
209 |
210 |
211 - 219 |  |
    =head1 METHODS

    =head2 rebase_url

      my $url = $ctx->rebase_url($path);

    Given a relative URL, this returns an absolute URL using the L</base_url>.

    =cut
220 |
221 |  |  |  |  |  |  | sub rebase_url {
222 | 27 |  |  | 27 | 1 | 44 | my ($self, $url) = @_;
223 | 27 |  |  |  |  | 71 | return URI->new($url)->abs($self->base_url);
224 |  |  |  |  |  |  | }
225 |
226 - 250 |  |
    =head2 list_errors

    =head2 list_warnings

    =head2 list_info

    These methods return the list of errors, warnings, and info messages associated with the current flow.

    =head2 add_errors

    =head2 add_warnings

    =head2 add_info

    These methods add zero or more errors, warnings, and info messages to be associated with the current flow.

    =head2 has_errors

    =head2 has_warnings

    =head2 has_info

    These methods return a true value if there are any errors, warnings, or info messages associated with the current flow.

    =cut
251 |
252 |  |  |  |  |  |  | 1;