Devel-hdb/lib/Devel/hdb/App/Stack.pm
package Devel::hdb::App::Stack;
BEGIN {
our @saved_ARGV = @ARGV;
}
use strict;
use warnings;
use Plack::Request;
use base 'Devel::hdb::App::Base';
use Exporter 'import';
our @EXPORT_OK = qw(_serialize_stack);
use Data::Transform::ExplicitMetadata qw(encode);
our $VERSION = '0.25';
__PACKAGE__->add_route('get', qr{(^/stack$)}, \&stack);
__PACKAGE__->add_route('head', qr{^/stack$}, \&stack_head);
__PACKAGE__->add_route('get', qr{(^/stack)/(\d+)$}, \&stack_frame);
__PACKAGE__->add_route('head', qr{^/stack/(\d+)$}, \&stack_frame_head);
sub stack {
my($class, $app, $env, $base_url) = @_;
my $req = Plack::Request->new($env);
my $frames = $class->_serialize_stack($app, $base_url, $req->param('exclude_sub_params'));
return [ 200,
[ 'Content-Type' => 'application/json',
'X-Stack-Depth' => scalar(@$frames),
],
[ $app->encode_json($frames) ],
];
}
sub stack_head {
my($class, $app, $env) = @_;
my $stack = $app->stack;
return [ 200,
[ 'Content-Type' => 'application/json',
'X-Stack-Depth' => $stack->depth,
],
[],
];
}
sub stack_frame {
my($class, $app, $env, $base_url, $level) = @_;
my $req = Plack::Request->new($env);
my $stack = $app->stack;
my $frame = $stack->frame($level);
my $rv = _stack_frame_head_impl($app, $frame);
if ($rv->[0] == 200) {
my $serialized_frame = _serialize_frame($frame, $base_url, $level, $req->param('exclude_sub_params'));
$rv->[2] = [ $app->encode_json($serialized_frame) ];
}
return $rv;
}
sub stack_frame_head {
my($class, $app, $env, $level) = @_;
my $stack = $app->stack;
my $frame = $stack->frame($level);
return _stack_frame_head_impl($app, $frame);
}
sub _stack_frame_head_impl {
my($app, $frame) = @_;
unless ($frame) {
return [ 404,
[ 'Content-Type' => 'application/json' ],
[ $app->encode_json( { error => 'Stack frame not found' } ) ],
];
}
return [ 200,
[ 'Content-Type' => 'application/json',
'X-Stack-Serial' => $frame->serial,
'X-Stack-Line' => $frame->line,
],
[ ]
];
}
sub _serialize_stack {
my($class, $app, $base_url, $exclude_sub_params) = @_;
my $frames = $app->stack()->iterator;
my @stack;
my $level = 0;
while (my $frame = $frames->()) {
push @stack, _serialize_frame($frame, $base_url, $level++, $exclude_sub_params);
}
return \@stack;
}
sub _serialize_frame {
my($frame, $base_url, $level, $exclude_sub_params) = @_;
my %frame = %$frame; # copy
if ($frame{autoload}) {
$frame{subname} .= "($frame{autoload})";
}
if ($exclude_sub_params) {
$frame{args} = undef;
} elsif ($frame{subroutine} eq '(eval)') {
$frame{args} = [];
} else {
my @encoded_args = map { encode($_) } @{$frame{args}};
$frame{args} = \@encoded_args;
}
$frame{href} = join('/', $base_url, $level++);
return \%frame;
}
1;
=pod
=head1 NAME
Devel::hdb::App::Stack - Get information about the program stack
=head1 DESCRIPTION
=head2 Routes
=over 4
=item GET /stack
=item GET /stack?exclude_sub_params=1
=item HEAD /stack
Get a list of the current program stack. Does not include any stack frames
within the debugger. The currently executing frame is the first element in
the list. Returns a JSON-encoded array where each item is a hash
with the following keys:
package Package/namespace
subroutine Fully-qualified subroutine name. Includes the package
subname Subroutine name without the package included
filename File where the subroutine was defined
lineno Line execution is stopped on
args Array of arguments to the subroutine
wantarray Context this frame was called in
serial Unique serial number for this frame
The header X-Stack-Depth will have the number of frames in the stack. The
caller may request the HEAD to omit the body/data and just get the headers.
The deepest stack frame is reported as being in the subroutine named 'MAIN'.
Values in the args list are encoded using Data::Transform::ExplicitMetadata
If the param exclude_sub_params is true, then the 'args' value will be undef,
useful to avoid serializing/deserializing possibly deep data structures
passed as arguments to functions.
=item GET /stack/<id>
=item GET /stack/<id>?exclude_sub_params=1
=item HEAD /stack/<id>
Get only one stack frame. 0 is the most recent frame in the debugged program,
1 is the frame before that. Returns a JSON-encoded hash with the same
information as each stack frame returned by GET /stack. In addition, the header
X-Stack-Line contains the current frame's line number, and the header
X-Stack-Serial contains the current frame's serial. Returns a 404 error if
there is no frame as deep as was requested.
=back
=head1 SEE ALSO
L<Devel::hdb>, L<Data::Transform::ExplicitMetadata>
=head1 AUTHOR
Anthony Brummett <brummett@cpan.org>
=head1 COPYRIGHT
Copyright 2018, Anthony Brummett. This module is free software. It may
be used, redistributed and/or modified under the same terms as Perl itself.