NAME
    Perinci::Sub::Wrapper - A multi-purpose subroutine wrapping framework

VERSION
    version 0.49

SYNOPSIS
     use Perinci::Sub::Wrapper qw(wrap_sub);
     my $res = wrap_sub(sub => sub {die "test\n"}, meta=>{...});
     my ($wrapped_sub, $meta) = ($res->[2]{sub}, $res->[2]{meta});
     $wrapped_sub->(); # call the wrapped function

DESCRIPTION
    Perinci::Sub::Wrapper is an extensible subroutine wrapping framework. It
    works by creating a single "large" wrapper function from a composite
    bits of code, instead of using multiple small wrappers (a la Python's
    decorator). The single-wrapper approach has the benefit of smaller
    function call overhead. You can still wrap multiple times if needed.

    This module is used to enforce Rinci properties, e.g. "args" (by
    performing schema validation before calling the function), "timeout" (by
    doing function call inside an "eval()" and using "alarm()" to limit the
    execution), or "retry" (by wrapping function call inside a simple retry
    loop).

    It can also be used to convert argument passing style, e.g. from
    "args_as" "array" to "args_as" "hash", so you can call function using
    named arguments even though the function accepts positional arguments,
    or vice versa.

    There are many other possible uses.

    This module uses Log::Any for logging.

FUNCTIONS
  wrap_all_subs(%args) -> [status, msg, result, meta]
    {en_US Wrap all subroutines in a package and replace them with the
    wrapped version}.

    {en_US This function will search all subroutines in a package which have
    metadata, wrap them, then replace the original subroutines and metadata
    with the wrapped version.

    One common use case is to put something like this at the bottom of your
    module:

        Perinci::Sub::Wrapper::wrap_all_subs();

    to wrap ("protect") all your module's subroutines and discard the
    original unwrapped version. }

    Arguments ('*' denotes required arguments):

    *   package => *str*

        {en_US Wrap all subroutines in a package and replace them with the
        wrapped version}.

        {en_US This function will search all subroutines in a package which
        have metadata, wrap them, then replace the original subroutines and
        metadata with the wrapped version.

        One common use case is to put something like this at the bottom of
        your module:

            Perinci::Sub::Wrapper::wrap_all_subs();

        to wrap ("protect") all your module's subroutines and discard the
        original unwrapped version. }

    *   wrap_args => *hash*

        {en_US Wrap all subroutines in a package and replace them with the
        wrapped version}.

        {en_US This function will search all subroutines in a package which
        have metadata, wrap them, then replace the original subroutines and
        metadata with the wrapped version.

        One common use case is to put something like this at the bottom of
        your module:

            Perinci::Sub::Wrapper::wrap_all_subs();

        to wrap ("protect") all your module's subroutines and discard the
        original unwrapped version. }

    Return value:

    Returns an enveloped result (an array). First element (status) is an
    integer containing HTTP status code (200 means OK, 4xx caller error, 5xx
    function error). Second element (msg) is a string containing error
    message, or 'OK' if status is 200. Third element (result) is optional,
    the actual result. Fourth element (meta) is called result metadata and
    is optional, a hash that contains extra information.

  wrap_sub(%args) -> [status, msg, result, meta]
    {en_US Wrap subroutine to do various things, like enforcing Rinci
    properties}.

    {en_US Will wrap subroutine and bless the generated wrapped subroutine
    (by default into "Perinci::Sub::Wrapped") as a way of marking that the
    subroutine is a wrapped one. }

    Arguments ('*' denotes required arguments):

    *   allow_invalid_args => *bool* (default: 0)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   allow_unknown_args => *bool* (default: 0)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   compile => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   convert => *hash*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   debug => *bool* (default: 0)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   forbid_tags => *array*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   meta* => *hash*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   normalize_schemas => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   remove_internal_properties => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   skip => *array*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   sub => *code*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   sub_name => *str*

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   trap => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   validate_args => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    *   validate_result => *bool* (default: 1)

        {en_US Wrap subroutine to do various things, like enforcing Rinci
        properties}.

        {en_US Will wrap subroutine and bless the generated wrapped
        subroutine (by default into "Perinci::Sub::Wrapped") as a way of
        marking that the subroutine is a wrapped one. }

    Return value:

    Returns an enveloped result (an array). First element (status) is an
    integer containing HTTP status code (200 means OK, 4xx caller error, 5xx
    function error). Second element (msg) is a string containing error
    message, or 'OK' if status is 200. Third element (result) is optional,
    the actual result. Fourth element (meta) is called result metadata and
    is optional, a hash that contains extra information.

USAGE
    Suppose you have a subroutine like this:

     sub gen_random_array {
         my %args = @_;
         my $len = $args{len} // 10;
         die "Length too big" if $len > 1000;
         die "Please specify at least length=1" if $len < 1;
         [map {rand} 1..$len];
     }

    Wrapping can, among others, validate arguments and give default values.
    First you add a Rinci metadata to your subroutine:

     our %SPEC;
     $SPEC{gen_random_array} = {
         v => 1.1,
         summary=> 'Generate an array of specified length containing random values',
         args => {
             len => {req=>1, schema => ["int*" => between => [1, 1000]]},
         },
         result_naked=>1,
     };

    You can then remove code that validates arguments and gives default
    values. You might also want to make sure that your subroutine is run
    wrapped.

     sub gen_random_array {
         my %args = @_;
         die "This subroutine needs wrapping" unless $args{-wrapped}; # optional
         [map {rand} 1..$args{len}];
     }

    Most wrapping options can also be put in "_perinci.sub.wrapper.*"
    attributes. For example:

     $SPEC{gen_random_array} = {
         v => 1.1,
         args => {
             len => {req=>1, schema => ["int*" => between => [1, 1000]]},
         },
         result_naked=>1,
         # skip validating arguments because sub already implements it
         "_perinci.sub.wrapper.validate_args" => 0,
     };
     sub gen_random_array {
         my %args = @_;
         my $len = $args{len} // 10;
         die "Length too big" if $len > 1000;
         die "Please specify at least length=1" if $len < 1;
         [map {rand} 1..$len];
     }

    See also Dist::Zilla::Plugin::Rinci::Validate which can insert
    validation code into your Perl source code files so you can skip doing
    it again in validation.

EXTENDING
    The framework is simple and extensible. Please delve directly into the
    source code for now. Some notes:

    The internal uses OO.

    The main wrapper building mechanism is in the "wrap()" method.

    For each Rinci property, it will call "handle_NAME()" wrapper handler
    method. The "handlemeta_NAME()" methods are called first, to determine
    order of processing. You can supply these methods either by subclassing
    the class or, more simply, monkeypatching the method in the
    "Perinci::Sub::Wrapper" package.

    The wrapper handler method will be called with a hash argument,
    containing these keys: value (property value), new (this key will exist
    if "convert" argument of "wrap()" exists, to convert a property to a new
    value).

    For properties that have name in the form of "NAME1.NAME2.NAME3" (i.e.,
    dotted) only the first part of the name will be used (i.e.,
    "handle_NAME1()").

VARIABLES
  $Log_Wrapper_Code (BOOL)
    Whether to log wrapper result. Default is from environment variable
    LOG_PERINCI_WRAPPER_CODE, or false. Logging is done with Log::Any at
    trace level.

METHODS
    The OO interface is only used internally or when you want to extend the
    wrapper.

ENVIRONMENT
  LOG_PERINCI_WRAPPER_CODE (bool)
    If set to 1, will log the generated wrapper code. This value is used to
    set $Log_Wrapper_Code if it is not already set.

  PERINCI_WRAPPER_VALIDATE_ARGS (bool, default 1)
    Can be set to 0 to skip adding validation code. This provides a default
    for "validate_args" wrap_sub() argument.

PERFORMANCE NOTES
    The following numbers are produced on an Asus Zenbook UX31 laptop (Intel
    Core i5 1.7GHz) using Perinci::Sub::Wrapper v0.33 and Perl v5.14.2.
    Operating system is Ubuntu 11.10 (64bit).

    For perspective, empty subroutine ("sub {}") as well as "sub { [200,
    "OK"] }" can be called around 4.3 mil/sec.

    Wrapping this subroutine "sub { [200, "OK"] }" and this simple metadata
    "{v=>1.1, args=>{a=>{schema=>"int"}}}" using default options yields call
    performance for "$sub->()" of about 0.28 mil/sec. For "$sub->(a=>1)" it
    is about 0.12 mil/sec. So if your sub needs to be called a million times
    a second, the wrapping adds too big of an overhead.

    By default, wrapper provides these functionality: checking invalid and
    unknown arguments, argument value validation, exception trapping ("eval
    {}"), and result checking. If we turn off all these features except
    argument validation (by adding options "allow_invalid_args=>1, trap=>0,
    validate_result=>0") call performance increases to around 0.47 mil/sec
    (for "$sub->()" and 0.24 mil/sec (for "$sub->(a=>1)").

    As more arguments are introduced in the schema and passed, and as
    argument schemas become more complex, overhead will increase. For
    example, for 5 int arguments being declared and passed, call performance
    is around 0.11 mil/sec. Without passing any argument when calling, call
    performance is still around 0.43 mil/sec, indicating that the
    significant portion of the overhead is in argument validation.

FAQ
  How to display the wrapper code being generated?
    If environment variable LOG_PERINCI_WRAPPER_CODE or package variable
    $Log_Perinci_Wrapper_Code is set to true, generated wrapper source code
    is logged at trace level using Log::Any. It can be displayed, for
    example, using Log::Any::App:

     % LOG_PERINCI_WRAPPER_CODE=1 TRACE=1 \
       perl -MLog::Any::App -MPerinci::Sub::Wrapper=wrap_sub \
       -e 'wrap_sub(sub=>sub{}, meta=>{v=>1.1, args=>{a=>{schema=>"int"}}});'

    Note that Data::Sah (the module used to generate validator code)
    observes "LOG_SAH_VALIDATOR_CODE", but during wrapping this environment
    flag is currently disabled by this module, so you need to set
    LOG_PERINCI_WRAPPER_CODE instead.

  How do I tell if I am being wrapped?
    Wrapper code passes "-wrapped" special argument with a true value. So
    you can do something like this:

     sub my_sub {
         my %args = @_;
         return [412, "I need to be wrapped"] unless $args{-wrapped};
         ...
     }

    Your subroutine needs accept arguments as hash/hashref.

  caller() doesn't work from inside my wrapped code!
    Wrapping adds at least one or two levels of calls: one for the wrapper
    subroutine itself, the other is for the eval trap loop which can be
    disabled but is enabled by default. The 'goto &NAME' special form, which
    can replace subroutine and avoid adding another call level, cannot be
    used because wrapping also needs to postprocess function result.

    This poses a problem if you need to call caller() from within your
    wrapped code; it will also be off by at least one or two.

    The solution is for your function to use the caller() replacement,
    provided by Perinci::Sub::Util.

  But that is not transparent!
    True. The wrapped module needs to load and use that utility module
    explicitly.

    An alternative is for Perinci::Sub::Wrapper to use Sub::Uplevel.
    Currently though, this module does not use Sub::Uplevel because, as
    explained in its manpage, it is rather slow. If you don't use caller(),
    your subroutine actually doesn't need to care if it is wrapped nor it
    needs "uplevel-ing".

  How to ensure that users use the wrapped functions?
    Sometimes you do rely on the functionalities provided by wrapping, most
    notably argument validation, and you want to make sure that arguments
    are always validated when users execute your function.

    If your module use Perinci::Exporter, users use()-ing your module will
    by default import the wrapped version of your functions. But they can
    turn this off via passing "wrap => 0".

    Another alternative is to embed the generated argument validation code
    directly into your built source code. If you use Dist::Zilla, take a
    look Dist::Zilla::Plugin::Rinci::Validate. This only covers the argument
    validation functionality and not others, but this does not add levels of
    calls or modifies the line numbers of your source code, so this solution
    is very transparent.

    I might write another dzil plugin which embeds the whole wrapper code
    into the source code, should there be such a demand.

SEE ALSO
    Perinci

HOMEPAGE
    Please visit the project's homepage at
    <https://metacpan.org/release/Perinci-Sub-Wrapper>.

SOURCE
    Source repository is at
    <https://github.com/sharyanto/perl-Perinci-Sub-Wrapper>.

BUGS
    Please report any bugs or feature requests on the bugtracker website
    <https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Sub-Wrapper>

    When submitting a bug or request, please include a test-file or a patch
    to an existing test-file that illustrates the bug or desired feature.

AUTHOR
    Steven Haryanto <stevenharyanto@gmail.com>

COPYRIGHT AND LICENSE
    This software is copyright (c) 2013 by Steven Haryanto.

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

