Group
Extension

WWW-Suffit-Plugin-CommonHelpers/lib/WWW/Suffit/Plugin/CommonHelpers.pm

package WWW::Suffit::Plugin::CommonHelpers;
use strict;
use utf8;

=encoding utf8

=head1 NAME

WWW::Suffit::Plugin::CommonHelpers - Common helpers plugin for Suffit API servers

=head1 SYNOPSIS

    # in your startup
    $self->plugin('WWW::Suffit::Plugin::CommonHelpers');

=head1 DESCRIPTION

This plugin is a collection of common helpers for Suffit API servers

=head1 HELPERS

This plugin implements the following helpers

=head2 base_url

    my $url = $c->base_url;

Returns the base URL from request

=head2 client_ip

    my $ip = $c->client_ip;
    my $ip = $c->client_ip([ ..trusted_proxies ...]);

Returns the client IP address

=head2 remote_ip

See L</client_ip>

=head2 reply.error

    return $c->reply->error(); # 500, E0500, "Internal server error"
    return $c->reply->error("Error message"); # 500, E0500, "Error message"
    return $c->reply->error(501 => "Error message");
    return $c->reply->error(501 => "Error code" => "Error message");

The method returns error in client request format

B<NOTE!>: This method with HTML format requires the 'error' template

=head2 reply.json_error

    return $c->reply->json_error(); # 500, E0500, "Internal server error"
    return $c->reply->json_error("Error message"); # 500, E0500, "Error message"
    return $c->reply->json_error(501 => "Error message");
    return $c->reply->json_error(501 => "Error code" => "Error message");

    {
      "code": "Error code",
      "message": "Error message",
      "status": false
    }

The method returns API error as JSON response

=head2 reply.json_ok

    return $c->reply->json_ok(); # 200, ""
    return $c->reply->json_ok("Ok."); # 200, "Ok."
    return $c->reply->json_ok(201 => "Ok."); # 201, "Ok."

    {
      "code": "E0000",
      "message": "Ok.",
      "status": true
    }

    return $c->reply->json_ok({foo => "bar"}); # 200, {...}

    {
      "code": "E0000",
      "foo": "bar",
      "status": true
    }

    return $c->reply->json_ok(201 => {foo => "bar"}); # 201, {...}

    # 201
    {
      "code": "E0000",
      "foo": "bar",
      "status": true
    }

The method returns API success status as JSON response

=head2 reply.noapi

    return $c->reply->noapi(
        status  => 501, # HTTP status code (default: 200)
        code    => "E0501", # The Suffit error code
        message => "Error message",
        data    => {...}, # Payload data
        html    => { template => "error" }, # HTML options
    );

The method returns data in client request format

=head1 METHODS

Internal methods

=head2 register

Do not use directly. It is called by Mojolicious.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Plugin>

=head1 AUTHOR

Serż Minus (Sergey Lepenkov) L<https://www.serzik.com> E<lt>abalama@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright (C) 1998-2025 D&D Corporation. All Rights Reserved

=head1 LICENSE

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

See C<LICENSE> file and L<https://dev.perl.org/licenses/>

=cut

use Mojo::Base 'Mojolicious::Plugin';

our $VERSION = '1.01';

use Acrux::RefUtil qw/ is_array_ref is_hash_ref isnt_void is_int8 /;

sub register {
    my ($self, $app, $opts) = @_; # $self = $plugin
    $opts //= {};

    # JSON API responses
    $app->helper('reply.json_error',    => \&_reply_json_error);
    $app->helper('reply.json_ok'        => \&_reply_json_ok);

    # No JSON API responses
    $app->helper('reply.error'          => \&_reply_error);
    $app->helper('reply.noapi'          => \&_reply_noapi);

    # Get Client/Remote IP address
    $app->helper('client_ip'            => \&_client_ip);
    $app->helper('remote_ip'            => \&_client_ip);

    # Get base URL
    $app->helper('base_url'             => \&_base_url);
}

sub _reply_json_error {
    my $self = shift;
    my $err = pop(@_) // "Internal Server Error";
    my $stt = shift(@_) || 500;
    my $cod = shift(@_);

    # Correct code and message
    unless ($cod) {
        if ($err =~ s/^(E[0-9]{4})[:]?\s+//) {
            $cod = $1;
        }
        $cod ||= "E0$stt";
    }

    # Log
    $self->log->error(sprintf("[%s] %s", $cod, $err)) if length $err;

    # Clean message
    $err =~ s/^(E[0-9]{4})[:]?\s+//;

    # Render
    return $self->render(
        json => {
            status => \0,
            code => $cod,
            length $err ? (message => $err) : (),
        },
        status => $stt,
    );
}
sub _reply_json_ok {
    my $self = shift;
    my $e = pop(@_) // "";
    my $s = pop(@_) // 200;
    my %j = (status => \1, code => 'E0000');
    my %d = ();
    if (is_hash_ref($e)) {
        %d = %$e;
    } elsif($e ne "") {
        $j{message} = $e;
    }
    return $self->render(json => {%j, %d}, status => $s);
}
sub _reply_error {
    my $self = shift;
    my $err = pop(@_) // "Internal Server Error";
    my $stt = shift(@_) // 500;
    my $cod = shift(@_) // undef;
    my $format = $self->helpers->can("exception_format") ? $self->helpers->exception_format : 'html';
    return _reply_noapi($self,
            status  => $stt, # HTTP status code
            code    => $cod, # The Suffit error code
            error   => $err, # Error message
            $format eq 'html' ? (html => {template => 'error', format => 'html'}) : (),
        );
}
sub _reply_noapi {
    my $self = shift;
    my %args = @_;
    my $status  = $args{status} || 200;         # HTTP status code
    my $code    = $args{code} || "E0$status";   # The Suffit error code
    my $message = $args{error} // $args{message} // ''; # Error message
    my $data    = $args{data}; # Payload data
    my $html    = $args{html}; # HTML options

    # Correct code and message
    unless ($args{code}) {
        if ($message =~ s/^(E[0-9]{4})[:]?\s+//) {
            $code = $1;
        }
    }

    # Log
    if ($status >= 400) {
        $self->log->error(sprintf("[%s] %s", $code, $message)) if length $message;
    }

    # Clean message
    $message =~ s/^(E[0-9]{4})[:]?\s+//;

    # Respond (extended render)
    return $self->respond_to(
        json    => {
                    json    => {
                            status => $status < 400 ? \1 : \0,
                            length $message ? (message => $message) : (),
                            code => $code,
                            defined $data ? (is_hash_ref($data) ? (%$data) : is_array_ref($data) ? (data => $data) : ()) : (),
                        },
                    status  => $status,
                },
        html    => {
                    message => $message // '',
                    code    => $code,
                    http_status => $status,
                    defined $html ? (is_hash_ref($html) ? (%$html) : ()) : (),
                    defined $data ? (is_hash_ref($data) ? (%$data) : is_array_ref($data) ? (data => $data) : ()) : (),
                    status  => $status,
                },
        text    => {
                    text    => length $message ? $message : defined $data ? $self->dumper($data) : '',
                    status  => $status,
                },
        any     => {
                    text    => length $message ? $message : defined $data ? $self->dumper($data) : '',
                    status  => $status,
                },
    )
}
sub _client_ip {
    my $self = shift;
    my $trustedproxies = shift;
    $self->req->trusted_proxies($trustedproxies)
        if defined($trustedproxies) && is_array_ref($trustedproxies) && $self->req->can("trusted_proxies");
    return $self->tx->remote_address; # X-Forwarded-For
}
sub _base_url {
    my $self = shift;
    my $base_url = $self->req->url->base->path_query('/')->to_string // '';
       $base_url =~ s/\/+$//;
    return $base_url;
}

1;

__END__


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.