File Coverage

blib/lib/WWW/Suffit/Plugin/CommonHelpers.pm
Criterion Covered Total %
statement 15 72 20.8
branch 0 54 0.0
condition 0 39 0.0
subroutine 5 12 41.6
pod 1 1 100.0
total 21 178 11.8


line stmt bran cond sub pod time code
1             package WWW::Suffit::Plugin::CommonHelpers;
2 1     1   89774 use strict;
  1         5  
  1         31  
3 1     1   4 use warnings;
  1         1  
  1         72  
4 1     1   543 use utf8;
  1         259  
  1         5  
5              
6             =encoding utf8
7              
8             =head1 NAME
9              
10             WWW::Suffit::Plugin::CommonHelpers - Common helpers plugin for Suffit API servers
11              
12             =head1 SYNOPSIS
13              
14             # in your startup
15             $self->plugin('WWW::Suffit::Plugin::CommonHelpers');
16              
17             =head1 DESCRIPTION
18              
19             This plugin is a collection of common helpers for Suffit API servers
20              
21             =head1 HELPERS
22              
23             This plugin implements the following helpers
24              
25             =head2 base_url
26              
27             my $url = $c->base_url;
28              
29             Returns the base URL from request
30              
31             =head2 client_ip
32              
33             my $ip = $c->client_ip;
34             my $ip = $c->client_ip([ ..trusted_proxies ...]);
35              
36             Returns the client IP address
37              
38             =head2 remote_ip
39              
40             See L
41              
42             =head2 reply.error
43              
44             return $c->reply->error(); # 500, E0500, "Internal server error"
45             return $c->reply->error("Error message"); # 500, E0500, "Error message"
46             return $c->reply->error(501 => "Error message");
47             return $c->reply->error(501 => "Error code" => "Error message");
48              
49             The method returns error in client request format
50              
51             B: This method with HTML format requires the 'error' template
52              
53             =head2 reply.json_error
54              
55             return $c->reply->json_error(); # 500, E0500, "Internal server error"
56             return $c->reply->json_error("Error message"); # 500, E0500, "Error message"
57             return $c->reply->json_error(501 => "Error message");
58             return $c->reply->json_error(501 => "Error code" => "Error message");
59              
60             {
61             "code": "Error code",
62             "message": "Error message",
63             "status": false
64             }
65              
66             The method returns API error as JSON response
67              
68             =head2 reply.json_ok
69              
70             return $c->reply->json_ok(); # 200, ""
71             return $c->reply->json_ok("Ok."); # 200, "Ok."
72             return $c->reply->json_ok(201 => "Ok."); # 201, "Ok."
73              
74             {
75             "code": "E0000",
76             "message": "Ok.",
77             "status": true
78             }
79              
80             return $c->reply->json_ok({foo => "bar"}); # 200, {...}
81              
82             {
83             "code": "E0000",
84             "foo": "bar",
85             "status": true
86             }
87              
88             return $c->reply->json_ok(201 => {foo => "bar"}); # 201, {...}
89              
90             # 201
91             {
92             "code": "E0000",
93             "foo": "bar",
94             "status": true
95             }
96              
97             The method returns API success status as JSON response
98              
99             =head2 reply.noapi
100              
101             return $c->reply->noapi(
102             status => 501, # HTTP status code (default: 200)
103             code => "E0501", # The Suffit error code
104             message => "Error message",
105             data => {...}, # Payload data
106             html => { template => "error" }, # HTML options
107             );
108              
109             The method returns data in client request format
110              
111             =head1 METHODS
112              
113             Internal methods
114              
115             =head2 register
116              
117             Do not use directly. It is called by Mojolicious.
118              
119             =head1 SEE ALSO
120              
121             L, L
122              
123             =head1 AUTHOR
124              
125             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
126              
127             =head1 COPYRIGHT
128              
129             Copyright (C) 1998-2026 D&D Corporation
130              
131             =head1 LICENSE
132              
133             This program is distributed under the terms of the Artistic License Version 2.0
134              
135             See the C file or L for details
136              
137             =cut
138              
139 1     1   610 use Mojo::Base 'Mojolicious::Plugin';
  1         13085  
  1         7  
140              
141             our $VERSION = '1.02';
142              
143 1     1   2515 use Acrux::RefUtil qw/ is_array_ref is_hash_ref isnt_void is_int8 /;
  1         2423  
  1         1299  
144              
145             sub register {
146 0     0 1   my ($self, $app, $opts) = @_; # $self = $plugin
147 0   0       $opts //= {};
148              
149             # JSON API responses
150 0           $app->helper('reply.json_error', => \&_reply_json_error);
151 0           $app->helper('reply.json_ok' => \&_reply_json_ok);
152              
153             # No JSON API responses
154 0           $app->helper('reply.error' => \&_reply_error);
155 0           $app->helper('reply.noapi' => \&_reply_noapi);
156              
157             # Get Client/Remote IP address
158 0           $app->helper('client_ip' => \&_client_ip);
159 0           $app->helper('remote_ip' => \&_client_ip);
160              
161             # Get base URL
162 0           $app->helper('base_url' => \&_base_url);
163             }
164              
165             sub _reply_json_error {
166 0     0     my $self = shift;
167 0   0       my $err = pop(@_) // "Internal Server Error";
168 0   0       my $stt = shift(@_) || 500;
169 0           my $cod = shift(@_);
170              
171             # Correct code and message
172 0 0         unless ($cod) {
173 0 0         if ($err =~ s/^(E[0-9]{4})[:]?\s+//) {
174 0           $cod = $1;
175             }
176 0   0       $cod ||= "E0$stt";
177             }
178              
179             # Log
180 0 0         $self->log->error(sprintf("[%s] %s", $cod, $err)) if length $err;
181              
182             # Clean message
183 0           $err =~ s/^(E[0-9]{4})[:]?\s+//;
184              
185             # Render
186 0 0         return $self->render(
187             json => {
188             status => \0,
189             code => $cod,
190             length $err ? (message => $err) : (),
191             },
192             status => $stt,
193             );
194             }
195             sub _reply_json_ok {
196 0     0     my $self = shift;
197 0   0       my $e = pop(@_) // "";
198 0   0       my $s = pop(@_) // 200;
199 0           my %j = (status => \1, code => 'E0000');
200 0           my %d = ();
201 0 0         if (is_hash_ref($e)) {
    0          
202 0           %d = %$e;
203             } elsif($e ne "") {
204 0           $j{message} = $e;
205             }
206 0           return $self->render(json => {%j, %d}, status => $s);
207             }
208             sub _reply_error {
209 0     0     my $self = shift;
210 0   0       my $err = pop(@_) // "Internal Server Error";
211 0   0       my $stt = shift(@_) // 500;
212 0   0       my $cod = shift(@_) // undef;
213 0 0         my $format = $self->helpers->can("exception_format") ? $self->helpers->exception_format : 'html';
214 0 0         return _reply_noapi($self,
215             status => $stt, # HTTP status code
216             code => $cod, # The Suffit error code
217             error => $err, # Error message
218             $format eq 'html' ? (html => {template => 'error', format => 'html'}) : (),
219             );
220             }
221             sub _reply_noapi {
222 0     0     my $self = shift;
223 0           my %args = @_;
224 0   0       my $status = $args{status} || 200; # HTTP status code
225 0   0       my $code = $args{code} || "E0$status"; # The Suffit error code
226 0   0       my $message = $args{error} // $args{message} // ''; # Error message
      0        
227 0           my $data = $args{data}; # Payload data
228 0           my $html = $args{html}; # HTML options
229              
230             # Correct code and message
231 0 0         unless ($args{code}) {
232 0 0         if ($message =~ s/^(E[0-9]{4})[:]?\s+//) {
233 0           $code = $1;
234             }
235             }
236              
237             # Log
238 0 0         if ($status >= 400) {
239 0 0         $self->log->error(sprintf("[%s] %s", $code, $message)) if length $message;
240             }
241              
242             # Clean message
243 0           $message =~ s/^(E[0-9]{4})[:]?\s+//;
244              
245             # Respond (extended render)
246 0 0 0       return $self->respond_to(
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
247             json => {
248             json => {
249             status => $status < 400 ? \1 : \0,
250             length $message ? (message => $message) : (),
251             code => $code,
252             defined $data ? (is_hash_ref($data) ? (%$data) : is_array_ref($data) ? (data => $data) : ()) : (),
253             },
254             status => $status,
255             },
256             html => {
257             message => $message // '',
258             code => $code,
259             http_status => $status,
260             defined $html ? (is_hash_ref($html) ? (%$html) : ()) : (),
261             defined $data ? (is_hash_ref($data) ? (%$data) : is_array_ref($data) ? (data => $data) : ()) : (),
262             status => $status,
263             },
264             text => {
265             text => length $message ? $message : defined $data ? $self->dumper($data) : '',
266             status => $status,
267             },
268             any => {
269             text => length $message ? $message : defined $data ? $self->dumper($data) : '',
270             status => $status,
271             },
272             )
273             }
274             sub _client_ip {
275 0     0     my $self = shift;
276 0           my $trustedproxies = shift;
277 0 0 0       $self->req->trusted_proxies($trustedproxies)
      0        
278             if defined($trustedproxies) && is_array_ref($trustedproxies) && $self->req->can("trusted_proxies");
279 0           return $self->tx->remote_address; # X-Forwarded-For
280             }
281             sub _base_url {
282 0     0     my $self = shift;
283 0   0       my $base_url = $self->req->url->base->path_query('/')->to_string // '';
284 0           $base_url =~ s/\/+$//;
285 0           return $base_url;
286             }
287              
288             1;
289              
290             __END__